關(guān)于TOD
前項目在模仿塞爾達的晝夜變換效果時用過 Time Of Day 這個Unity插件歉铝。
本文記錄一下 TOD 的云層渲染方式,以及如何讓 TOD 的云層遮擋太陽光的 Flare扑媚。
TOD渲染順序
以上圖的白天為例,TOD的主要渲染順序如下:
- 繪制太陽 (Background+30)
- 繪制大氣層 (Background+50)
- 繪制云層 (Geometry+530)
其中轻姿,太陽和大氣層都是實體渲染乳乌,按照實體渲染從近往遠的優(yōu)化策略,這里 RenderQueue 放在 Background 似乎不太合理叙淌,我們游戲中會把他們的渲染順序調(diào)整到 Geometry 的后面秤掌。
關(guān)于太陽和大氣層的渲染,特別是大氣層的渲染鹰霍,內(nèi)容還不少闻鉴,日后慢慢寫,本文主要是關(guān)于云層渲染的茂洒。
TOD的云層渲染
大氣層的模型是一個球面孟岛,而云層的模型是一個半球面,因為云層不會出現(xiàn)在地平線之下:
云層渲染的基本原理比較簡單督勺,就是根據(jù)一張密度圖計算云層的形狀渠羞,貼在這個半球面上,然后和大氣層做Alpha混合智哀。
作者在這個插件的主頁說這個云層是 Semi-volumetric 的次询,從代碼上看,這里的半體積其實就是在 y 和 z 兩個方向計算云的密度瓷叫,這樣計算很快屯吊,雖然不是真正的 體積云送巡,但是效果不錯,移動設(shè)備也能跑得動雌芽。
云層的密度圖
云層的形狀主要根據(jù)作者提供的一張密度圖來計算授艰,密度圖的4個通道分別是不同頻率的噪聲圖,如下:
在介紹密度計算之前世落,我們首先需要處理好半球面上的點到密度圖的UV映射問題淮腾。
云層的UV計算
根據(jù) TOD 的實現(xiàn)機制,攝像機剛好位于球面的中心屉佳,我們可以用模型空間下 歸一化 的 viewDir 來映射密度圖的紋理坐標谷朝。
這里 viewDir 計算方式如下:
o.viewDir = normalize(v.vertex.xyz);
考慮下面這種最簡單的映射方式:用 viewDir 的水平面 XZ坐標 直接映射紋理的 UV:
可以看到,云層越接近水平線武花,拉伸的就越厲害圆凰,因為均勻變化的水平坐標XZ對應的球面跨度變化是不均勻的铣减。
要得到正確的結(jié)果佣赖,我們可以參考 這篇文章 來做球面點到紋理二維坐標的轉(zhuǎn)換闹蒜,不過作者用了一個計算量更小的方式:
inline float3 CloudPosition(float3 viewDir, float3 offset)
{
float mult = 1.0 / lerp(0.1, 1.0, viewDir.y);
return (float3(viewDir.x * mult + offset.x, 0, viewDir.z * mult + offset.z)) / TOD_CloudSize;
}
這里就是把 viewDir.y 考慮進去观挎,這個值越小越接近水平線,XZ 平面上的跨度就越大割择,效果如下:
云層的密度計算
正確計算UV后蚤吹,我們就可以在像素著色器計算密度了球榆。
這里先不考慮N層噪聲的疊加娃兽,我們看一下一層噪聲的代碼:
inline half3 CloudLayerDensity(sampler2D densityTex, float4 uv, float3 viewDir)
{
half3 density = 0;
half4 n = tex2D(densityTex, uv.xy);
// Density when marching in up direction
density.y += n.r;
// Density when marching in view direction
density.z += n.r;
// Coverage
density.yz = (density.yz - TOD_CloudCoverage) * half2(TOD_CloudAttenuation, TOD_CloudDensity);
// Opacity
density.x = saturate(density.z);
return density;
}
這里 y 和 z 兩個方向的密度都取密度圖的R通道菇民,TOD_CloudCoverage 用于裁剪云朵的范圍,TOD_CloudDensity 可以控制云朵邊緣的透明程度投储,TOD_CloudAttenuation 可以控制云朵的顏色衰減第练。
云層的顏色計算
得到密度后,云層的顏色計算代碼如下:
inline half4 CloudLayerColor(sampler2D densityTex, float4 uv, float4 color, float3 viewDir, float3 lightDir, float3 lightCol)
{
half3 density = CloudLayerDensity(densityTex, uv, viewDir);
half4 res = 0;
res.a = density.x;
res.rgb = 1.0 - density.y;
res *= color;
return res;
}
代碼很簡單玛荞,不啰嗦娇掏。
云層的渲染分級
上面的代碼只是云層最基礎(chǔ)的計算,TOD 針對云層的渲染質(zhì)量分了三級:
- TOD_CloudQualityType.Low
- 一層噪聲
- TOD_CloudQualityType.Medium
- 四層噪聲疊加
- TOD_CloudQualityType.High
- 四層噪聲疊加
- 米氏散射疊加
這里就不貼代碼了勋眯,有興趣的話可以去支持一下作者驹碍。
Flare遮擋
下面回到項目中實際遇到的問題,如下圖所示凡恍,太陽其實已經(jīng)被云層遮擋了,但是 Flare 還是顯示了出來:
要解決這個問題其實也簡單怔球,首先我們需要定義自己的 LensFlare 的渲染嚼酝,如下圖:
然后,在像素著色器部分竟坛,我們需要計算出太陽所在位置的云密度闽巩,進而計算出 影衰減 并應用到 Flare 上钧舌,主要代碼如下:
half4 frag (v2f i) : SV_Target
{
half4 texColor = tex2D(_FlareTexture, i.uv);
float3 skyPos = normalize(mul((float3x3)TOD_World2Sky, TOD_SunWorldPos));
float4 cloudUV = CloudUV(skyPos);
float cloudShadowAtten = TOD_CloudOpacity * CloudShadow(TOD_CloudTexture, cloudUV);
cloudShadowAtten = 1 - cloudShadowAtten;
texColor.rgb = texColor.rgb * cloudShadowAtten;
return texColor * i.color;
}
最后放一張動圖:
個人主頁
本文的個人主頁鏈接:https://baddogzz.github.io/2020/04/16/Tod-Cloud/。
好了涎跨,拜拜洼冻!