找回密碼
 注冊帳號

掃一掃,訪問微社區

——待審專欄,請聯系管理員 Unity Shader基于視差映射的云海效果

9
回復
1097
查看
打印 上一主題 下一主題
[ 復制鏈接 ]
4四處流浪
330/500
排名
13986
昨日變化

56

主題

99

帖子

330

積分

Rank: 4

UID
5
好友
1
蠻牛幣
1107
威望
0
注冊時間
2013-5-24
在線時間
65 小時
最后登錄
2019-12-9

馬上注冊,結交更多好友,享用更多功能,讓你輕松玩轉社區。

您需要 登錄 才可以下載或查看,沒有帳號?注冊帳號

x
本帖最后由 badwolf 于 2019-9-27 11:35 編輯

還是先上一個動態圖
這個方法是一個同事教給我的,個人覺得效果很好,在這里借花獻佛,拿出來與大家分享。
這種方法是基于一種叫視差映射的方法POM(Parallax Occlusion Mapping),網上有詳細講解這種方法在圖形學里的應用。

原文很長,我這里只簡單介紹一下,有興趣的同學可以深入研究下。
在計算機圖形學中視差映射是法線映射的一個增強版本,它不止改變了光照的作用方式,還在平坦的多邊形上創建了3D細節的假象,不會生成任何額外的圖元。
要實現視差映射你需要一張高度貼圖。高度圖中的每個像素包含了表面高度的信息。紋理中的高度會被轉化成對應的點沉入表面多少的信息。這種情況你得把高度圖中讀出來的值反過來用。
一般情況,視差映射會把高度圖中的值當深度來用,黑色(0)代表和表面齊平的高度,白色(1)代表最深的凹陷值。
設當前點(譯者:原文中用的是Fragment,片元。)是圖片中用黃色方塊高亮出來的那個點,這個點的紋理坐標是T0。
向量V是從攝像機到點的方向向量。用坐標T0在高度圖上采樣,你能得到這個點的高度值H(T0)=0.55。這個值不是0,所以點并不是在表面上,而是凹陷下去的。
所以你得把向量V繼續延長直到與高度圖定義出來的表面最近的一個交點。這個交點我們說它的深度就是H(T1),它的紋理坐標就是T1。所以我們就應該用T1的紋理坐標去對顏色和法線貼圖進行采樣。
所以說,所有視差映射技術的主要目的,就是要精確的計算攝像機的向量V和高度圖定義出來的表面的交點。

視差映射和帶偏移上限的視差映射
視差映射的計算是在切空間進行的(跟法線映射一樣)。所以指向光源的向量(L)和指向攝像機的向量(V)應該先被變換到切空間。
頂點著色器把光照向量和攝像機向量變換到切空間。片元著色器調用視差映射的相關函數,然后計算自陰影系數,并計算最終光照后的顏色值。

視差映射中最簡單的版本只取一步近似來計算新的紋理坐標,這項技術被簡單的稱為視差映射。
視差映射只有在高度圖相對比較平滑,并且不存在復雜的細節時,才能得到相對可以接受的效果。如果攝像機向量和法線向量的夾角過大的話,視差映射的效果會是錯誤的。視差映射近似計算的核心思想是:
(1)從高度圖讀取紋理坐標T0位置的高度H(T0)
(2)根據H(T0)和攝像機向量V,在初始的紋理坐標基礎上進行偏移。
偏移紋理坐標的方法如下。因為攝像機向量是在切空間下,而切空間是沿著紋理坐標方向建立的,所以向量V的X和Y分量就可以直接不加換算的用作紋理坐標的偏移量。
向量V的Z分量是法向分量,垂直于表面。你可以用Z除X和Y。這就是視差映射技術中對紋理坐標的原始計算。你也可以保留X和Y的值,這樣的實現叫帶偏移上限的視差映射。
帶偏移上限的視差映射可以避免在攝像機向量V和法向量N夾角太大時的一些詭異的結果。然后你把V的X和Y分量加到原始紋理坐標上,就得到了沿著V方向的新的紋理坐標。
下面是偏移后的紋理坐標Tp的最終公式:

