[工作積累] 大型世界的草渲染


最近工作在研究如何在大型世界的植被(主要是草)的渲染,主要考慮下面幾個問題:

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;
#endif

half2 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:

由於反饋的結果太像塞爾達(笑哭), 抄襲痕跡太重(基本就是照着它的效果做的), 后面會加上自己的風格.

因為工作太忙, 暫時不更了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM