在很多太空科幻類的電影、游戲中、我們常常看到在太空中的星球的場景,在這些場景中我們可以看到真實的行星地表光影效果和云層、以及非常炫酷的大氣層效果,在unity中我們也可以創建類似的效果,本文我將介紹如何在unity shader中撰寫地球特效渲染,


上圖是最終的效果,實作這樣的效果可以使用基于物理的大氣渲染或體素渲染(詳見【Unity/大氣渲染】單次散射的原理和簡單實作 - Relolihentai - 博客園 (cnblogs.com)),在本文我主要介紹通過特效的形式實作地球渲染,并非基于物理的大氣渲染,以下是我的思路,
主要的思路是:對效果進行分層,我們使用三個同心球來渲染地球,從里到外分別是地表、云層、大氣層,以下將分別描述,
1、 地表
地表的渲染其實沒有太多特別之處,主體部分直接用標準馮氏光照模型,如果能夠獲取到高清的地表金屬度和粗糙度貼圖的話也也可以用PBR來達到更真實的效果(不過地球哪來的PBR貼圖啊),在一些影視級別的制作中,為了追求真實性,甚至可以使用高度貼圖而非法線貼圖來真實模擬出地表的凹凸效果,并且將海水與大陸分離來獲取更加真實的水體渲染,不過這都不是本文的重點,
我們直接將三張貼圖:表面顏色貼圖、表面法線貼圖和陸地-海洋貼圖傳遞給shader,其中陸地-海洋圖主要用于區分不同地區的高光強度的區別,
現在的效果如下:

接下來,我們可以加入一個地球的標志性的夜晚燈光的特效,在馮氏光照漫反射項的計算的程序中,我們計算了光源方向與法線方向的點積,夜晚地球暗面的燈光的強度可以視為反向平行光的光照強度,因此直接用1減去點積,即可以得到初步的一個值,將這疊加上去,效果如下:

然后我們開始制作云層,
2、 云層
云層我們使用球體+透明度剔除的方案來制作,給一張云層貼圖即可,將灰度直接映射到剔除檢測值上,就可以完成一個最基礎的云層,同時也使用最基礎的馮氏光照,
補充說明:為了增強真實性,可以在地球表面上通過貼圖的方式顯示云層陰影(因為云層和地表是同心球的方式,所以無法正常投影),在地表shader中新建一個外部變數,將云層的旋轉量傳入shader,通過云層的旋轉量來偏移uv坐標,采樣云層貼圖,通過云層密度來控制陰影濃度,以上思路所得的結果如下圖

3、 大氣層
大氣層是三者之中最復雜的,我們雖然不使用完全基于物理的大氣渲染方法,但是我們也可以通過簡化的運算來模仿大氣投射的效果,
首先假設地面是一個平面,當攝像機在太空中俯瞰地表一點時,光所在大氣層中穿過的距離等于大氣層厚度除以觀察角的正弦值,即
\(l=\frac{h}{\sinα}\)
示意圖如下:

由此圖容易知道,當我們假設大氣不存在散射,并且消光效應對于各個波長的光線是均勻分布的話,那么可以知道:假設垂直向下看時的“濃度”為f,那么對于觀察角度為α時,“大氣濃度”為
\(\frac{f}{\sinα}\)
那么我們就將這個式子拿到球形的地球-大氣層模型中套用即可,我們可以通過地球球心坐標和大氣與地球半徑來計算某一片元處的光在大氣中穿過的距離,示意圖如下:

