自從以前看了clayman的博客后,就很想把自己代碼里dx的effect框架替代掉。如今實習結束正好有短暫的自由時間,所以就ogre的material開刀吧。
山寨代碼也是有學問的,如果你想搞明白這些代碼的含義的話,最后之前就對這些已經有了一定的使用經歷和認知,第一步先得自己想一下material系統(tǒng)的組成:
material應該包含的:1Material->n Techinque->(n Pass + extra params)->(n TextureUnit + m or 0 shaderUnit + n RenderStates(部分)),Pass里的TextureUnit又包含了這一組texture的相關信息和設置,TexcoordSet, AddressMode, FilterSetting等等, ShaderUnit則是對應于vs,fs以及新的gs,普通點也就最多兩組。shaderUnit包含了具體的shader和其shaderParams,為何要把shader和shaderParams分開呢,不說設計優(yōu)雅的考慮,簡單的說就是方便,很多時候params可能會需要共享,譬如shaderA里用到mvpMat,和eyePos, diffuse,specular,可能shaderB也只用到這些,但是兩個shader的內部計算并不相同。到此我們只分析了作為程序中的材質的結構,還需要考慮如果編寫一個類似fx格式的material script,這也是另外一個大頭,簡單的說就是要做個腳本解釋,然后給shader參數加上語義,減少大部分需要手動更新的shaderParams。鑒于我如今還沒把這塊扒完,等下次再寫這塊吧。
當你至少能閉著眼睛想透這些了,那就可以開始看ogre的material system了,先說句題外話,我讀ogre的代碼其實讀挺久了。一直覺得很難讀,開始覺得是自己水平不夠,但后來實習才發(fā)現(xiàn)很多人都有這想法,甚至wolfgang對此也有批評,而自己曾經寫了個olong引擎,接觸過ipad編程的人應該對這些代碼都比較熟悉~ogre里用了大量的設計模式,使得整個渲染流程從不同類里跳來跳去(SceneMgr這塊看著最累了。。其實有很多地方看著都很累。。)個人覺得應該把render從SceneMgr里抽離出來做個RenderMgr,(記住這里的RenderMgr的概念和ogre的RenderSystem,后者是提供底層渲染API的接口,而前者是管理renderData并最終遞交給RenderSystem,以前也見有別人博客講過這個問題)讓SceneMgr專門管理Scene。。
打住打住。。我是來寫material的。。咳咳。。相當于渲染來說,ogre的material system還是相當易讀的,也是因為這些模塊相互關系幾乎都是單向的,我們開始編寫的時候可以先不考慮Technique,就是直接1 Material->n Pass->...當然甚至你都可以把multi pass去掉,就等同于1個pass,當然由于pass都代碼實際也沒多少,我們還是加上好了。
原始渲染的偽代碼就是:
1
for each pass
2

