ETH官方钱包

創(chuàng)作內(nèi)容

10 GP

【進度】3D背景—解決顏色問題、實裝影子

作者:Shark│2018-12-06 03:24:25│巴幣:118│人氣:1154
Cyber Sprite 2實裝3D背景第二階段。
之前的3D研究進度在此
【進度】3D背景實裝過程

同時發(fā)在官網(wǎng)

上次做到這樣,有些部位顏色錯了。


下一步是解決反鋸齒計算時,被隔壁像素影響的問題。

由於目前的貼圖擠得滿滿的,沒有地方增加邊緣。


改良的第一步是叫3D美術(shù)把貼圖改小一點。
然後我把合併的貼圖重新排列,每塊之間留一些空隙。


叫鈷寶修改模型檔裡的貼圖檔名和貼圖坐標(biāo)。
鈷寶:……好。(開始工作,修改PMD檔的內(nèi)容)
這裡的鈷寶現(xiàn)實中是個Inkscape外掛,上次就寫好的。

順便做個小實驗,把底色改成粉紅色。


輸出貼圖,然後用PmxEditor看模型。

到處都有粉紅色的線,這就是因為在每塊的邊緣,反鋸齒計算把外面的粉紅色也拿來內(nèi)插造成的。

再來真正對貼圖做處理了,做法如下,把邊緣的像素複製,往外擴張數(shù)個pixel。


至於用什麼方法?可以用GIMP手動複製貼上,也可以用Inkscape的pattern功能截取圖的一部分,但是數(shù)量很多人工做很費事,有必要自動化。有找了一下GIMP裡有沒有現(xiàn)成的濾鏡可以做這件事……,發(fā)現(xiàn)沒有。

還是要靠我的電子妖精了。

檢查並修改像素的值是很底層的操作,雖然是要做輔助工具,但鈷寶不擅長做這個(難以用GIMP或Inkscape的外掛,或是用腳本語言做到),要叫艾莉兒弄(用C++寫個程式)。

艾莉兒,第一步是用WIC讀取PNG檔。
艾莉兒:好,開始!
(做法像這篇寫的:【程式】讀取圖檔的方法-Windows篇)

再來用迴圈檢查每個像素。
艾莉兒:我看看……,先檢查周圍四個像素是不是透明。

艾莉兒:兩個不透明代表角落,三個不透明代表邊,要修改周圍像素,其他情況可以不用管。
(不知道有沒有更快的演算法,不過輔助工具不要求速度,作者自己的電腦跑得動就行了,不調(diào)效能也沒關(guān)係)

弄完了,用WIC存回PNG檔。
艾莉兒:(工作中)……,大功告成!主人請過目。

用PmxEditor看看。


這樣沒有奇怪的顏色了。



3D美術(shù)跟我說希望加上影子,也就是阻擋陰影,目前為止都只有做背光陰影。

有畫圖的人應(yīng)該都知道,陰影有背光產(chǎn)生的和阻擋產(chǎn)生的兩種。


3D繪圖裡,背光陰影可以用頂點法線和光源位置算出來,但是阻擋陰影就沒那麼簡單了,現(xiàn)實中看似平常的現(xiàn)像,在電腦裡模擬要費一番工夫。

我手上一本書有介紹影子的做法,可以照它的方法做而不用自己發(fā)明,一般有兩種方法:shadow map和shadow volume。其中shadow volume需要模型裡有記錄相鄰三角形的資訊,PMD和PMX沒有此資訊,而且用geometry shader才比較容易計算,考慮不想讓硬體要求太高,以及以後可能會寫手機軟體,目前不想用geometry shader。
(雖然畫「【進度】Cyber Sprite外語版 (3)」的插圖時開發(fā)出用shadow volume畫影子的方法,但此法不能用在3D繪圖)

shadow map的做法大致如下:



