屏幕后处理·雾效

在阅读《Unity Shader入门精要》时,跟着此书实现了案例shader,今天把第十三章介绍的屏幕后处理之全局雾效复现了一遍。记录一下。感谢乐乐女神~

雾效效果

添加雾效前的场景:

添加雾效后的场景:

该雾效脚本中FogDensity为0.72

思路

在屏幕后处理阶段添加雾效,在camera中添加雾效脚本,使得在Y轴方向根据位置高低,将原渲染结果与雾以不同系数混合输出最终效果。

雾效混合系数与其计算公式

需要一个混合系数f,来混合原始rgb值与雾rgb值:

1
float3 afterFog = f * FogColor + (1-f) * origColor;

书中采用类似线性的雾效公式来计算f:
$$
f ={H_{end}-h}\over{H_{end}-H_{start}}
$$
如果h是指世界空间Y轴坐标的话,可见随着海拔增高,f越小,则雾越淡。

世界坐标的求解

因为雾效混合系数的计算需要渲染位置的世界坐标,因此在后处理shader中计算像素的世界坐标是必须的一步。很容易想到用View矩阵和Projection矩阵的逆矩阵来计算屏幕像素的世界坐标,但这种需要在fragment shader中进行矩阵运算的方法性能不佳,因此需要另外一种方法。

另一种方法,就是利用相机的世界坐标与像素位置相对于相机的偏移来计算像素的实际世界坐标,即根据公式:

1
float4 worldPos=_WorldSpaceCameraPos+lineDepth*interpolatedRay;

其中lineDepth可以由相机的深度纹理直接得到,interpolatedRay是每个像素与相机相关的射线向量,该向量记录了位置与方向信息。
且interpolatedRay是由硬件插值计算来的,使得消耗更小的算力。

camera深度纹理的获取

在脚本中设置camera的深度纹理类型:

1
camera.depthTextureMode |= DepthTextureMode.Depth;

在shader中布置深度纹理变量,并获取对应像素位置的深度值:

1
2
3
4
5
...
sampler2D _CameraDepthTexture;
...
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth));
...

shader

关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
fixed4 frag(v2f i) :SV_Target{
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth));
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;
float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
fogDensity = saturate(fogDensity * _FogDensity);//Clamps x to the [0, 1] range.

fixed4 finalColor = tex2D(_MainTex, i.uv);
finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);

return finalColor;
}

记住个shader中常用的函数saturate:

参考文献

Unity Shader 入门精要