最近工作在研究如何在大型世界的植被(主要是草)的渲染,主要考慮下面幾個問題:
1.半自動化procedual生成,密度分布,畫刷
2.受環境的的影響(風,角色等)
3.LOD和動態加載釋放
由於睡覺后突發奇想,想到了一些idea,趁着沒有忘記先記下來:
基本思路:billboard+instancing+分區域管理
1.自動生成一般根據地形的高度/坡度來選擇和過濾,加上一些隨機化方法
密度分布可以:每1x1 (1平方)用一個8位來保存密度,2048x2048的世界,最大需要4M數據。可以壓縮存儲。如果支持不同的類型,可以用4444,支持四種,最大8M數據。2048x2048這么大的地圖,只有四種顯然不夠,可以分區域,每個區域有不同的palette。在分區域的情況下,很多區域是沒有草的,不需要這些數據,那么預計runtime數據在2M~4M之間。 使用密度分布是為了減少保存的數據(數據包大小),否則每個草都保存位置,即便用uint8nx4數據量是原來的4N倍。
過程化生成的可能不是最好,需要美術手動修,用畫刷。畫刷實現不難,略。
2.環境影響:目前的目標平台,不考慮geometry shader,可以在vertexshader里面做頂點動畫。草受環境的影響,unity asset store里有一個package,是用垂直向上的相機渲染displacement normal,來實現區域風(風扇類的,或技能特效),以及和角色的互交
3.動態加載和釋放:和問題1一樣,需要分區域。指定可視范圍,然后加載一定的區塊。用加載隊列和釋放隊列管理這些塊,每個塊可能在兩個隊列里切換, 更新很快。
數量LOD:前兩天一直沒想清楚怎么在LOD切換時,高校合並批次(instancing的buffer), 因為如果沒有LOD的話,就不需更改數量,動態切換渲染的物體(草)
實際上可以這么做:
instancing用vertex buffer保存位置,假設有兩級LOD,LOD0畫10顆草,LOD1畫5顆。 那么,instancing buffer里前5個元素,是LOD1的五顆,位置均勻隨機分布,后5個元素,是LOD0時,與LOD1相差的5顆,位置也要均勻隨機分布,且不跟LOD1的重合。
也就是說,低LOD的草,也包含在高LOD級別里面。所以低LOD的buffer,包含在高LOD的buffer里。
這樣一來,只要把低LOD的instancing buffer放在最前,按LOD級別依次往后放, 就不需要動態合並批次。用同一個buffer,只不過draw call時的vertex count/instance count不一樣,幾乎沒有runtime update開銷,支持任意LOD級別。
最大的開銷在加載后的處理,根據密度生成instancing position。比如16mx16m的塊,按8位密度值,實際密度比例1:1無縮放,最大密度16x16x256,64k顆草,需要隨機化位置, 可能比較耗時,不過可以在IO以后異步處理。
instancing position 的頂點格式可以用int8nX4,因為每個塊的位置是共享的,只需要生成塊內偏移,加上可見塊數量不多,所以顯存耗費不大,按上面的數據,單塊最大極限64k x 4字節,實際可能要小很多。
后面有時間准備實現一下(目前預研,全是空想,還沒寫一行代碼 囧rz)
可視剔除是按區域做,只要區域可見,就畫全部的草,為了避免浪費overdraw,區塊的大小可以更小。
看了一下WiiU塞爾達荒野之息的草,方式如下:
1.和場景人物互交,用的是上面提到的一樣的方法,渲染normal作為placment。
2.一個塊大概1x1或2x2,但沒有instancing,而是預先建立好的mesh,這樣的mesh每個葉子的屬性都可以由美術建模是確立
3.大部分草葉沒有用alphatest, 其他的帶形狀的有,但看起來PS里沒有任何采樣,是在VS里采樣(VTF)后穿給PS做test和discard
4.草的高度也是在vertex shader里采樣得到的,大部分紋理采樣都在vertex shader里
更新 2017.08.19 今天想到的問題點都備忘一下,省得后面實現的時候忘記:
首先看了一下在Unity下如何實現:
Unity的terrain自帶了detail物體,然而不支持自定義shader,所以只能pass。如果支持自定義shader,那么需求折衷一下或許可以更簡單的利用terrain的detail來實現
這里有幾個問題參考: 基本都是要做定義shader的,比如tesselation, toon shading:
http://answers.unity3d.com/questions/414458/tessellation-shaders-on-terrain-detail-object.html
http://answers.unity3d.com/questions/583378/unity-terrain-detail-mesh-toon-shading.html
http://answers.unity3d.com/questions/1173078/is-it-possible-to-change-shader-of-terrain-details.html
答案里的方法沒有試過。因為即便可以,也沒有官方文檔說明和支持,可能會坑。。
不使用terrain detail的實現:
1.如果用Unity的static batch自動合並,需要GameObject,對於大量的草來說,就要大量的gameobject。這種方式太浪費。所以最好用DrawMeshInstanced直接畫。
2.用DrawMeshInstanced可以,但是每次傳入的矩陣數組/列表,估計每次draw call在native端都要創建/更新vertex buffer,至少需要一次memcpy,如果是PC平台,還需要system memory走PCI到video memory。
理論上只需要創建一次vertex buffer,后面只更新instance count就可以。但是unity沒有提供這樣的功能,而且vertex format不能自定義,前面分析的某些優化無法實現。好在DrawMeshInstancedIndirect是類似的方式,創建好buffer后面就不用修改,看了一下這個方法metal/GLES3是支持的。可以考慮試一下效果。
---更新:unity使用了constant/uniform buffer來設置instance matrices,而沒有用instainced頂點數據。所以每次不同的instanced draw call都會綁定uniform。
3.不管是DrawMeshInstanced 還是DrawMeshInstancedIndirect, Unity文檔都說了不支持可見性剔除(culling),Unity提供的方案是在DrawMeshInstancedIndirect里用GPU剔除。。個人認為目前最好的方法是自己管理。用四叉樹是很簡單也很快的(unity的地形detail應該直接用了terrain的quadtree,然而前面分析了不可用)。可能存在的問題是還是比較浪費,比如2048x2048的地形,如果按最小cell為8x8,那么quad tree的葉子節點就有256x256=64K個。如果是超大地形范圍,由多個2048的terrain拼接,那么更加浪費。
可以考慮的一種方式是:只創建可見范圍的quadtree,把他作為view/window,隨着角色的移動把內容填充給quad tree。看起來不好實現,因為要實時更新每個quad tree節點,事實上因為草塊的規則分布,可以用(二維)數組來做windowing,quadtree里只保存索引。這樣只需要更新數組的內容就可以,只要loading的范圍比可見范圍稍大,double buffer+async填充數組應該沒有問題。這種方式quadtree跟streaming和terrain解耦,不需要每個地形一個quadtree,只需要在填充的時候讀取對應terrain的草信息就可以。 ---更新:因為quadtree的包圍盒也更新,所以需要兩份quadtree,這比起大范圍全部創建還是小得可以忽略。 ---更新2:這個agressive optimization放在最后再考慮要不要做 ---更新2:現在在運行時更新quadtree,每次遞歸只需要兩次比較就能定位到子節點,所以效率不是問題。
4.關於密度的問題,如果密度值的粒度太大,比如16x16,那么畫刷的精度太小,一次只能刷16x16的范圍,所以粒度要盡量小。4x4或許可以接受,再小的話,占用的(package)資源會變大
目前第1點和第3點的部分已經實現了。后面會根據分析做進一步的改進。第2點相對比較簡單,可以方后面。下面主要完成第3點和第4點。等整個系統差不多了,在完善shading。
如果在Blade上實現(目前只是分析):
1.使用完整quadtree沒有問題。因為Blade的空間管理(space subdivision/management)已經是跟streaming和地形解耦的,可以管理(主要負責culling和space querying)地形,和模型,等等。因為利用率的提高,加上草也沒有關系。從設計上來講,streaming不應該和quadtree/octree耦合,同樣,把quadtree內置到地形里面,雖然是最常見的實現方法,但是個人認為利用率不高。
2.instancing用的vertex buffer,可以像前面分析的那樣,用最優的vertex format,並且不用動態更新instancing buffer。
備忘LOD矩陣的生成方式:
所有LOD共用同一個矩陣數組,每一級LOD的矩陣都包含前面的內容:
LOD3 繪制前8個實例, LOD2繪制前8+15個,LOD1繪制前8+15+30個,LOD0繪制全部
|
LOD3 |
LOD2 |
LOD1 |
LOD0 |
矩陣數組 |
8 |
15 |
30 |
60 |
目前在Unity上的運行結果:
PC上耗時0.3~0.5ms,即使可見距離為128性能也沒有太大損失。
在IPHONE7S上測試,20米的可見距離,耗時5ms (60FPS=>45FPS)。為了避免LOD數量跳變導致的不平滑,本來每級LOD數量減半,改為每級減少1/4。現在可能要改回去了。 需要繼續優化效率,同時調整距離和密度分布。對於更遠處的草,可能要像塞爾達一樣用換一種方式繪制了
細節更新: 2017/09/14
關於草的模型,目前還沒有優化,有1000個面,后期會做優化。
模型max在導入到unity會有node旋轉,可能還會有縮放。考慮直接使用mesh,不適用導入的gameobject的矩陣,這樣就只需要一個位置float4。
但是因為Unity的接口是matrix4x4,所以會有浪費和效率損失,不過多出來的3個float4后期會做切割和燃燒等per Instance效果。
遇到一個坑是,unity的matrix是column major,shader里面想直接取行相加,即 + matrix[3], 那么就要在script設置matrix的row 3, SetRow(3, xxx), 但是發現只有原點附近的可見時,草才可見。懷疑是Unity在繪制前拿instance matrices的位置做了簡單的culling。但是Unity文檔里說,DrawMeshInstanced沒有culling,WTF? https://docs.unity3d.com/2017.2/Documentation/ScriptReference/Graphics.DrawMeshInstanced.html
Use this function in situations where you want to draw the same mesh for a particular amount of times using an instanced shader. Meshes are not further culled by the view frustum or baked occluders, nor sorted for transparency or z efficiency.
目前只能SetColumn(3, xxx) 同時SetRow(3, xxx), shader里面只用第3行 ( [0,3] )就可以, matrix multiply 變為加法,減少3個指令。。
2017/09/15
因為instance matrix只用了偏移,這時候里面可以填其他數據,而不占用額外的per instance uniform array。比如草的割斷,matrix[0][0]存放一個標記,vertex shader里用分支判斷,修改頂點位置就可以了。。
整體效果和切割效果還有點丑,后面慢慢改進。。。
更新:
四叉樹的內存問題: 使用按需動態創建的方式,只跟草的可視范圍相關。 因為四叉樹結點頻繁創建/銷毀,需要使用object pool避免頻繁的gc alloc,本身草的處理也是這樣。
instance matrix里的位置坐標問題: 發現所有unity shader匯編都用的column major,所以pos + matrix[3], 反而是需要transpose的(多了三條mov指令),因此用列存儲就可以
草的邊緣過渡:在有草和沒有草的地方,顯得非常突兀。根據臨接塊的密度,計算一個漸變值,在shader里面做linear scale。 linear scale的參考中心,未必在塊的中央,有可能在邊緣,是根據鄰接塊的密度差來算的。
這是邊緣的過渡效果
明暗效果是在instance matrix里存一個隨機值,但是那樣只能一個instance一種明暗,所以在頂點色里也存儲了一個隨機值,兩個隨機值疊加得到最終的亮度
另外,草的根部顏色會變暗,這個可以根據模型空間頂點的高度計算
更新:
草的高光
邊緣高光希望實現類似塞爾達的效果。同時邊緣高光跟是視角和光照方向相關,比如光從左邊照時邊緣的高光在葉片左邊,相反時則在右邊;
左邊邊緣
右邊邊緣
一開始想到的方法就是用頂點切線,並不是法線貼圖的TBN切線,而是手刷或者腳本刷的邊緣朝外的切線, 當然也可以用TBN的切線,但是因為要根據UV算,所以UV要布成水平/垂直的網格。
但是目前的模型根本沒有貼圖,就是純計算的顏色,所以使用的方法是刷了邊緣的方向信息,可以理解為邊緣的mask。
而上面的圖實際上是計算了普通的高光,然后再用頂點色作為mask在pixel shader里mask掉。備忘note:為了效率,所有的光照都在vertex里算,類似vertex lit,但這個mask因為需要漸變所以只能插值后在pixel shader里用
計算mask的模型的頂點色,占一個通道,可以刷在頂點的alpha通道里。可以用maxscript來刷,左邊刷0, 右邊刷1。或者左邊刷1,右邊刷0這樣子 ,左、右以正面(法線)為參考
普通高光
只加了葉片的邊緣高光以后,效果很丑,很散,沒有成片成范圍的高光效果。所以普通的高光也要加上。所以最終結果是mask后的邊緣高光 和另一個高光混合(lerp)。兩個高光的gloss和強度可以分開調整。
遺留問題
目前兩個方向的邊緣高光是跳變(瞬間切換)的,雖然不仔細看是注意不到的。。后面會考慮修正
壓倒效果
做法和塞爾達類似,用一個proxy mesh渲染法線然后用VTF采樣偏移頂點。這種方式的好處是各種效果都可以統一做,比如塞爾達里武器揮動對草的影響,或者那個芭蕉扇扇風的效果。
為了優化,rendertarget的格式為5551 (unity的ARGB1555),大小為256x256,有效范圍為50米,這樣以來帶寬的開銷就比較低了,法線的精度也可以接受。 還可以考慮繼續降低分辨率。
因為渲染的是world space normal (0~1),所以clear color 設置為(0.5,1,0.5)即對應的vector為(0,1,0)。需要注意的問題是,直接設置為這個值是不對的,結果錯了。。unity會把他當作sRGB空間的顏色值,正確的做法是:
cam.backgroundColor = (new Color(0.5f, 1, 0.5f, 1)).gamma; //convert vector to color (linear to sRGB)
然后渲染法線時就可以直接把世界空間法線從[-1,1]映射到[0,1]寫入rendertarget。 這個方式和dx11 geometry shader的demo (https://www.assetstore.unity3d.com/en/#!/content/36335)略微不同,但原理類似。
暫時沒有其他的問題,結果如下
對了,vertex里面采樣法線的時候,模型上每片葉子上的所有頂點需要采樣同一個位置,否則葉片上的每個頂點的偏移程度不一樣,葉子會被拉伸變形。
目前使用的是每片葉子的根部來采樣,根部的坐標刷在頂點色里,只需要xz,可以認為固定值(0),因為草葉的縮放/切割已經用到了(根部相當於每片葉子的pivot,可以實現每片葉子的單獨縮放而不是整體模型的縮放),所以這里直接用。
根部坐標的頂點色在max腳本里刷,需要每個葉片是一個element,這樣按每個element取到最低的頂點為根部。
后面可能加上腳印壓痕的殘留效果。
更新
ARGB1555格式的兼容性並不好,現在改為ARGB32
即便在win10 + dx11的系統上,發現GTX1080顯卡支持該格式(驅動版本388.13,D3D feature level 12_1),另外一台GTX1080TI (驅動版本388.00, d3d feature level 11_1) 的機器竟然不支持。。。可能是驅動太舊了。。。
報錯如下“RenderTexture.Create failed: format unspported 6”
這里有關於1555格式的兼容性文檔: https://msdn.microsoft.com/en-us/library/windows/desktop/ff471325(v=vs.85).aspx
DXGI_FORMAT_B5G5R5A1_UNORM
Requires DXGI 1.2 or later. DXGI 1.2 types are only supported on systems with Direct3D 11.1 or later.
這個格式以前在GLES3.0的手機(高通adreno330)上測試過,作為普通texture(GPU read)沒遇到問題,不過沒有測試過作為rendertarget的情況。
RGB565 也可以考慮,但是alpha通道可能會用來做強度的時間漸變,為了兼容性還是暫時選擇ARGB32,優化的話后面再考慮
細節更新:
草壓倒的buffer用的是法線,草的vertex shader里采樣(VTF)采樣法線並做偏移。對應的mesh是這樣(從上面unity dx11 grass的package里面拿的,塞爾達也是類似的方式)
可以看出越靠近中心點,發現越往外偏,那么草的倒幅就越強。邊緣的法線基本朝上,草就不怎么倒了。
buffer的preview如下:
改進: 倒下的草為輻射狀,太整齊和規律,希望能夠隨機一點
想到的方法有兩種:
1. 給上面的mesh加上normal map
2.使用noise texture,給xz方向加上隨機編譯
目前選用的方法2。因為方法1需要美術給mesh要加uv和法線貼圖,使制作流程更加復雜。更重要的的隨機性是固定的,不管角色走到哪里都一樣。 而方法2沒有這樣的問題,比如目前的noisetexture 是32x32,直接在腳本里生成,shader里面tiling的距離是4米,也就是說在4m內走動,擾動的方向都不一樣。
加了擾動(-90~90的隨機旋轉)后的buffer preview如下:
改進:草倒下后起來的延遲(腳步痕跡)
unity那個dx11 grass demo里面是手動創建的軌跡mesh,跟隨移動物體的腳步,隨着物體移動而補充三角形,同時根據時間刪除掉超時的三角形。用類似的方法把法線追加到buffer里。
我認為這個方法不是很好,因為它需要動態修改vertex buffer。 而且這個方法和移動物體的數量相關。物體越多,效率越低。而且demo里刪除三角形時是立即刪除,並沒有過渡,看起來被壓倒的草,過一會兒突然恢復原狀了
想到的方法是把normal buffer做blend,blend權重隨着時間變小,草的倒伏就弱。把這個權重寫入normal buffer的alpha 通道就可以控制。
遇到的問題
1.一開始嘗試直接使用alpha blend,但發現直接用alpha blend 是不行的(雖然所有的結果都可通過blend op和blend factor來完美控制),但是。。。渲染normal buffer的相機也在移動,上一幀的結果無法直接用alpha blend到這一幀。需要兩個buffer,一個保存上一幀的結果,在繪制移動物體物體的normal之前,先用reprojection,將當前幀每個像素對應的位置,reproject到上一幀的對的位置,采樣上一幀后寫入到當前幀里。(類似TAA的history buffer及其采樣), 然后再把當前幀里物體的法線的draw 上去
reprojection shader大致如下
const half4 normalUp = half4(0, 0, 1, 0) * 0.5 + 0.5;
float4 prevPos = mul(_GrassDisplacementFadeReprojection, i.clipPos);
//prevPos /= prevPos.w; //orthographic projection doesn't need persepctive divide
#if UNITY_UV_STARTS_AT_TOP
prevPos.y *= -1;
#endifhalf2 uv = prevPos.xy;
uv = uv * 0.5 + 0.5;half4 n = tex2D(_MainTex, uv.xy); //sample previous frame
//blend / fade
half3 blendNormal = lerp(normalUp, n.rgb, n.a);return half4(blendNormal, _oneMinusFadeSpeed * n.a);
其中_oneMinusFadeSpeed 是一個小於1的tweak值,所以隨着每幀的迭代,alpha會越來越小並趨近於0,也就是草倒下的幅度越來越小
2.上面的方法發現效果並不明顯,因為每兩幀里物體移動的偏移量很小,當前幀的內容覆蓋掉了上一陣的絕大部分內容。雖然上一幀的內容有一絲殘留,但是殘留的法線都是mesh的邊緣,注意“邊緣的法線基本朝上”,所以上一幀殘留下來的法線強度太太弱,肉眼看不出什么效果。
兩個圓為兩幀之間的位置,B部分為重疊部分,但是因為當前幀的法線后畫上去,所以覆蓋掉了上一幀的內容。紅色的A部分為上一幀的殘留(位置在邊緣,偏移強度很低),所以在角色移動時,拖尾的部分一直都是上一幀的邊緣部分,都是很弱的,看不到效果。
目前的解決方法:
將重疊的B部分做blend合並,blend 方法為max。上圖中重疊的黃色部分,取兩幀之間偏移強度最大的值。這么做相當於把當前幀里物體邊緣較弱的法線偏移給去掉了,而采用上一幀較強的偏移。
然而仍然不能用alpha blend 和對應的max op,因為存儲的向量,可以為負,所以max比較的是絕對值。
具體方法:再追加一個buffer, 把reprojection 和 當前幀用到的兩個buffer分開,在shader里合並。 代碼大致如下: (x,y,z對應世界空間法線的xzy)
half4 n0 = tex2D(_MainTex, uv.xy);
half4 n1 = tex2D(_LastTex, uv.xy);half fade = max(n0.a, n1.a);
n0 = n0 * 2 - 1;
n1 = n1 * 2 - 1;half3 n;
if (abs(n0.x) >= abs(n1.x))
n.x = n0.x;
else
n.x = n1.x;if (abs(n0.y) >= abs(n1.y))
n.y = n0.y;
else
n.y = n1.y;if (abs(n0.z) <= abs(n1.z))
n.z = n0.z;
else
n.z = n1.z;return half4(n * 0.5 + 0.5, fade);
最終效果已經達到預期,被壓下去的草在角色離開后開始慢慢恢復原狀,速度可調控。目前感覺不是最高效的方法,后面會考慮改進。
更新: 三個軸獨立的max結果是不對的,會出現類似(n0.x,n1.y,n0.z)的向量。直接使用水平面上投影長度(的平方)比較就可以,另外比較時還要乘上現在的強度(alpha)
half4 n0 = tex2D(_MainTex, uv.xy);
half4 n1 = tex2D(_LastTex, uv.xy);half fade = max(n0.a, n1.a);
half2 n02 = (n0 * 2 - 1) * n0.a;
half2 n12 = (n1 * 2 - 1) * n1.a;half3 n;
if (dot(n02, n02) >= dot(n12.xy, n12.xy))
n = n0;
else
n = n1;return half4(n, fade);
更新:上面的方式草的回彈效果不好,太快。調節速度以后,拖尾又覺得太長太慢,原因是max()方式會導致歷史腳印的中心(強度最大)只有脫離了當前mesh范圍才開始漸變,而實際上mesh本身就有曲線,max方法會把mesh自身的漸變去掉。 目前的方法是不使用max方法,而把模型最邊緣的沒有偏移(法線朝上)的部分去掉。這樣可以得到需要的效果。
高度問題:
上面的方法沒有加入高度,也就是說,即便角色在空中,地面的草也會倒。解決方法是用角色位置向下做raycast,得到的h,除以草的高度H,即strength = clamp(h/H, 0, 1)作為強度調節系數,為1時強度最弱,0時強度最強。當然這種效果是對草的直接影響,反應過快,不像上面可以有延遲效果,不過可以對strength這個參數做一些hack,使他看上去好像有延遲。
主相機對草的碾壓:
因為相機貼地時,視線會被草擋住,所以為了更好的效果,可以給相機加一個displacment mesh:
(突起部位靠近相機)
從這個模型的法線可以看到,離相機近的地方草被撥開,然后隨着距離變大,強度慢慢減弱。頂視圖上看撥動草的范圍是一個尖三角形
更新:相機用的displacement mesh改用其他模型了,但是大致原理如此。角色的模型也改回去了,因為有其他問題,但是改進一下算法就可以了。
Fresnel
給草的高光加上fresnel (類似rim light)之后,平視的時候更有層次感(只要地形稍微有點起伏,就能看到層次差別),不過像塞爾達那樣,只有草的尖部是亮的。另外,為了使相機稍微往下看的時候有也有效果,可以把向量稍微偏一下。
草的擺動
擺動可以參考這個:GPU Gems3 Chapter 16. Vegetation Procedural Animation and Shading in Crysis
如果查看Unity的builtin shader,TerrainEngine.cginc 可以發現里面的某些實現也是參考上面的文章
更新:
假定波函數為wave( a*t + bx + c),其中a為頻率(或頻率相關的系數,b為波長倒數,c為初相參數
在頻率改變的時候,會導致強烈抖動,因為相位發生跳動, 如果頻率改變量是Δf, wave( (a+Δf)*t + bx + c) 相位的參數跳動為:Δf*t0, 其中t0為改變頻率那一刻的時間
修正方法:把每次的相位跳動抵消值,-Δf*t0 累加到參數c中
更新2:
遠處billboard的波動,假定billboard在viewspace,那么把world space的wave offest轉到view space,疊加就可以,當然為了效率可以簡化,因為billboard離得比較遠:
//note: the more correct way should preserve original length: //float3 vdiff = vpos - vpivot; //float len = length(vdiff); //vpos = vpipot + normalize(vdiff + wave)*len; //but since it's far low LOD, some stretch won't be a big problem vpos.xyz += mul((float3x3)UNITY_MATRIX_V, wind.xyz);
草和地形shading的匹配
由於效率的原因,草的shading是在vertex shader里面完成的custom lighting,所以和地形的光照並不匹配,比如向光面或者背光面,兩者亮度的差異。
為了表現效果,目前的terrain用的Unity的standrad specular,blinn phong效果太差。為了提高光照的匹配度,原則上講,需要草和地形的shading使用相同或者近似的shading model,問題就可以解決。
以上思路很簡單,大部分都是體力活,簡單備忘一下調試流程:
純色調整(關掉lighting,ambient可選擇性打開 先對顏色)--> diffuse 調整 (在shader里關掉specular、ambient,調整shading function以達到diffuse匹配) --> specular調整(關閉/注釋掉ambient、diffuse)
最后再把光照打開,lighting應該對應的差不多了
另外,割草時飛的草葉(例如particle),理論上也需要一定程度的匹配(比如使用相同的shader/shader header/shading),避免色差太大。
2018.06.21:
由於反饋的結果太像塞爾達(笑哭), 抄襲痕跡太重(基本就是照着它的效果做的), 后面會加上自己的風格.
因為工作太忙, 暫時不更了