要修改很多地方。
  • 需要建立另一種framebuffer物件:有Z buffer、Z buffer可被其他shader讀取、無color buffer。
    艾莉兒:好。
  • 3D繪圖流程要改,畫3D scene之前多一個步驟:對特定光源畫出shadow map。
    Direct3D的設(shè)定如sampler、viewport、rasterizer state也要準(zhǔn)備一份畫shadow map專用的。
    艾莉兒:好,再加一些東西。
  • 各種3D物件除了畫出本體的draw()函式,還要多一個只更新Z buffer的drawZPass()函式。
    艾莉兒:慢一點,等我弄好前面的。
    現(xiàn)在只有一種類型的3D物件:靜態(tài)模型,以後如果要做3D遊戲可能還有其他的,每種要各別處理。
  • 增加用來畫shadow map的shader,只要計算頂點位置,不需要貼圖、法線、打光。
    //CPU傳來的頂點資料
    struct modelZPassVsIn
    {
      float3 pos:P;
    };

    //vertex shader傳給pixel shader的資料
    struct modelZPassVsOut{
      float4 pos:SV_POSITION;
    };

    void modelZPassVS(in modelZPassVsIn IN, out modelZPassVsOut OUT){
      float4 viewPos = transform3D(IN.pos, shadowMapMatrix);
      OUT.pos = projection3D(viewPos, shadowMapProjectionCoef);
      //transform3D和projection3D是我自己寫的函式
      //這次的光源是點光源,所以跟透視投影一樣要乘上投影系數(shù)。

    }

    float4 modelZPassPS(modelZPassVsOut IN):SV_Target {
      return float4(0,0,0,0);
    }
    這部分是鈷寶的工,寫好後請她編譯和打包shader。
    鈷寶:……嗯。
  • shader與input layout是成對的,新增shader也要新增對應(yīng)的input layout。
    先拿畫物件本體的D3D11_INPUT_ELEMENT_DESC試試看,如果可以用就不用寫新的。
    艾莉兒:我試試看……,可以用,沒問題。

艾莉兒:主人,一次加那麼多東西跑起來沒問題嗎?

總算可以做個初步測試,以上過程像在安裝零件,只能靠想像完成時的樣子來寫程式,全部裝好了才能起動機器跑跑看。

————————

艾莉兒:報告主人,不能建shadow map的貼圖。

D3D11建立framebuffer或貼圖時要先建立一個Texture2D物件,再用它建立render target view、depth stencil view或shader resource view物件。由於shadow map會被用在depth buffer也會被其他shader讀取,BindFlags要設(shè)成「D3D11_BIND_DEPTH_STENCIL|D3D11_BIND_SHADER_RESOURCE」,但這樣寫建不出貼圖。
D3D11_TEXTURE2D_DESC td;
  //填入其他屬性
  ……
td.BindFlags=D3D11_BIND_DEPTH_STENCIL|D3D11_BIND_SHADER_RESOURCE;
td.Format=DXGI_FORMAT_D32_FLOAT;

ID3D11Texture2D* texture;
device->CreateTexture2D(&td, NULL, &texture);

研究一下,發(fā)現(xiàn)Format必須設(shè)成TYPELESS,之後建depth stencil view和shader resource view時分別指定格式。
通常情況下只要建立Texture2D時指定格式即可,建立view物件時不用再設(shè)定,但建立shadow map時不一樣。
D3D11_TEXTURE2D_DESC td;
  //填入其他屬性
  ……
td.BindFlags=D3D11_BIND_DEPTH_STENCIL|D3D11_BIND_SHADER_RESOURCE;
td.Format = DXGI_FORMAT_R32_TYPELESS;

ID3D11Texture2D* texture;
device->CreateTexture2D(&td, NULL, &texture);

D3D11_DEPTH_STENCIL_VIEW_DESC dsDesc;
ZeroMemory(&dsDesc, sizeof(D3D11_DEPTH_STENCIL_VIEW_DESC));
dsDesc.Format = DXGI_FORMAT_D32_FLOAT;
dsDesc.ViewDimension=D3D11_DSV_DIMENSION_TEXTURE2D;
ID3D11DepthStencilView* dsView;
device->CreateDepthStencilView(texture, &dsDesc, &dsView);

