Unity3D 深度貼花
一年前遇到在地面挖坑需求的時候第一眼想的是CPU端構建,如程式化地形以及CSG,但是實現起來十分吃力,同時應用場景相對有限,隨之棄坑,不過在後續的學習過程中瞭解到視差貼圖原理,以及深度修改,於是將這兩點結合整理出深度貼花。
示例
Unity3D 深度貼花
https://www。zhihu。com/video/1435394469462491136
示例場景: Billboard
渲染佇列配置: 1998 地面 1999 貼花 2000+普通渲染
程式碼實現:UberDiffuse。Shader 索引 189行
核心庫:Parallax。hlsl
視差貼圖對映
通常在表面空間做視覺凹陷,在各種文章上了解到最多的就是視差貼圖 (Parallax Mapping)。
透過在切線空間的視線向量求出平面空間的uv偏移向量,並做迴圈取樣獲取視角偏移距離,並與uv偏移向量相乘獲取最終的uv偏移,即可獲得根據視角進行深度偏移的貼圖。
基礎的視差貼圖對映
https://www。zhihu。com/video/1435412105470644224
//專案內程式碼片段
float
ParallaxMappingPOM
(
TEXTURE2D_PARAM
(
_texture
,
_sampler
),
float
depthOffset
,
float2
uv
,
float2
uvOffset
,
uint
marchCount
)
{
float
deltaParallax
=
1。0
h
/
marchCount
;
float2
deltaUV
=
uvOffset
/
marchCount
;
float
layer
=
0。
h
;
float
parallaxSample
=
SAMPLE_TEXTURE2D_LOD
(
_texture
,
_sampler
,
uv
,
0
)。
r
-
depthOffset
;
float
preParallaxSample
=
0。
h
;
[unroll]
for
(
uint
i
=
0
u
;
i
<
128
u
;
i
+=
1
u
)
{
if
(
parallaxSample
<
layer
)
break
;
preParallaxSample
=
parallaxSample
;
parallaxSample
=
SAMPLE_TEXTURE2D_LOD
(
_texture
,
_sampler
,
uv
,
0
)。
r
-
depthOffset
;
layer
+=
deltaParallax
;
uv
-=
deltaUV
;
}
float
d1
=
layer
-
parallaxSample
;
float
d2
=(
layer
-
deltaParallax
)-
preParallaxSample
;
float
interpolate
=
d1
*
rcp
(
d1
-
d2
);
return
layer
-
interpolate
*
deltaParallax
;
}
//輸入UV以及世界切線空間矩陣,以及世界空間視角方向
void
ParallaxUVMapping
(
inout
float2
uv
,
half3x3
TBNWS
,
half3
viewDirWS
)
{
half3
viewDirTS
=
mul
(
TBNWS
,
viewDirWS
);
half3
viewDir
=
normalize
(
viewDirTS
);
float2
uvOffset
=
viewDir
。
xy
/
viewDir
。
z
;
//UV偏移
uvOffset
*=
INSTANCE
(
_DepthScale
);
//深度比
float
depthOffset
=
INSTANCE
(
_DepthOffset
);
//深度偏差
uint
parallaxCount
=
INSTANCE
(
_ParallaxCount
);
float
parallax
=
ParallaxMappingPOM
(
_DepthTex
,
sampler_DepthTex
,
depthOffset
,
uv
,
uvOffset
,
parallaxCount
);
uv
-=
uvOffset
*
parallax
;
}
注:視差貼圖對映可以實現相較於體積雲更便宜的雲海效果
可參考ParallaxCloud。shader,更多詳情參考知乎某位大佬文章,。
空間資訊修改
在上文的基礎上可以發現,視差貼圖對映只與平面空間的uv做互動,如果要與幾何資訊互動做出更多符合視覺的效果(如陰影,光照與螢幕深度)則需要在對世界座標以及深度進行修改。
對於陰影貼圖與光照取樣透過修改
positionWS
變數,在
fragment
階段計算相應的輸入。
對於螢幕深度則需要透過
SV_DEPTH
輸出修改後的螢幕空間深度深度。
計算步驟
已知其餘資訊,求projectionWS
在進行視差對映前獲取
viewDirTS與normalTS(0,0,1)的點乘
用於後續的資料計算。
由於視差對映獲取的是世界切線空間空間深度
depthTS
與引數相乘求得
depthWS
透過1。得出的投影係數進行投影,獲取到世界空間視差投射距離
projectionDistance
。
與viewDirWS相乘求出世界座標偏移,與原始世界座標相加求出最終世界座標(
positionWS
)。
透過矩陣轉換成螢幕空間深度SV_DEPTH。
帶空間資訊修改的視差貼圖對映
https://www。zhihu。com/video/1435413151173525504
//inout 世界座標以及螢幕深度,用於後續的陰影以及SS深度(如AO)相關計算,並獲得正確的幾何關係。
void
ParallaxUVMapping
(
inout
float2
uv
,
inout
float
depth
,
inout
float3
positionWS
,
half3x3
TBNWS
,
half3
viewDirWS
)
{
half3
viewDirTS
=
mul
(
TBNWS
,
viewDirWS
);
half3
viewDir
=
normalize
(
viewDirTS
);
float2
uvOffset
=
viewDir
。
xy
/
viewDir
。
z
*
INSTANCE
(
_DepthScale
);
float
parallax
=
0。
;
float
depthOffset
=
INSTANCE
(
_DepthOffset
);
float
marchDelta
=
saturate
(
dot
(
half3
(
0。
,
0。
,
1。
),
viewDirTS
));
uint
parallaxCount
=
INSTANCE
(
_ParallaxCount
);
//最佳化,視角越垂直與平面視差貼圖迴圈次數越少
parallaxCount
=
min
(
lerp
(
parallaxCount
/
2
u
,
parallaxCount
,
marchDelta
),
128
u
);
parallax
=
ParallaxMappingPOM
(
_DepthTex
,
sampler_DepthTex
,
depthOffset
,
uv
,
uvOffset
,
parallaxCount
);
float
projectionDistance
=
parallax
*
INSTANCE
(
_DepthScale
)*
step
(
0。01
h
,
marchDelta
)*
rcp
(
marchDelta
);
positionWS
=
positionWS
-
viewDirWS
*
projectionDistance
*
INSTANCE
(
_DepthBufferScale
);
depth
=
EyeToRawDepth
(
TransformWorldToEyeDepth
(
positionWS
,
UNITY_MATRIX_V
));
uv
-=
uvOffset
*
parallax
;
}
侷限性及拓展
-由於進行深度覆蓋,ZTest需要Greater或者Always
-SV_DEPTH 需要更高的Target Level,在低端機型不適用。
-深度構建在GPU端,所以無法同步深度資訊到CPU端,遇到複雜碰撞需求需要做更多邏輯。
*對於視差貼圖的重複取樣最佳化可以嘗試:
透過降低取樣數以及Dither的方式。
對於幾何複雜度不高的淺坑,可以使用單次視差對映取樣結果配合空間資訊修改。
將邏輯替換為幾何射線相交檢測(如AABB與BS),避免多次取樣貼圖帶來的效能開銷。
*在複雜度較高的場景,由於ZTest導致貼花渲染錯誤,目前想到的解決方法有兩點:
透過Geometry佇列深度貼圖做深度比較並對畫素做Clip操作。
CPU Culling把渲染的幾何複雜度降低。
上一篇:國粹藝術名家——潘福忠
下一篇:UniLM論文閱讀筆記