再來要解決前兩篇提到的第二個問題:Blender讀取OBJ,和輸出PMD,PMX會出錯。
前兩篇:
【進度】2019年2月~3月的進度 (1)前一篇:
【進度】2月~3月的進度(2) & 鈷寶的工作介紹雖然以前沒做過Blender的add-on,得從頭學起,我想遲早要學的,如果以後做3D遊戲一定會常常用Blender修改東西再輸出資料。
同時發在官網
這次不只我,鈷寶也要學習新的東西。
艾莉兒:雖然輔助工具的部分我幫不上忙,不過主人和鈷寶加油!鈷寶:可是……主人……,從哪裡做起呢。先查一下Blender官方文件吧
Blender API Quickstart IntroductionBlender API Addon Tutorial還有看Blender內建的OBJ importer怎麼寫的,位於Blender安裝資料夾的這個位置。(把2.79換成你的Blender版本)
……\Blender Foundation\Blender\2.79\scripts\addons\io_scene_obj\ |
先寫個最底限還不做任何事的add-on試試看。
script裡可以用print()輸出訊息,在Blender用目錄裡的Window → Toggle System Console開啟命令列視窗就能看到print()輸出的訊息,開發期可以利用這個debug。
利用Console視窗顯示錯誤訊息,一步步寫出能work的程式。
必要的程式碼大概是這些。
#!/usr/bin/python3 #coding:utf-8 #寫Python程式必須有上面兩行
#必要 import bpy #寫importer(增加Blender支援的檔案格式)要加這個 from bpy_extras.io_utils import ImportHelper
#顯示在Blender add-on一覽的說明 bl_info = { "name": "OBJ to PMD converter for CS Engine", "description": "Convert OBJ to PMD for used in Cyber Sprite Engine", "author": "shark0r", "version": (0,0,1), "blender": (2, 78, 0), "location": "File > Import-Export", "warning": "", "wiki_url": "", "support": 'TESTING', "category": "Import-Export" }
#這個class做實際工作 class ObjImport(bpy.types.Operator, ImportHelper): bl_idname = 'csengine.import_obj' bl_label = 'Import Wavefront obj file' bl_options = {'PRESET','UNDO'} filename_ext = ".obj"
#實際工作放在這裡,Blender執行add-on時會呼叫這個function def execute(self, context): scene=context.scene keywords=self.as_keywords() objFilePath=keywords['filepath'] #檔名 ………… return {'FINISHED'}
def menu_func_import(self, context): self.layout.operator(ObjImport.bl_idname, text="OBJ import for CS engine (.obj)")
#Blender載入和重讀add-on時會呼叫這兩個,把class註冊進Blender,以及在選單增加項目 def register(): bpy.utils.register_class(ObjImport) bpy.types.INFO_MT_file_import.append(menu_func_import)
def unregister(): bpy.utils.unregister_class(ObjImport) bpy.types.INFO_MT_file_import.remove(menu_func_import)
#必須寫這一行才能正常運作 if __name__ == "__main__": register() |
Blender的add-on是可以在目錄甚至編輯器新增UI的,但用法官方文件也沒介紹,要自己翻這裡的檔案。
……\Blender Foundation\Blender\2.79\scripts\startup\bl_ui\ |
放置script的地方是使用者資料夾下的Blender資料夾,如Windows是在這裡。
C:\Users\(使用者名稱)\AppData\Roaming\Blender Foundation\Blender\2.79\scripts\addons\ |
在這裡建個資料夾,把自己寫的script存成__init__.py,如下
C:\Users\(使用者名稱)\AppData\Roaming\Blender Foundation\Blender\2.79\scripts\addons\csengine_converter\__init__.py |
成功的話在Add-on一覽就可以看到自已寫的add-on了。
————————
再來加入功能,首先是載入OBJ檔,把裡面的資料轉換成Python的資料結構,這一步跟前一篇「
2月~3月的進度(2)」一樣故不再贅述。
然後建立mesh物件,讓這模型在Blender裡顯示。
參考這篇:
Blender API的Mesh說明當然也參考內建的OBJ importer是怎麼建立模型的。
鈷寶:好多屬性,該從……哪裡開始?那篇說明裡有寫「Blender stores 4 main arrays to define mesh geometry.」,從這裡入手吧。
…………(研究中)
mesh屬性很多,但是儲存幾何形狀的只有這四個陣列。
mesh.Verticesmesh.Edgesmesh.Loopsmesh.Polygons四者的關係如下面的圖。
這四個物件各自也有很多屬性,下圖的藍字是本例需要設的屬性。
好,鈷寶,照我的指示建立mesh物件。
鈷寶:嗯。1.用 mesh=bpy.data.meshes.new() 建立mesh物件。
2.用 mesh.vertices.add() 設定陣列長度。
3.一個一個設定各個頂點的co和normal屬性。
4.edge、loop、polygon也是類似的方法填資料。
5.填好資料後呼叫這幾個函式檢查資料,並把mesh加入scene。
mesh.validate()
mesh.update()
bpyObj=bpy.data.objects.new(meshName, mesh)
scene.objects.link(bpyObj)
scene.update()
先不處理材質和貼圖坐標,先把位置和法線弄好看看。
還有一個地方要注意,OBJ和Blender的坐標軸不一樣,設定頂點坐標和法線的時候要把坐標調換一下。
這個階段的成果,匯入一個OBJ檔看頂點部分的特寫,左邊用自製的add-on,右邊是Blender內建的importer。
短的藍線是法線,這個OBJ內部資料是一個頂點有三條法線,左邊可以正確讀取三條法線,右邊則不行。
艾莉兒:等一下,上面右邊的圖法線是錯的,但是有做出flat shading。對哦,到底是為什麼?
看了script API文件才知道Blender是用兩個方法標記是smooth還是flat shading,如果是flat shading就不用頂點法線計算打光。
1.polygon是否有use_smooth的標記。
2.edge是否有use_edge_sharp的標記。
不像3DS max是用smoothing group。
Blender裡按T鍵出現的選單有這幾項。
Faces是設定上面的第一項,Edges和Vertices是設定第二項,Vertices實際是把交會在這個頂點的邊都設為sharp。
Blender匯入OBJ時法線會錯,但是可以解析smoothing group的資料正確設定shading種類,或許讀取OBJ的功能是可以用的,問題出在輸出PMD和PMX的add-on,沒有讀取Blender的smooth資訊所以輸出的模型錯了。
所以真正要自己幹的是輸出PMD、PMX的工具。
艾莉兒:什麼?……那上面OBJ importer白做了嗎?也不是完全沒用,練習一次之後比較了解Blender內部資料是怎麼樣了。
前面做的是載入檔案,現在則是輸出檔案了。
位置、法線照上面說的,從mesh.vertices、mesh.loops這些陣列取得。
此外要檢查edge.use_edge_sharp和polygon.use_smooth,如果是flat shading就把法線設成特殊的值,讓艾莉兒可以利用我做的flat shading功能。
鈷寶:好。前面沒有用到材質和貼圖,現在要用了。
貼圖坐標存在mesh.uv_layers[n].data,是跟loops對應的陣列。例如loops[35]的頂點,貼圖坐標放在uv_layers[0].data[35]。
此外Blender裡一個mesh可以存多組貼圖坐標,所以uv_layers是個陣列
材質放在mesh.materials[polygon.material_index],如名稱所示是polygon的屬性,貼圖放在material.active_texture。
Blender的貼圖除了圖檔以外還有很多種由程式自動產生的,要先檢查texture.type,=='IMAGE' 的才是圖檔,此時這個texture是ImageTexture物件,然後用texture.image.filepath取得檔名。
Material說明Texture說明ImageTexture說明鈷寶,讀取Blender的上面這些資料,輸出材質和貼圖。
鈷寶:……。(開始工作)如何寫入PMD檔也是「
2月~3月的進度(2)」提過,在此省略。
做到這裡碰到一些坑,寫Blender add-on有一些難搞的地方,要特別注意。
- 不論哪個軟體,製作外掛常要反覆做修改→重新讀取的操作,從零開始一步步寫到完成。Blender重讀script要有點技巧。
重讀的方法是在User Perferences裡設定Add-on的地方,把這個勾取消再點選。
如果script只有__init__.py那上面這樣就行了,但如果有其他檔案讓它import,要用一些技巧。
__init__.py以外的檔案只有啟動時會載入一次,之後重新載入add-on只會重讀__init__.py,所以import其他檔案的時候要這樣寫強制重讀。
import importlib if "export_pmd" in locals(): print('reload export_pmd') importlib.reload(export_pmd)
from . import export_pmd |
加個print()才能清楚看出有沒有重讀。
如果只修改其他檔案而沒動到__init__.py,要設法把__init__.py重新寫入磁碟一次(例如打一個空白,刪除,再存檔),讓它的「檔案修改時間」更新。因為Blender只會檢查__init__.py一個檔案,如果沒有修改就不重讀add-on。
- 貼圖完全不透明的情況下,材質設定裡的Diffuse color其實沒用處,貼圖會把這個顏色蓋掉。
Blender文件裡Texture的說明
雖然貼圖設定裡的Influence可以修改計算方式把Diffuse color拿來用,但也做不到接近PMD、PMX和我的引擎的方式。儘量能在Blender裡看到實際效果比較好調參數,否則必須反覆「調整→輸出→用引擎載入看效果→回來調整」很麻煩。
試了一下發現Ramp設定看起來是能做到最接近PMD、PMX做法的,如下圖可以把光線斜射的地方變成偏紅,但光沒照到,只受ambient影響的地方還是不能修改顏色。
輸出到PMD、PMX的ambient和diffuse可能改成讀取Ramp比較好。
至於Specular color可以直接使用Blender的Specular color,不用用到Ramp。
研究Blender的打光演算法就想到,現在好像很多3D軟體用一個叫Physically Based Rendering的方法計算打光,例如這兩個的說明。
UnityglTF format,看Material的部分畫圖有個常用技巧:畫陰影時不是只把基本色變暗,而是有色相和彩度變化,這樣整體畫面會比較鮮艷。
但是上面的Physically Based Rendering只有Metallic和Roughness兩個參數,陰影只能把亮度變暗而沒有色相和彩度變化。我想這是為什麼很多3D遊戲都有色彩灰暗的感覺。
既然很少人會用,以後做3D遊戲想試試看這個技巧。只是3D美術怎麼培訓是問題,如果美術人員學不會調ambient和diffuse的方法,那也無法發揮威力。
現在3D模型轉格式的工法是這樣,艾莉兒、鈷寶、還有我自己都必須上場。
Blender輸出PMD、PMX的add-on試過pymeshio、mmd_script、mmd_tools、exp_pmx等好幾個,每個多少都有不能滿足需求的地方,只好自己打造一個。
艾莉兒:厲害,主人,以後轉換格式靠這個工具就行了嗎?其實還沒有,自製的PMD exporter只完成了初步,還有很多不足的地方。
- 一次只能輸出一個物件,還是scene裡所有物件都要輸出?
- 新增UI編輯PMD、PMX專屬資料。
- 雖然目前還不做動態,以後要做的話bone要怎麼處理?
- 在Blender裡使用自訂shader,讓Blender裡看到的跟遊戲裡看到的一樣。
(這我查過資料覺得應該做不到。)
不過自己寫的東西比較能按照自己的需求做,而且即使用別人的工具,熟悉工具的做法後也比較知道要如何改造。
弄完以上一連串工程,總算讓第二關背景可以在畫面上出現了。
艾莉兒:模型給我,開始上工囉。
主人快看,這樣行嗎?鈷寶:打光……好像……怪怪的。看起來Blender輸出法線的部分還有問題,要繼續修,不過本篇進度文先寫到這裡。
一個模型就將近70000個頂點是太多了點,而且是用在背景不需要很精細的,要叫美術想辦法減少頂點和面的數量。
有一句俗話是「不要重新發明輪子」,但實際做過東西之後覺得這句話不太符合實際情況,別人的輪子規格不合、有瑕疵、或者這個領域的輪子根本還沒發明的情況很常見。
我覺得這樣講比較對:「要有發明輪子的能力」,不是每次都要把能力拿出來用,但遇到不時之需時要能夠應付。
3D格式轉換難搞是因為OBJ、Blender和PMD,PMX內部構造先天不一樣,資料要怎麼一一對應讓人很頭痛,使用現成的軟體有這個難處。
很多遊戲引擎有自己做一個編輯器,可以編輯引擎專屬的資料,不知道我哪天會不會想自己開發一個3D編輯器。
艾莉兒支援檔案格式的能力其實很弱,3D模型只支援PMD和PMX,tilemap和骨骼動畫只能吃自訂的binary格式,XML則是無能為力,完全要靠鈷寶事先轉換格式並做資料最佳化,才能給艾莉兒用。
艾莉兒:各……各位讀者可別誤會!我是為了追求速度和輕量化,才捨棄不必要的能力,我可一點也不弱!