{
3
setRenderStates(like alphe belnd, depth..)
4
for each texture unit
5
setTexSettings();
6
if(hasVertexShader())
7
{
8
bindVertexShader();
9
setShaderParams(vs);
10
}
11
if(hasFragmentShader())
12
{
13
bindFragmentShader();
14
setShaderParams(fs);
15
}
16
drawPrimitives();
17
}
加入的RenderQueue也是一樣,只是pass和對應的renderData我們是從renderqueue里取。
看到這里你會覺得,嘿,很簡單嘛,但如果你看了ogre里關于shader參數的細節(jié),你就知道這塊還是比較復雜的。我們剛只說了一個運行過程,而建立params到constant registers的關系,以及如何編寫autoConstants的代碼都是魔鬼的細節(jié)~~對于dx,我們可以簡單點,直接使用ID3DXConstantTable,它提供了set接口。但ogre則是直接從ID3DXConstantTable中解析參數。
設計這塊的時候,腦子里先要對我整個過程有個清晰的認識:一方面是從shader里(我們這里假設是hlsl,asm先不考慮),另一方面則是解析參數讀入材質腳本里定義的參數(named,autoNamed,或者index),最后將兩者對應上。
0.類型解釋
看過OgreGpuProgramParams.cpp/h的人都知道:GpuConstantDefinition,GpuNamedConstants,GpuLogicalIndexUse,GpuSharedParameters,GpuSharedParametersUsage,一堆的struct和class。。其實按照我們上面這個思路逐步加斷點分析,代碼也沒那么繁瑣。暫時不看GpuSharedParameters這塊,大概看下GpuConstantDefinition,重點變成員是physicalIndex和logicalIndex,前者的解釋是buffer中的起始地址,每個GpuProgramParameters里都包含了vector<int>和vector<float>兩個buffer,他們存儲了shader變量的值,而physicalIndex就是對應vector的索引。而logicalIndex則是我們后面將hlsl編譯成asm后這些變量所綁定的寄存器id,而由于constant reg的size都是4,即一個register為4個float的大小,所以這些logicalIndex則不一定連續(xù),譬如第一個uniform為float1那么第二個uniform的logicalIndex雖然是1,但實際跨過了4個float,而實際內存中我們的float(int同) buffer里,兩個變量之間的則就空了3個float,這就是physicalIndex的作用,所以我們還需要建立一個從logicalIndex到physicalIndex的map,也就是兩個GpuLogicalBuffersStructPtr對象(這兩個對象和GpuNamedConstantsPtr在GpuProgram類和GpuProgramParameters是共享的,都是sharedPtr)
1.解析shader的變量
我們從再具體的讀取代碼來看,假設我們已經從腳本里讀入了對應的shader,經過一堆調用后(略。。)在D3D9HLSLProgram的函數buildConstantDefinitions->processParamElement將每個參數的registerIndex,physicalIndex, type(原子類型,即float, int,),size都寫入GpuLogicalBuffersStructPtr和GpuNamedConstantsPtr對象里(后者當然是只有highLevelShader里才有),并會為這個定義同樣在插入帶下標的一對鍵值,以便于如果我們傳入的參數是數組(譬如float4x4[5]),則在程序里可以通過數組下標訪問對應的數組變量。再constantDef建立好之后,將GpuNamedConstantsPtr傳入到GpuProgramParameters對象里,然后向其中的兩個vector添加上述對象大小的空間,在將兩個LogicalToPhysicalMap的指針也傳到shaderParams里,這樣整個GpuProgramParameters幾乎就完工了。
2.解析材質里定義的params,并將其與前者對應上
如今就差再把material script定義的param解析出來并與之前shader里的變量進行對應。材質腳本讀出的每個變量的信息包含了param類型(named,auto_named,index,auto_index),name,type(float,int..),autoType(auto的才有),初始值等。我們根據其autoType在全局變量AutoConstantDictionary中查找是否有定義,然后調用set[Named]AutoConstant,在這里我們在會在我們之前從hlsl里解析出的GpuNamedConstantsPtr里查找是否有該名字的變量(畢竟shader里的變量才是最終有用的,甚至由于編譯器優(yōu)化的關系,由于shader編寫者可能定義了一個變量而未實際使用,在編譯shader后,這個變量實際是會被省略的,這樣在GpuNamedConstantsPtr也就找不到了)然后調用_setRawAutoConstant將這個變量加入到這個GpuProgramParameters對象所持有的autoConstant中。
(ps:關于_getFloatConstantLogicalIndexUse的用途我沒看懂,為何需要去在mFloatLogicalToPhysical里去查找這個logicalIndex是否存在呢,我覺得如果走到這一步肯定shader里肯定就是定義了的,因為這個logicalIndex是由mNamedConstants里取出,我覺得是沒必要的。。)
3,更新autoParamsbuffer并最終在DP前進行更新
把上面的過程看懂了,下面就很好理解了。GpuProgramParameters::_updateAutoParams里就是對GpuProgramParameters對象所持有的autoConstant值進行更新,當然這是我們只是對GpuProgramParameters持有的buffer里的值做更新,最終在文章開頭的偽代碼中的setShaderParams(ogre里叫bindGpuProgramParamters)而這里的更新就輕而易舉了,我們遍歷logicalToPhysicalMap對每個param取出對應的logicalIndex,dataPointer,vector4ofCount,然后調用SetVertex/PixelShaderConstantF/I即可。
呼。。終于把這塊寫完了。。上述只是一個實現(xiàn)自己的material的基本思路。關于優(yōu)化的話,我開頭推薦的clayman的文章里有很多敘述。關于怎么寫ogre script translator,等下一篇再說。。