下圖展示了高度圖中的深度值H(T0)是如何影響紋理坐標T0沿著V方向偏移的。

陡峭視差映射 (Steep Parallax Mapping, SPM)

這種方法的核心思想是把表面的深度切分成等距的若干層。然后從最頂端的一層開始采樣高度圖,每一次會沿著V的方向偏移紋理坐標。
如果點已經低于了表面(當前的層的深度大于采樣出的深度),停止檢查并且使用最后一次采樣的紋理坐標作為結果。


浮雕視差映射 (Relief Parallax Mapping, RPM)
浮雕視差映射升級了陡峭視差映射,讓我們的shader能找到更精確的紋理坐標。首先你先用浮雕視差映射,然后你能得到交點前后的兩個層,和對應的深度值。
在下面的圖中這兩個層分別對應紋理坐標T2和T3。現在你可以用二分法來進一步改進你的結果,每一次搜索迭代可以使精確度提升一倍。


視差遮蔽映射(Parallax Occlusion Mapping, POM)
視差遮蔽映射(POM)是陡峭視差映射的另一個改進版本。浮雕視差映射用了二分搜索法來提升結果精度,但是搜索降低程序性能。視差遮蔽映射旨在比浮雕視差映射更好的性能下得到比陡峭視差映射更好的效果。但是POM的效果要比浮雕視差映射差一些。
視差遮蔽映射簡單的對陡峭視差映射的結果進行插值。
視差遮蔽映射可以使用相對較少的采樣次數產生很好的結果。但視差遮蔽映射比浮雕視差映射更容易跳過高度圖中的小細節,也更容易在高度圖數據產生大幅度的變化時得到錯誤的結果。


云海效果的實現
這里的云海shader有RPM和POM兩個版本,最終使用的是RPM。
在迭代次數足夠的情況下,兩種差別不大。
躁波圖的rgb是顏色,a是高度
VS方法
[C#] 純文本查看 復制代碼
v2f vert (appdata_full v)[/size]
[size=4]{[/size]
[size=4]    v2f o;[/size]
[size=4]    o.pos = UnityObjectToClipPos(v.vertex);[/size]
[size=4]    o.uv = TRANSFORM_TEX(v.texcoord,_MainTex) + frac(_Time.y*_HeightTileSpeed.zw);[/size]
[size=4]    o.uv2 = v.texcoord * _HeightTileSpeed.xy;[/size]
[size=4]    o.posWorld = mul(unity_ObjectToWorld, v.vertex);[/size]
[size=4]    o.normalDir = UnityObjectToWorldNormal(v.normal);[/size]
[size=4]    TANGENT_SPACE_ROTATION;[/size]
[size=4]    o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));[/size]
[size=4]    o.color = v.color;[/size]
[size=4]#if USING_FOG[/size]
[size=4]    HeightFog(o.posWorld.xyz,o.fog);[/size]
[size=4]#endif[/size]
[size=4]    UNITY_TRANSFER_FOG(o,o.pos);[/size]
[size=4]    return o;[/size]
[size=4]}[/size]

[size=4]

