前一篇在此
巴哈姆特官網(wǎng)這篇嘗試一個(gè)做法,不同程式語(yǔ)言用不同底色。
C++的引擎本體:艾莉兒的顏色,粉紅底。
shader:要兩個(gè)電子妖精合作,用黃底
本篇沒出現(xiàn)鈷寶負(fù)責(zé)的輔助工具,以後如果有就是鈷寶的顏色:淺藍(lán)色。
同時(shí)發(fā)在官網(wǎng)Blogspot可以修改表格邊框顏色,所以改用邊框顏色區(qū)分程式語(yǔ)言。
前一篇是Direct3D,這一篇是移植到Linux,要把shadow map用OpenGL再寫一份。
一代Windows和Linux都是用OpenGL,為何二代Windows版要改用D3D11?因?yàn)殡S著二代採(cǎi)用更進(jìn)階的OpenGL功能,發(fā)現(xiàn)Windows版的OpenGL驅(qū)動(dòng)程式通常寫得比較爛,像是效能比較差、同一個(gè)晶片有些功能D3D有但OpenGL沒有,可能OpenGL用的人比較少所以顯卡廠商就沒有認(rèn)真寫驅(qū)動(dòng)程式。而且Windows Phone和Windows on ARM只能使用D3D,以後開發(fā)這些平臺(tái)的程式遲早要碰D3D。
步驟大致如下
- 建立畫shadow map用的framebuffer。
- 新增一個(gè)sampler物件,用來(lái)讀取shadow map。
- 修改計(jì)算矩陣的方式。
- 變更c(diǎn)ull和depth test狀態(tài),D3D11是套用物件一次設(shè)定一組狀態(tài),OpenGL是呼叫函式一項(xiàng)一項(xiàng)設(shè)定。
//設(shè)成只畫背面、開啟depth test glEnable(GL_CULL_FACE); glCullFace(GL_FRONT); glDepthMask(GL_TRUE); glEnable(GL_DEPTH_TEST); |
- 一個(gè)shader要使用兩張貼圖時(shí)要呼叫g(shù)lActiveTexture()變更目前的slot,這是筆者首次在一個(gè)shader裡用兩張貼圖。
glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, shadowMapTexture); glActiveTexture(GL_TEXTURE0); //之後畫模型時(shí)將模型的貼圖放在slot 0 |
- 畫模型要準(zhǔn)備三組shader:不畫影子的、畫shadow map的、用shadow map畫影子的。把以前寫的shader也重整一下。
再來(lái)把DirectX的shader語(yǔ)言:HLSL改寫成OpenGL的shader語(yǔ)言:GLSL。
這一步不難,HLSL和GLSL其實(shí)很多地方可以一對(duì)一對(duì)應(yīng)。
float → float
float4 → vec4
線性內(nèi)插 lerp() → mix()
很多函式都同名,如normalize、dot、clamp。
float4x3 → mat3x4
矩陣的行數(shù)和列數(shù)寫法相反。
mul(float4, float4x3) → vec4*mat3x4
向量與矩陣相乘HLSL必須用mul函式,GLSL可以用乘號(hào)。
兩者差別較大的地方是程式載入shader的方式。
第一點(diǎn)是HLSL沒有限定程式進(jìn)入點(diǎn)的名稱,是在編譯shader時(shí)指定,但是GLSL規(guī)定叫做main(),所以把多個(gè)shader寫在同一個(gè)檔案裡有名稱相衝的問題。
第二點(diǎn)是HLSL是先用SDK附的工具——fxc.exe將程式碼編譯成byte code(中間碼),再把byte code給顯卡驅(qū)動(dòng)程式,遊戲只要附帶byte code。但GLSL要把程式碼附在遊戲裡,執(zhí)行遊戲時(shí)叫驅(qū)動(dòng)程式即時(shí)編譯。
雖然OpenGL後來(lái)也開發(fā)出自己的byte code格式——SPIR-V,但目前還有很多顯卡沒支援。
這點(diǎn)我用自製的輔助工具型電子妖精——鈷寶解決。
鈷寶:好……。(開始包shader)花點(diǎn)工夫做個(gè)工具,把編譯、打包、壓縮、以及遊戲本體載入shader自動(dòng)化,之後就輕鬆了。
————————
到此為止一切都很順利,沒想到又在一個(gè)地方卡關(guān):畫模型+影子時(shí)畫不出任何東西,但切換成不畫影子的模式就正常。
鈷寶:那個(gè)……,shader坐標(biāo)計(jì)算……有錯(cuò)。艾莉兒:sampler物件也沒設(shè)對(duì)。如前一篇所說(shuō),vertex shader要算出頂點(diǎn)在shadow map裡的位置,即shadow map framebuffer的螢?zāi)蛔鴺?biāo),再給pixel/fragment shader使用。
由於兩者對(duì)螢?zāi)蛔鴺?biāo)的定義不一樣,pixel shader裡把螢?zāi)蛔鴺?biāo)轉(zhuǎn)換到貼圖坐標(biāo)的算式不同。
//要先除以w float3 shadowMapPos = IN.shadowMapPos.xyz/IN.shadowMapPos.w; //此時(shí)xy=-1~1,轉(zhuǎn)換成0~1且y要反向 shadowMapPos.xy = shadowMapPos.xy*float2(0.5,-0.5) + 0.5; |
Direct3D經(jīng)過矩陣變換後Z坐標(biāo)是0~1,可以直接跟shadow map裡的數(shù)值比較,只有xy需要-1~1到0~1的轉(zhuǎn)換。
//要先除以w vec3 shadowMapPos = varShadowMapPos.xyz/varShadowMapPos.w; //此時(shí)xyz=-1~1,轉(zhuǎn)換成0~1,跟D3D不同的是y不需要反向 shadowMapPos = shadowMapPos*0.5 + 0.5; |
OpenGL經(jīng)過矩陣變換後Z坐標(biāo)是-1~1,xyz都要轉(zhuǎn)換到0~1的範(fàn)圍,我把這個(gè)算式寫錯(cuò)。
OpenGL的Y不需要反向是因?yàn)槲夜室庾屬N圖倒立,詳細(xì)原因看這篇:
【程式】倒立的OpenGL貼圖坐標(biāo)還有忘了先呼叫g(shù)lUseProgram()再設(shè)定uniform變數(shù)。glGetUniformLocation()有個(gè)programID參數(shù),用這個(gè)參數(shù)指定要操作的program,但是glUniformi()是用glUseProgram()指定要操作的program。
//programID代表OpenGL的program物件 glUseProgram(programID); int location=glGetUniformLocation(programID, "shadowMapSampler"); glUniform1i(location, 1); |
————————
但修正這些錯(cuò)誤後變成模型畫得出來(lái),看不到影子。
艾莉兒:想辦法把GPU內(nèi)部資料秀出來(lái)看看吧。實(shí)驗(yàn)一,把shadow map的貼圖坐標(biāo)顯示出來(lái)。
在fragment shader裡這樣寫。
//Direct3D float3 shadowMapPos=IN.shadowMapPos.xyz/IN.shadowMapPos.w; shadowMapPos.xy = shadowMapPos.xy*float2(0.5,-0.5)+0.5; return float4(shadowMapPos.xyz,1);
//OpenGL vec3 shadowMapPos=varShadowMapPos.xyz/varShadowMapPos.w; shadowMapPos = shadowMapPos*0.5+0.5; gl_FragColor=vec4(shadowMapPos.xyz,1); |
這樣顏色的R分量是X坐標(biāo),G是Y坐標(biāo),B是與光源的距離(非線性)。
(暫時(shí)到Windows開發(fā)機(jī)上工作)
艾莉兒:弄好了,D3D11是這樣。![]()
(再回到Linux開發(fā)機(jī))
艾莉兒:OpenGL是這樣。![]()
看起來(lái)一片藍(lán)+紫看不出什麼東西,但重點(diǎn)是兩張圖一樣,可見坐標(biāo)計(jì)算是對(duì)的。
鈷寶:shadow map呢?實(shí)驗(yàn)二,顯示shadow map本身。
//Direct3D float3 shadowMapPos=IN.shadowMapPos.xyz/IN.shadowMapPos.w; shadowMapPos.xy = shadowMapPos.xy*float2(0.5,-0.5)+0.5; float4 depth = shadowMap1.Sample(defaultSampler, shadowMapPos.xy); return float4(depth.rrr,1);
//OpenGL vec3 shadowMapPos=varShadowMapPos.xyz/varShadowMapPos.w; shadowMapPos = shadowMapPos*0.5+0.5; vec4 depth = texture(defaultSampler2, shadowMapPos.xy); gl_FragColor=vec4(depth.rrr,1); |
(跑到Windows)
艾莉兒:D3D11。![]()
(再到Linux)
艾莉兒:OpenGL。![]()
D3D可以正確顯示shadow map,但OpenGL是一片黑。
艾莉兒:主人等一下,有些要注意的地方有做對(duì)嗎?
艾莉兒:我看有哪些……,Z buffer通常都建立成renderbuffer,但想在shader讀取Z buffer的話,Z buffer要建立成texture。
艾莉兒:shader裡的sampler必須是sampler2D型態(tài),不能是sampler2DShadow型態(tài)。鈷寶:還有……貼圖和sampler物件,bind不能弄錯(cuò)。艾莉兒:還有畫之前有沒有用glClear(GL_DEPTH_BUFFER_BIT)清除Z buffer?再看一下code,這些都有注意到了,為了做這個(gè)實(shí)驗(yàn)在shader裡宣告了第三個(gè)sampler物件,使用普通sampler讀取Z buffer而不是shadow sampler。
還有用glCheckFramebufferStatus()檢查framebuffer狀態(tài),確定只有Z buffer沒有color buffer的framebuffer物件是可以使用的,不會(huì)被系統(tǒng)擋下來(lái)。
另外發(fā)現(xiàn)一件怪事,在Linux看實(shí)驗(yàn)一的圖顏色會(huì)變這樣。
![]()
上圖是GIMP,用Korora預(yù)設(shè)的看圖軟體Gwenview也一樣,但用Firefox開圖檔的話顏色正常,且GIMP的顏色挑選器可以取得正確的顏色。
把GIMP的color management功能關(guān)閉顏色就對(duì)了,可見問題在color management,但是Gwenview要怎麼關(guān)閉color management?查了一下……,好像沒辦法,是寫死在程式裡。
發(fā)現(xiàn)一個(gè)很蠢的錯(cuò)誤,算shadow map的FOV時(shí)忘了把角度換算成弧度,所以投影計(jì)算錯(cuò)了,下面才是正確的shadow map坐標(biāo)和shadow map。
![]()
但是修正角度後OpenGL還是畫不出shadow map。
艾莉兒:主人,應(yīng)該有兩種可能,shadow map根本沒被畫出來(lái),或是shadow map有畫出來(lái)但shader讀不到。鈷寶:找到這個(gè),要不要用? apitrace
https://apitrace.github.io/特別找了一個(gè)D3D和OpenGL的debug工具——apitrace來(lái)用,確定Z pass這一步?jīng)]問題,shadow map有畫出來(lái),問題出在shader讀不到貼圖。
也試過另一個(gè)debug工具——RenderDoc,這個(gè)似乎比較有名,但我用的時(shí)候常常當(dāng)?shù)簦恢荝enderDoc本身的bug、Korora套件庫(kù)收錄的版本有問題、還是要debug的程式要做什麼特別處理。
艾莉兒、鈷寶,換個(gè)方法吧。
解數(shù)學(xué)難題的時(shí)候,常用方法是把問題簡(jiǎn)化看會(huì)得到什麼解,再增加變因一步步還原成原來(lái)的題目。決定先不要用整個(gè)引擎測(cè)試,寫個(gè)簡(jiǎn)單的程式,只做「設(shè)法讓Z buffer有變化→把Z buffer畫到另一個(gè)framebuffer上」,坐標(biāo)是程式直接給-1~1的螢?zāi)蛔鴺?biāo),把坐標(biāo)轉(zhuǎn)換也省了。
————————
總算查出原因,以上過程花了兩天。
//這樣寫會(huì)錯(cuò) glGenSamplers(1, &samplerObj); glSamplerParameteri(samplerObj, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(samplerObj, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); …… glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_2D, textureID);
//解法一 glGenSamplers(1, &samplerObj); glSamplerParameteri(samplerObj, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(samplerObj, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); …… glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_2D, textureID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
//解法二 glGenSamplers(1, &samplerObj); glSamplerParameteri(samplerObj, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(samplerObj, GL_TEXTURE_MIN_FILTER, GL_LINEAR); …… glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_2D, textureID); |
內(nèi)插方式如果設(shè)定成有mipmap的,如上面的GL_LINEAR_MIPMAP_NEAREST,貼圖物件必須把mipmap設(shè)定完整,shader才能正常讀取貼圖。
貼圖的GL_TEXTURE_MAX_LEVEL屬性預(yù)設(shè)值是1000,也就是必須把最大到最小的mipmap都準(zhǔn)備好才能使用貼圖。
解法一是把GL_TEXTURE_MAX_LEVEL設(shè)成0,告訴驅(qū)動(dòng)程式只有原始大小,沒有縮小的mipmap。
設(shè)成1代表有「原始大小+縮小成1/2」共兩層,依此類推。
解法二是把內(nèi)插方式設(shè)成GL_LINEAR,確實(shí)地叫OpenGL不要用mipmap。
這個(gè)錯(cuò)誤難找出來(lái)是因?yàn)橛胓lGetError()取得錯(cuò)誤碼也是傳回0,代表沒有error。
這裡反鋸齒設(shè)定用OpenGL 3.3新增的sampler物件功能,較早的版本是把反鋸齒設(shè)定視為貼圖的屬性,用glTexParameteri()設(shè)定。
——結(jié)論:產(chǎn)生貼圖之後一定要呼叫 glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, level) 設(shè)定mipmap層數(shù),即使沒使用mipmap也一樣。
艾莉兒:快看,總算畫出來(lái)了。跟前一篇不是同一個(gè)程式故看起來(lái)有些不同。
![]()
離鏡頭較近的部分投射不出影子(如Patchouli的腳附近),因?yàn)楣庠次恢迷O(shè)得很近,讓有些部分在shadow map外面,不過總之OpenGL的shadow map可以用了。
決定再做一個(gè)東西:參考這篇用cubemap實(shí)做shadow map。
https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows其實(shí)目前只能在測(cè)試程式看到影子,還沒辦法在遊戲裡正確畫出影子。
前一篇進(jìn)度文提過,使用shadow map要注意shadow map在3D空間裡的位置,沒被shadow map涵蓋的物體就畫不出影子。本遊戲是點(diǎn)光源且因?yàn)楣庠次恢玫年P(guān)係,照射角度有點(diǎn)大,要建立好幾個(gè)shadow map才能涵蓋所有可視範(fàn)圍(如下面俯視圖),這樣C++建立物件,還有shader裡讀取shadow map很麻煩。
![]()
光源是點(diǎn)光源的時(shí)候,用cubemap實(shí)作shadow map是最不用煩惱位置的解法,可以涵蓋全方向,雖然Cyber Sprite 2光只朝一個(gè)方向照而不會(huì)用到四面八方,但現(xiàn)在做好的話以後做其他3D遊戲也用得著。
介紹一下cubemap的概念:這是一種特別格式的貼圖,正如其名有六個(gè)面,假想有個(gè)正方體以原點(diǎn)為中心,六個(gè)面分別是六張貼圖,可能來(lái)自圖檔或即時(shí)繪製產(chǎn)生。
![]()
使用左手坐標(biāo)系,上圖的-X、-Y、+Z面在背後。
讀取平面貼圖時(shí),貼圖坐標(biāo)要給兩個(gè)軸決定讀取哪個(gè)位置的像素,數(shù)值在0和1之間。
讀取cubemap則要給三個(gè)軸,數(shù)值範(fàn)圍不限,把貼圖坐標(biāo)視為3D空間裡的點(diǎn),將此點(diǎn)與原點(diǎn)連接會(huì)與正方體有一個(gè)交點(diǎn),讀取正方體表面此位置的像素。
它的應(yīng)用之一是環(huán)境貼圖(站在中央,往四面八方看到的景色),點(diǎn)光源是從中央往四周照射,也可以用cubemap記錄點(diǎn)與光源的距離。
————————
這還是我第一次用cubemap,而且是即時(shí)繪製,除了貼圖物件以外還要建立framebuffer物件。
保持冷靜,一步一步解題目,先看使用cubemap要準(zhǔn)備哪些東西。
艾莉兒:我查一下……,產(chǎn)生貼圖的時(shí)候ArraySize和MiscFlags屬性要特別設(shè)。
其他屬性跟平面貼圖一樣,就寬高和格式之類的。
D3D11_TEXTURE2D_DESC td; td.ArraySize = 6; td.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE; td.Format = DXGI_FORMAT_R16_TYPELESS; …… //之後用CreateTexture2D()建立ID3D11Texture2D物件 |
此外因?yàn)橐裠epth buffer當(dāng)成貼圖使用,F(xiàn)ormat必須設(shè)成TYPELESS而不是UNORM或FLOAT。
有測(cè)試過,本遊戲的3D場(chǎng)景不是很大,16 bit的depth buffer就夠用,不需要用到24或32 bit。
艾莉兒:然後,shader resource物件建一個(gè)就好了,但是depth stencil物件要建六個(gè)。
//要建立這些物件 ID3D11ShaderResourceView* srView; ID3D11DepthStencilView* dsView[6]; ID3D11DepthStencilView** dsViewPtr=dsView;
//texture是已經(jīng)建好的ID3D11Texture2D物件
D3D11_SHADER_RESOURCE_VIEW_DESC srDesc; ZeroMemory(&srDesc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC)); srDesc.Format=DXGI_FORMAT_R16_UNORM; srDesc.ViewDimension=D3D11_SRV_DIMENSION_TEXTURECUBE; srDesc.TextureCube.MipLevels=1; device->CreateShaderResourceView(texture, &srDesc, &srView);
D3D11_DEPTH_STENCIL_VIEW_DESC dsDesc; ZeroMemory(&dsDesc, sizeof(D3D11_DEPTH_STENCIL_VIEW_DESC)); dsDesc.Format=DXGI_FORMAT_D16_UNORM; dsDesc.ViewDimension=D3D11_DSV_DIMENSION_TEXTURE2DARRAY; dsDesc.Texture2DArray.ArraySize=1; //用迴圈建立六個(gè)depth stencil view for(int i=0;i<6;i++){ dsDesc.Texture2DArray.FirstArraySlice=i; device->CreateDepthStencilView(texture, &dsDesc, dsViewPtr); dsViewPtr++; } |
艾莉兒:再來(lái),更新cubemap的時(shí)候要把場(chǎng)景畫六次,一次畫一個(gè)面。
分別算六次旋轉(zhuǎn)矩陣,把鏡頭面對(duì)+X、-X、+Y……這六個(gè)方向,套用六個(gè)depth stencil view。上面那篇教學(xué)使用geometry shader,畫一次場(chǎng)景就可以更新六個(gè)面,但本遊戲不使用geometry shader,要跑迴圈畫六次。
float rotMatix[9]; //暫存的旋轉(zhuǎn)矩陣
for(int i=0;i<6;i++){ //套用depth stencil view context->OMSetRenderTargets(0, NULL, dsView[i]); context->ClearDepthStencilView(dsView[i], 0.0, DEPTH_CLEAR_VALUE,0);
//計(jì)算旋轉(zhuǎn)矩陣,這一步還未完成 oneCubeFace(rotMatix, this->rotTr, FACE_DIR[i],FACE_TOP_DIR[i]);
//用uniform buffer把shadow map位置傳給shader PerSceneUniform* data=(PerSceneUniform*)mapUniformBuffer( sppl->perSceneUniform, sizeof(SP3DCamera)); this->calcViewProj(rotMatix, &data->camera); unmapUniformBuffer(sppl->perSceneUniform); //畫出物件 ListIter iter; for(iter=objList->begin();iter!=objList->end();iter=iter->next()){ SP3DObjBase* obj=(SP3DObjBase*)iter->data; if(obj->flags & _3DOBJ_VISIBLE){ obj->draw(SP3DObjBase::DRAWTYPE_ZPASS); } } } |
這裡有些是自訂的函式和常數(shù),不是D3D11定義的。
本遊戲的場(chǎng)景配置並不需要六個(gè)面都畫,或許可以用個(gè)flag設(shè)定哪些面不用畫,省略一些計(jì)算。
計(jì)算旋轉(zhuǎn)矩陣嘛,雖然之前已經(jīng)把3D的公式都研究出來(lái)但還是有點(diǎn)麻煩,先只畫一面看看(把for迴圈暫時(shí)拿掉)。
艾莉兒:更新好cubemap後,用cubemap畫圖的時(shí)候跟平面貼圖一樣,呼叫context->PSSetShaderResources()。這個(gè)比較容易。
context->PSSetShaderResources(2,1, &srView);
|
第一參數(shù)是shader裡的slot,要跟shader裡旳宣告對(duì)應(yīng),0與1已經(jīng)用在模型本身的貼圖和平面shadow map,所以這裡用2。
再來(lái)修改shader,鈷寶,看一下要準(zhǔn)備什麼。
鈷寶:嗯……,要宣告一個(gè)TextureCube變數(shù)。好,在宣告貼圖的地方加一行。
Texture2D<float4> texture1:register(t0); //一般貼圖 Texture2D<float> shadowMap1:register(t1); //平面shadow map TextureCube<float> cubeShadowMap1:register(t2); //cube shadow map
|
register(t2)就是對(duì)應(yīng)到PSSetShaderResources()裡旳2。
鈷寶:sampler……,不用加新的。
鈷寶:讀取貼圖嘛……,只要貼圖是TextureCube型態(tài),會(huì)自動(dòng)判斷。上面說(shuō)過cubemap貼圖坐標(biāo)要給三個(gè)軸,讀取貼圖的函式「texture物件.SampleCmpLevelZero()」,只要貼圖是TextureCube型態(tài),就會(huì)使用貼圖坐標(biāo)是三維向量的版本,平面貼圖則是貼圖坐標(biāo)是二維。
不過Z buffer裡儲(chǔ)存的距離,cubemap和平面貼圖計(jì)算法不一樣,大概是這樣寫吧。
vertex shader
//float3 worldPos為頂點(diǎn)在世界坐標(biāo)系的位置 OUT.shadowMapPos.xyz=mul(float4(worldPos,1), shadowMap.viewMatrix); float3 absValue=abs(OUT.shadowMapPos.xyz); float maxComponent = max(absValue.x, max(absValue.y, absValue.z)); OUT.shadowMapPos.w = maxComponent*shadowMap.projCoef.z+ shadowMap.projCoef.w;
//shadowMapPos.xyz=鏡頭坐標(biāo)系,w=要跟shadow map比較的值 |
pixel shader
//shadowMapPos是vertex shader算出來(lái),像素在shadow map裡的位置
//對(duì)照:讀取平面shadow map的方法 float3 shadowMapPos=shadowMapPos.xyz/shadowMapPos.w; shadowMapPos.xy= shadowMapPos.xy*float2(0.5,-0.5) + 0.5; //SampleCmpLevelZero參數(shù)為(sampler, 貼圖坐標(biāo), 要跟shadow map比較的值) float notInShadow=shadowMap1.SampleCmpLevelZero( shadowMapSampler, shadowMapPos.xy, shadowMapPos.z);
//cubemap要這樣寫 float3 absValue=abs(shadowMapPos.xyz); float maxComponent = max(absValue.x, max(absValue.y, absValue.z)); float notInShadow=cubeShadowMap1.SampleCmpLevelZero( shadowMapSampler, shadowMapPos.xyz, shadowMapPos.w/maxComponent);
|
平面shadow map要把xyz除以w,但cube shadow map要把w除以xyz的最大值。
寫了一大堆而且沒有中途測(cè)試部分功能,也不知道這樣寫對(duì)不對(duì)。
好吧,艾莉兒、鈷寶,先試試看吧。
結(jié)果,只有二樓地板,還有天花板的一小部分有影子。
艾莉兒:果然沒那麼簡(jiǎn)單……。不過物件都有成功建立,表示產(chǎn)生物件的參數(shù)有填對(duì)。
做到這裡覺得還是先研究一下cubemap的性質(zhì)比較好,不然一直是霧裡摸索,像是cubemap坐標(biāo)、2D貼圖坐標(biāo)、render target坐標(biāo)這三者如何對(duì)應(yīng),找了幾篇說(shuō)明都沒有寫得很清楚的。
(待續(xù),寫到這裡已經(jīng)很長(zhǎng)了,分成另一篇)