創作內容

4 GP

【進度】挑戰Blender外掛——開發3D轉檔工具

作者:Shark│2019-04-28 23:27:06│贊助:8│人氣:544
再來要解決前兩篇提到的第二個問題:Blender讀取OBJ,和輸出PMD,PMX會出錯。
前兩篇:【進度】2019年2月~3月的進度 (1)
前一篇:【進度】2月~3月的進度(2) & 鈷寶的工作介紹

雖然以前沒做過Blender的add-on,得從頭學起,我想遲早要學的,如果以後做3D遊戲一定會常常用Blender修改東西再輸出資料。

同時發在官網



這次不只我,鈷寶也要學習新的東西。

艾莉兒:雖然輔助工具的部分我幫不上忙,不過主人和鈷寶加油!
鈷寶:可是……主人……,從哪裡做起呢。

先查一下Blender官方文件吧
Blender API Quickstart Introduction
Blender 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.Vertices
mesh.Edges
mesh.Loops
mesh.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有一些難搞的地方,要特別注意。
  1. 不論哪個軟體,製作外掛常要反覆做修改→重新讀取的操作,從零開始一步步寫到完成。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。
  2. 貼圖完全不透明的情況下,材質設定裡的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的方法計算打光,例如這兩個的說明。
Unity
glTF 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則是無能為力,完全要靠鈷寶事先轉換格式並做資料最佳化,才能給艾莉兒用。

艾莉兒:各……各位讀者可別誤會!我是為了追求速度和輕量化,才捨棄不必要的能力,我可一點也不弱!
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=4375065
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:Cyber Sprite|遊戲製作|Blender|3D

留言共 2 篇留言

ays.
不好意思問個小問題,不考慮直接用blender 輸出 obj 然後用 pmxEditor 轉成 pmx 嗎?

05-02 01:22

Shark
有試過用PMXEditor讀取OBJ,問題很多。
像是原來的OBJ檔有17個材質,但PMXEditor不會把相同材質的面合併,讀取後變成1626個。
還有本來約有20000個面,讀取後不知為何變成600多萬個。05-02 21:24
ays.
原來如此,感謝你的回答

05-03 05:01

我要留言提醒:您尚未登入,請先登入再留言

4喜歡★shark0r 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:【進度】2月~3月的進度... 後一篇:【程式】遊戲程式基本架構...

追蹤私訊

作品資料夾

henry881025大家
你在大聲什麼啦看更多我要大聲說2小時前


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】