最終計算得到大氣的濃度是f*r/l*sinalpha. 其中f是單位距離上大氣的濃度值,r是地球半徑,l是地球到球心的距離,α是大氣表面該點到攝像機的向量和地球球心到攝像機的向量的夾角,
注意,我們不僅需要考慮穿過大氣看到地表的情況,也應該考慮穿過大氣看到外太空的情況,所以此處應該分情況討論,
這一部分的代碼如下所示:
// 大氣 // // 當透過大氣能看到地面時 // α是球心到攝像機向量與片元到攝像機向量的夾角 // l是攝像機到球心的距離 // r是地球半徑 // F是大氣霧系數(垂直看向地面時霧的強度/單位積分值) // 計算公式是 (F*r)/(l*sinα) // // 當透過大氣無法看到地面時 // 類似 float3 center = mul(unity_ObjectToWorld , float4(0,0,0,1)).xyz;// 球心 float3 centerDir = normalize(center.xyz - _WorldSpaceCameraPos.xyz);// 攝像機指向球心的方向 float3 fragDir = -normalize(UnityWorldSpaceViewDir(i.worldPos)); // 攝像機指向片元的方向 float cosalpha = abs(dot(centerDir, fragDir)); float sinalpha = abs(sin(acos(cosalpha))); float3 cV = (center.xyz - _WorldSpaceCameraPos.xyz); float length = pow((cV.x * cV.x + cV.y * cV.y + cV.z * cV.z), 0.5); float tangentAngle = asin(EarthRadius / length); // 最終的大氣霧強度 float fogStrength; if (sinalpha <= sin(tangentAngle) + 0.0001) { // 透過大氣能看到地面 float sintheta = abs(sin(acos(length * sinalpha / (EarthRadius + 0.0005)))); fogStrength = InnerFog_Strength * AtoFog / sintheta; } else { // 透過大氣看到宇宙 float3 AtoRadiusVec = center.xyz - i.worldPos.xyz; float AtoRadius = pow((AtoRadiusVec.x * AtoRadiusVec.x + AtoRadiusVec.y * AtoRadiusVec.y + AtoRadiusVec.z * AtoRadiusVec.z), 0.5) + 0.0005; float lightLength = 2 * pow((AtoRadius * AtoRadius - (length * sinalpha) * (length * sinalpha)), 0.5); fogStrength = AtoFog * lightLength / (AtoRadius - EarthRadius); fogStrength *= 1 - pow((((length * sinalpha) - EarthRadius) / (AtoRadius - EarthRadius)), 0.45); }
但是由于我們沒有使用基于物理的大氣散射計算,所以電影中常見的從很低的角度觀察大氣層時的邊緣輝光是不存在的,所以我們通過手工的方式添加這部分邊緣輝光,邊緣輝光既然存在于邊緣,那么應該是越到邊緣越強,在中心則極弱,于是我們可以采用類似于菲涅爾的做法來計算邊緣輝光的強度,這一部分的具體代碼如下:
fixed Alpha = dot(worldLightDir, worldNormal) * 0.5 + 0.5; Alpha = pow(Alpha, 2.75); // 邊緣輝光 float TheCos = dot(worldLightDir, fragDir); float glowStrength_1 = 2 * pow(E, 50 * TheCos - 50); float glowStrength_2 = 5 * pow(E, 200 * TheCos - 200); float3 glow_1; float3 glow_2; if (sinalpha >= sin(tangentAngle) - 0.01) { // 透過大氣看到宇宙 glow_1 = glowStrength_1 * SunGlowColor.xyz; glow_2 = glowStrength_2 * SunGlowColor.xyz; } else { glow_1 = float3(0, 0, 0); glow_2 = float3(0, 0, 0); } fixed4 ans_atomosphere_base = fixed4(AtoCol.xyz, fogStrength * Alpha); fixed4 ans_atomosphere_glow = fixed4(glow_1.xyz + glow_2.xyz, pow(fogStrength, 2) * (glow_1.x + glow_1.y + glow_1.z + glow_2.x + glow_2.y + glow_2.z) / 6);
這樣的大氣層已經非常好看了,但是當我們把攝像機運動到晨昏線附近時,晨昏線附近的大氣層太藍了,缺乏更多的太陽光的暖色部分的層次感,因此我們考慮再增加一層太陽光暖色光波長部分的散射光,尤其是在攝像機從背向對準晨昏線是,需要更多的層次來營造更好看的輪廓,我們通過計算光源(太陽)方向和大氣表面片元到攝像機的向量的夾角大小來控制這部分暖色光的強度,這部分的實作如下:
// 背向輝光 float DawnGlowStrength_1 = 1 * pow(E, 8 * TheCos - 8); fixed Strength = dot(worldNormal, worldLightDir); fixed DawnGlowStrength = max((- 27 / 6 * Strength * pow(E, -4 * pow(Strength, 2))), 0); float3 DawnGlow; if (sinalpha >= sin(tangentAngle) - 0.01) { // 透過大氣看到宇宙 DawnGlow = DawnGlowStrength_1 * DawnGlowColor.xyz * DawnGlowStrength; } else { DawnGlow = float3(0, 0, 0); }
以上的每一層效果單獨的效果和混合的效果如下圖所示:

最終我們添加一些小的引數的調整,就得到了最終的結果,可以看到還是比較漂亮的,不過不夠真實,如果需要真實的話還是要用大氣散射模擬的方式來制作,
以上就是本文的全部內容了,因為距離我實作這個效果已經有一段時間了,有些細節我也無法說得很清楚了,還請見諒,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/550208.html
標籤:其他
下一篇:meterpreter后滲透攻擊