D3D11_SHADER_RESOURCE_VIEW_DESC srDesc;
ZeroMemory(&srDesc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
srDesc.Format = DXGI_FORMAT_R32_FLOAT;
srDesc.ViewDimension=D3D11_SRV_DIMENSION_TEXTURE2D;
srDesc.Texture2D.MipLevels=1;
ID3D11ShaderResourceView* srView;
device->CreateShaderResourceView(texture, &srDesc, &srView);

————————

相關(guān)物件的建立都沒問題了,先做個小實驗,試試看在shader讀取Z buffer,然後顯示出來。
寫一個讀取Z buffer的shader叫鈷寶打包,整合進引擎。
然後叫艾莉兒套用貼圖和shader畫出Z buffer。

場景

Z buffer是這樣

顏色越淺代表距離越遠。
shader裡讀取Z buffer後有用一行「pow(zBuffer, 8);」調(diào)整數(shù)值,因為依照3D繪圖裡Z buffer的設(shè)計,Z buffer大部分區(qū)域都是接近1,如果不用pow()縮小數(shù)值看起來會一片白。

————————

再來要真正實裝了,畫模型的shader裡要讀取shadow map做計算。

shadow map基本觀念不難,但製作時還有一些細節(jié)要注意,有查了一些資料,主要參考這篇。
https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping

如這篇所說直接畫會產(chǎn)生奇怪的紋路(shadow acne),解法是修改cull設(shè)定,Z pass時改成畫back face,畫本體時再改成畫front face。

vertex shader要做兩種坐標(biāo)轉(zhuǎn)換並輸出兩個位置,一個是轉(zhuǎn)換到真正的鏡頭,一個是把光源當(dāng)作鏡頭來轉(zhuǎn)換,後者用來從shadow map讀取像素。
大概像這樣
//IN.pos是頂點坐標(biāo)

//實際的位置

float3 viewPos = transform3D(IN.pos, modelViewMatrix);
OUT.pos = projection3D(viewPos, projectionCoef);
//在shadow map裡的位置
float3 shadowMapPos = transform3D(IN.pos, shadowMapMatrix);
OUT.shadowMapPos = projection3D(shadowMapPos, shadowMapProjectionCoef);
shadowMapMatrix和shadowMapProjectionCoef和上面Z pass shader裡的相同。
把OUT.pos和OUT.shadowMapPos傳給pixel shader做之後的計算。
pixel shader裡還要做點處理才能得到真正的坐標(biāo)。
//要先除以w
float3 shadowMapPos = IN.shadowMapPos.xyz/IN.shadowMapPos.w;
//此時xy=-1~1,轉(zhuǎn)換成0~1且y要反向
shadowMapPos.xy = shadowMapPos.xy*float2(0.5,-0.5) + 0.5;
//讀取shadow map
float outOfShadow = shadowMap1.SampleCmpLevelZero(
  shadowMapSampler, shadowMapPos.xy, shadowMapPos.z);

點光源可以朝四面八方照射但shadow map的範(fàn)圍有限,3D空間裡shadow map配置在哪裡也是要考慮的,要看情況調(diào)整光源的矩陣,儘量讓shadow map涵蓋到可視範(fàn)圍。


本遊戲還算比較好調(diào),模型只用在背景,只會從前方看過去而不會進到場景裡亂跑。

覺得有個地方也要改,之前打光是在鏡頭坐標(biāo)系計算,改成世界坐標(biāo)系似乎比較好,本來shader裡用一個modelView矩陣計算,改成model矩陣和view矩陣分開。

發(fā)現(xiàn)計算鏡頭位置的算式有錯,順便修正。

為了實裝shadow map,畫一個物體要寫三個版本的shader。
1.畫出物體,不考慮影子。
2.Z pass,只更新Z buffer的shader。
3.畫出物體,會計算影子。

引擎裡render物體的部分也要分成這三種情況。

畫影子也是計算量較大的操作,所以預(yù)定做成可以在option開關(guān),玩家的配備太弱的話可以設(shè)成不畫影子。

————————

艾莉兒:framebuffer、sampler、cull設(shè)定、矩陣計算……,還有新增的render流程……,好多啊。
鈷寶:(編譯及打包shader,包括新增的和修改做法的)……。好了,主人。

艾莉兒:主人,我們準(zhǔn)備好了,動手吧!

弄了上面一大串,總算可以看到成果了。
把以前做實驗用過的Patchouli也拿來試試看。


人、椅子、隔板這些物體可以擋住光線,在後面產(chǎn)生影子了。
人的大小我沒有仔細調(diào)整,可能跟場景不太能配合。

Patchouli的模型來自這裡。
https://mikumikudance.fandom.com/wiki/Patchouli_Knowledge_(Zakoneko)
由於還沒做3D骨骼動畫,目前還沒辦法讓她動。

shadow map解析度會有影響,這張圖開1024×1024還是會有shadow acne,要開到2048×2048才行。
相對地另一種畫影子的方法:shadow volume有不受解析度限制的好處。

附帶一提,上圖是用一個自製工具顯示3D畫面。

做個工具比較方便調(diào)參數(shù),可以調(diào)打光、鏡頭位置等等,調(diào)好再寫進程式裡。

還有一個地方有問題,二樓地板沒有產(chǎn)生影子。


這是因為畫shadow map的時候修改cull設(shè)定,改成畫back face,而此處的背後因為遊戲中看不到,沒有做背面。


做遊戲把玩家看不到的地方省略是常用做法,但有畫影子的時候就不能省略背面了,要叫美術(shù)改一下。

shadow map總算做到可以用的程度,有夠難做。
以上是用Direct3D 11寫,Linux版要再寫一份OpenGL的,演算法照抄就行了不是難事。

過程中又忍不住想抱怨,Blender匯入OBJ的功能有問題,匯入PMD的plugin試了好幾個也找不到能用的,所以無法用Blender修改模型檔,想編輯模型只能靠PmxEditor,不然就是派我的電子妖精上場。
別人的輪子有問題的時候也只好自己發(fā)明一個,哪天我會想自己寫個Blender外掛,甚至一個模型編輯器也說不定。
引用網(wǎng)址:http://www.jamesdambrosio.com/TrackBack.php?sn=4217472
All rights reserved. 版權(quán)所有,保留一切權(quán)利

相關(guān)創(chuàng)作

同標(biāo)籤作品搜尋:Cyber Sprite|遊戲製作|程式|3D|Direct3D|DirectX

留言共 5 篇留言

冰音
蠻專業(yè)的(看不懂)

12-06 13:04

Shark
畢竟這是分享我做遊戲的進度,不是Direct3D新手入門。
總之3D繪圖要做影子不是很容易的事。12-06 22:25
=?ω?)σ(?ω?=
辛苦了,你還真有心寫引擎
感覺很累就比較沒在自己開發(fā)引擎了

12-21 10:33

ays.
辛苦了 當(dāng)初也是覺得有l(wèi)earnopengl, 概念上也不難 結(jié)果寫了才知道有一大堆小細節(jié) (像我用openGL, 要用哪個Sampler, 貼圖怎麼設(shè)定, pow 那邊debug 很麻煩等等) 會讓你卡超級久...

另外,好奇的話想詢問一下關(guān)於pmx 模型您的workflow 是怎麼樣? 是直接用lib (assimp?) 讀取還是用blender轉(zhuǎn)成比較通用的格式呢? 謝謝您

01-31 18:30

Shark
引擎做成直接吃PMD格式,因為它的構(gòu)造幾乎就是照D3D和OpenGL函式的需求,讀取後不用什麼轉(zhuǎn)換就可以繪製。其他有些格式還要轉(zhuǎn)換才能給D3D和OpenGL用。

把其他格式轉(zhuǎn)成PMD如第一階段所說,我試了兩種方法:Blender的外掛和PMDEditor都有問題,於是自己寫一個程式轉(zhuǎn)換。

引擎讀取PMD則是完全自己寫。
先把整個檔案讀到記憶體成為一個char陣列,參考這個說明得知哪個byte是什麼東西,再轉(zhuǎn)成引擎自己的資料結(jié)構(gòu)。
https://mikumikudance.fandom.com/wiki/MMD:Polygon_Model_Data01-31 23:43
ays.
了解,我之前是想要用assimp直接讀pmx 不過遇到一些不太知道怎麼解的bug, 既然是這樣我先試試看你的做法(pmx -> pmd -> char array)好了!

非常謝謝您!

02-01 11:51

Shark
PMX的話,其實我在寫這篇之後也有做出讀PMX的功能,參考這篇格式說明。
https://gist.github.com/felixjones/f8a06bd48f9da9a4539f
比PMD複雜很多,轉(zhuǎn)成PMD可能有些資訊會失去,如果想用PMX還是直接照PMX的規(guī)格來做讀取功能比較好。
我目前也只讀取其中一部分資料:Vertex、Surface、Texture、Material,動畫功能還沒做。02-04 16:54
ays.
了解~ 我再試試看好了!

謝謝您[e12]

02-05 15:47

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

10喜歡★shark0r 可決定是否刪除您的留言,請勿發(fā)表違反站規(guī)文字。

前一篇:徵求Cyber Spri... 後一篇:22週年站聚遊記...


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

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