shader計算了兩套uv,uv是高度和顏色貼圖的uv,uv2是做擾動用的uv
viewDir是切線空間下的視線向量,透明度寫在頂點色里
FS方法里初始化
[C#] 純文本查看 復制代碼
float3 viewRay = normalize(-i.viewDir);
viewRay.z = abs(viewRay.z)+0.42;
viewRay.xy *= _Height;
 
float3 shadeP = float3(i.uv,0);
float3 shadeP2 = float3(i.uv2,0);

0.42的一個hack的值,是為了防止攝像機向量V和法向量N夾角過大。本身精度足夠是不需要這個值的,但是為了減少迭代次數必須加上這個值。
_Height控制云的凹凸程度。
shadeP、shadeP2 用于記錄迭代的狀態。
shadeP的xy是采樣uv,z是當前深度。
shadeP2的xy是擾動uv,z沒有用到。
視差計算
RPM版

[C#] 純文本查看 復制代碼
const int linearStep = 2;
const int binaryStep = 5;
float4 T = tex2D(_MainTex, shadeP2.xy);
float h2 = T.a * _HeightAmount;
// linear search
float3 lioffset = viewRay / (viewRay.z * (linearStep+1));
for(int k=0; k<linearStep; k++)
{
    float d = 1.0 - tex2Dlod(_MainTex, float4(shadeP.xy,0,0)).a * h2;
    shadeP += lioffset * step(shadeP.z, d);
}
// binary search
float3 biOffset = lioffset;
for(int j=0; j<binaryStep; j++)
{
    biOffset = biOffset * 0.5;
    float d = 1.0 - tex2Dlod(_MainTex, float4(shadeP.xy,0,0)).a * h2;
    shadeP += biOffset * sign(d - shadeP.z);
}

先線性查找,再用二分法查找。
采樣過程中乘上擾動的高度值。
注:項目用的深度圖其實是高度圖,所以采樣和上文有點區別,取的是一個反向的值。
迭代次數越多,效果越好,過少會出現顏色分層。
POM版
[C#] 純文本查看 復制代碼
float linearStep = 7;[/size]
[size=4]float4 T = tex2D(_MainTex, shadeP2.xy);[/size]
[size=4]float h2 = T.a * _HeightAmount;[/size]
[size=4]float3 lioffset = viewRay / (viewRay.z * linearStep);[/size]
[size=4]float d = 1.0 - tex2Dlod(_MainTex, float4(shadeP.xy,0,0)).a * h2;[/size]
[size=4]float3 prev_d = d;[/size]
[size=4]float3 prev_shadeP = shadeP;[/size]
[size=4]while(d > shadeP.z)[/size]
[size=4]{[/size]
[size=4]    prev_shadeP = shadeP;[/size]
[size=4]    shadeP += lioffset;[/size]
[size=4]    prev_d = d;[/size]
[size=4]    d = 1.0 - tex2Dlod(_MainTex, float4(shadeP.xy,0,0)).a * h2;[/size]
[size=4]}[/size]
[size=4]float d1 = d - shadeP.z;[/size]
[size=4]float d2 = prev_d - prev_shadeP.z;[/size]
[size=4]float w = d1 / (d1 - d2);[/size]
[size=4]shadeP = lerp(shadeP, prev_shadeP, w);[/size]
[size=4]

先線性查找,最后直接對最后兩次查找做線性插值。
迭代的次數過少會丟失細節。
光照計算
[C#] 純文本查看 復制代碼
half4 c = tex2D(_MainTex,shadeP.xy) * T * _Color;[/size]
[size=4]half Alpha = i.color.r;[/size]
[size=4]float3 normal = normalize(i.normalDir);[/size]
[size=4]half3 lightDir = UnityWorldSpaceLightDir(i.posWorld);[/size]
[size=4]float NdotL = max(0,dot(normal,lightDir));[/size]
[size=4] [/size]
[size=4]#if USING_FOG[/size]
[size=4]    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.posWorld));[/size]
[size=4]    float sunFog = saturate( dot(-viewDir,lightDir));[/size]
[size=4]    half3 sunFogColor  = lerp(_HeightFogColor,_sunFogColor,pow(sunFog,2));[/size]
[size=4]    fixed3 finalColor = c.rgb * (NdotL * lightColor + unity_AmbientEquator.rgb * sunFogColor * _LightIntensity);[/size]
[size=4]    unity_FogColor.rgb = lerp(sunFogColor, unity_FogColor.rgb, i.fog.y*i.fog.y);[/size]
[size=4]    finalColor.rgb = lerp(finalColor.rgb,unity_FogColor.rgb, i.fog.x);[/size]
[size=4]#else[/size]
[size=4]    fixed3 finalColor = c.rgb*(NdotL*lightColor + unity_AmbientEquator.rgb);[/size]
[size=4]#endif[/size]
[size=4]UNITY_APPLY_FOG(i.fogCoord, finalColor);[/size]
[size=4] [/size]
[size=4]return ColorOutput(fixed4(finalColor.rgb,Alpha));[/size]
[size=4]

這里我也放了整個工程文件,有興趣可以下載來看看
提取碼:qt14


回復

使用道具 舉報

2初來乍到
116/150
排名
28932
昨日變化

0

主題

62

帖子

116

積分

Rank: 2Rank: 2

UID
55623
好友
0
蠻牛幣
14
威望
0
注冊時間
2014-11-15
在線時間
42 小時
最后登錄
2019-10-16
沙發
2019-10-15 15:32:47 只看該作者
牛皮反復發生范德薩發大水發射點發射點
回復 支持 反對

使用道具 舉報

0

主題

16

帖子

38

積分

Rank: 1

UID
281120
好友
0
蠻牛幣
4
威望
0
注冊時間
2018-5-14
在線時間
23 小時
最后登錄
2019-12-4
板凳
2019-10-17 11:23:21 只看該作者
厲害厲害
回復

使用道具 舉報

6蠻牛粉絲
1159/1500
排名
3288
昨日變化

2

主題

231

帖子

1159

積分

Rank: 6Rank: 6Rank: 6

UID
55491
好友
1
蠻牛幣
1637
威望
0
注冊時間
2014-11-14
在線時間
458 小時
最后登錄
2019-12-5
地板
2019-10-29 08:25:07 只看該作者
應該多發些這樣的帖子嘛
回復 支持 反對

使用道具 舉報

8常駐蠻牛
5751/10000
排名
1
昨日變化

12

主題

239

帖子

5751

積分

Rank: 8Rank: 8

UID
2427
好友
4
蠻牛幣
8764
威望
0
注冊時間
2013-8-22
在線時間
1624 小時
最后登錄
2019-12-9
5#
7 天前 只看該作者
天空盒子+HDR貼圖更省資源
回復 支持 反對

使用道具 舉報

6蠻牛粉絲
1048/1500
排名
10708
昨日變化

0

主題

754

帖子

1048

積分

Rank: 6Rank: 6Rank: 6

UID
301976
好友
1
蠻牛幣
1582
威望
0
注冊時間
2018-10-31
在線時間
196 小時
最后登錄
2019-12-9
6#
7 天前 只看該作者
大贊....     
回復 支持 反對

使用道具 舉報

6蠻牛粉絲
1048/1500
排名
10708
昨日變化

0

主題

754

帖子

1048

積分

Rank: 6Rank: 6Rank: 6

UID
301976
好友
1
蠻牛幣
1582
威望
0
注冊時間
2018-10-31
在線時間
196 小時
最后登錄
2019-12-9
7#
7 天前 只看該作者
簡直是十分的優秀,向大佬致敬
回復 支持 反對

使用道具 舉報

5熟悉之中
734/1000
排名
5900
昨日變化

0

主題

349

帖子

734

積分

Rank: 5Rank: 5

UID
11484
好友
0
蠻牛幣
2
威望
0
注冊時間
2013-12-31
在線時間
149 小時
最后登錄
2019-12-9
8#
6 天前 只看該作者
厲害, 感謝分享~
回復

使用道具 舉報

7日久生情
1699/5000
排名
1946
昨日變化

16

主題

489

帖子

1699

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
182832
好友
7
蠻牛幣
8443
威望
0
注冊時間
2016-11-11
在線時間
666 小時
最后登錄
2019-12-9
9#
7 小時前 只看該作者
new be -------------
回復 支持 反對

使用道具 舉報

7日久生情
2227/5000
排名
1896
昨日變化

61

主題

811

帖子

2227

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
214924
好友
8
蠻牛幣
25235
威望
0
注冊時間
2017-3-28
在線時間
639 小時
最后登錄
2019-12-9
10#
4 小時前 只看該作者
不明覺厲,沒別的辦法
回復 支持 反對

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 注冊帳號

本版積分規則

法甲球队主场名称