利用GPU实现无尽草地的实时渲染
利用GPU實現(xiàn)無盡草地的實時渲染
0x00 前言
在游戲中展現(xiàn)一個寫實的田園場景時,草地的渲染是必不可少的,而一提到高效率的渲染草地,很多人都會想起GPU Gems第七章
《Chapter 7. Rendering Countless Blades of Waving Grass》中所提到的方案。
現(xiàn)在國內(nèi)很多號稱“次世代”的手游甚至是一些端游仍或多或少的采用了這種方案。但是本文不會為這個方案著墨過多,相反,接下來的大部分內(nèi)容是關(guān)于如何利用Geometry Shader在GPU生成新的獨立草體的。
0x01 一個簡單的星型
傳統(tǒng)的方式,即將模型數(shù)據(jù)從CPU傳遞給GPU,GPU再根據(jù)這些數(shù)據(jù)進(jìn)行渲染的方式在渲染大規(guī)模的草體時,往往會忽略單個草體的模型細(xì)節(jié)。因為單個草體的建模如果過于細(xì)致,則渲染大片的草地就需要傳遞很多多邊形,從而造成性能的下降。
因此,一個渲染大片草地的方案往往需要滿足以下條件:
- 單個草的多邊形不能過多,最好一棵草只用一個quad來表示
- 從不同的角度觀察,草都必須顯得密集
- 草的排布不能過于規(guī)則,否則會不自然
綜上,渲染草體時的經(jīng)典結(jié)構(gòu)——星形就出現(xiàn)了。
?
?
這樣,簡單的星形結(jié)構(gòu)既滿足了單棵草的面數(shù)很低同時也兼顧了從不同角度觀察也能夠顯得密集。 而讓草隨風(fēng)而動也很簡單,只需要根據(jù)頂點的uv信息找出上面的幾個頂點,按照自己規(guī)則讓頂點移動就可以了。
if (o.uv.y > 0.5) {float4 translationPos =float4(sin(_Time.x * _TimeFactor * Pi ), 0, sin(_Time.y * _TimeFactor * Pi ), 0); v.vertex += translationPos * _StrengthFactor; }現(xiàn)在很多游戲在渲染草地時仍然使用了這種結(jié)構(gòu)。
(圖片來自:九州天空城3D)
(圖片來自:劍網(wǎng)3)
但是,各位也都看到了,這種方式雖然簡單,但是卻并不自然,從上方俯視的時候各個面片也能看到清清楚楚,因此這種方式并不是我想要的。
0x02 更真實的草葉
我想要的效果是能夠大規(guī)模實時渲染,并且每一顆草的葉片都能夠隨風(fēng)搖曳的更真實自然的效果。在這方面,業(yè)內(nèi)早有一些探索,例如Siggraph2006上的《Rendering Grass Terrains in
Real-Time with Dynamic Lighting》,以及Edward Lee的論文《REALISTIC REAL-TIME GRASS RENDERING》。
本文主要按照Edward Lee的論文中描述方式在Unity中實現(xiàn)GPU生成無盡草地隨風(fēng)搖曳的效果。
這里,我主要用到了Direct3D 10之后新引入的Geometry Shader來實現(xiàn)在GPU上創(chuàng)建單獨草體葉片的邏輯。每個葉片根據(jù)LOD有3種組成方式,分別需要1個quad、3個quad以及5個quad。
(圖片來自:Edward Lee)
而每顆草的位置則由CPU來隨機決定,由于GS的輸入是一個圖元(point、line或triangle)而非頂點,所以我們在CPU中需要根據(jù)隨機的位置創(chuàng)建point類型的圖元作為這棵草的根位置。
ok,接下來就在GPU上通過一個根位置來制作草的葉子。
[maxvertexcount(30)]void geom(point v2g points[1], inout TriangleStream<g2f> triStream){float4 root = points[0].pos;雖然位置是隨機的,但是我們顯然也希望葉子本身的高度和寬度也存在一些隨機。
float random = sin(UNITY_HALF_PI * frac(root.x) + UNITY_HALF_PI * frac(root.z));_Width = _Width + (random / 50); _Height = _Height +(random / 5);設(shè)置好葉子的屬性之后,我們就可以根據(jù)這些屬性來創(chuàng)建新的頂點模擬葉子的樣子了。
畫一個簡圖各位可以看到,組成一顆草的葉子需要12個不同的頂點,但是由于這里沒有用index,所以最后總共要輸出30個頂點來組成5個quad。
而根據(jù)這幅簡圖,我們還可以很方便的根據(jù)根的位置計算各個頂點的位置。
同時,還能發(fā)現(xiàn)偶數(shù)頂點對應(yīng)的uv坐標(biāo)是(0,v),而奇數(shù)頂點對應(yīng)的uv坐標(biāo)都是(1,v)——這里的v是uv坐標(biāo)中的v——因此,我們又能很輕松的計算出各個頂點對應(yīng)的uv坐標(biāo)了。
最后,如果我們要計算實時光,則還需要獲取頂點的法線信息,這里簡單起見統(tǒng)一為(0, 0, 1)。
for (uint i = 0; i < vertexCount; i++){v[i].norm = float3(0, 0, 1); if (fmod(i , 2) == 0) { v[i].pos = float4(root.x - _Width , root.y + currentVertexHeight, root.z, 1); v[i].uv = float2(0, currentV); } else { v[i].pos = float4(root.x + _Width , root.y + currentVertexHeight, root.z, 1); v[i].uv = float2(1, currentV); currentV += offsetV; currentVertexHeight = currentV * _Height; } v[i].pos = UnityObjectToClipPos(v[i].pos); }這樣,一個葉片的網(wǎng)格就在GPU上創(chuàng)建完成了。
接下來,我們需要處理一下草葉的紋理來渲染出符合我們預(yù)期的葉片。這里我用到了GPU Gem那篇文章中的草叢紋理的處理方法:
即葉片的顏色可以只用一個張單獨表示葉片顏色的紋理來處理,比如我用的這張紋理:
而草體的具體輪廓則靠另一張紋理提供。但是這里沒有使用alpha blend,而是使用了alpha to coverage,因為在處理重重疊疊的草葉時blend會有一些顯示順序上的問題,至于如何使用alpha to coverage各位可以參考SL-Blend。
SubShaderTags{ "Queue" = "AlphaTest" "RenderType" = "TransparentCutout" "IgnoreProjector" = "True" } Pass AlphaToMask On所以,現(xiàn)在我們只需要在fs內(nèi)簡單的取樣輸出就可以了。
half4 frag(g2f IN) : COLOR{fixed4 color = tex2D(_MainTex, IN.uv);fixed4 alpha = tex2D(_AlphaTex, (IN.uv)); return float4(color.rgb, alpha.g); }0x03 生成覆蓋地面的無盡草地
有了葉子之后,我們就可以考慮如何生成地形以及地面上覆蓋的草了。為了地面的起伏輪廓自然真實,我們可以根據(jù)一張高度圖來動態(tài)創(chuàng)建地面的網(wǎng)格。
由于Unity的網(wǎng)格頂點上限是65000,因此我決定讓地面網(wǎng)格的尺寸為250 * 250:
這樣,一個自然而真實地面網(wǎng)格就創(chuàng)建好了。
之后就來生鋪草吧。所謂的鋪草無非就是我們需要生成一些頂點,作為草葉的根位置傳入之前完成的GS。需要說明的是,由于草的密度要足夠大,因此不止需要一個草地的mesh,例如我們要種200,000棵草的話就需要3個草地mesh。另外還要說明的一點,也是要吐槽Unity的地方就在于Unity的mesh實現(xiàn)默認(rèn)是triangle,而非point(參考Invoking Geometry Shader for every vertex of a mesh)。因此創(chuàng)建記錄草根位置的mesh的方法和之前創(chuàng)建地面稍有不同。
m.vertices = verts.ToArray();m.SetIndices(indices,MeshTopology.Points, 0);grassLayer = new GameObject("grassLayer"); mf = grassLayer.AddComponent<MeshFilter>();grassLayer.AddComponent<MeshRenderer>();創(chuàng)建好之后,可以看到草根的位置隨機的分布在地面上,數(shù)量有上百萬個。
把我們的shader應(yīng)用于記錄草根位置的mesh上。
wow,我們的草地出現(xiàn)了。
0x03 風(fēng)的模擬
呆立的草雖然看上去比之前的紙片草好看了很多,但是靜止而整齊的葉子畢竟還是很不自然。因此,我們要讓草動起來也就是模擬風(fēng)的效果。
思路仍然是利用三角函數(shù)來讓草葉搖擺起來,同時根據(jù)草的根位置為三角函數(shù)提供初始相位然后再增加一些隨機性在里面讓效果更自然。
但是針對目前每一顆草都有獨立的葉片網(wǎng)格,為了更加逼真的模擬風(fēng)的效果,顯然不同的葉片的不同部位受到風(fēng)的影響是不同的。
距離葉子的頂端越近,則受到風(fēng)的影響就越大。
?
?
因此在GS生成新頂點的邏輯中,增加風(fēng)對頂點位置的影響,越高的頂點被影響的程度越大,這樣一個更真實的無盡草地效果就實現(xiàn)了。
更多的渲染效果可以關(guān)注我的公眾號:chenjd01
ref:
【1】《Chapter 7. Rendering Countless Blades of Waving Grass》
【2】《Rendering Grass Terrains in
Real-Time with Dynamic Lighting》
【3】《REALISTIC REAL-TIME GRASS RENDERING》
【4】《Programming Guide for Direct3D 11》
-EOF-
?
http://www.cnblogs.com/murongxiaopifu/p/7572703.html
總結(jié)
以上是生活随笔為你收集整理的利用GPU实现无尽草地的实时渲染的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 记录一次无聊的(经历了Nodejs -
- 下一篇: thrift数据类型