Unity3D 中的高度雾

fog
图中有2个效果, 分别是 HDR(做法在这里uniy3d-hdr_bloom/, 高度5的白色高度雾
不过除了雾以外,其他都不重要.

unity3d 一直都有一个距离雾,也可以说是深度雾,随着物体的远近慢慢渐变颜色,设置在 Window->Lightning->Fog(Unity 5.x)

这次来实现一个u3d中没有的,从低到高,而不是从远到近的雾效 – 高度雾。

之前做这个效果之前,有查阅些资料,有看到某外国的大大做了个类似的效果,地址在这里 altitude-fog
我们将要做的效果和这个差不多,但是区别在于,老外的做法是需要场景中的每个物体都添加一个雾效材质,这个雾效Shader是判断物件顶点Y轴在世界坐标系中的位置,然后 (FogEnd – 物体Y) / (FogEnd – FogStart) 算出一个在Y轴方向的雾效颜色比例,最后差值一下。

请不要无脑复制转载本文, 由 dreamfairy 原创, 转载请注明出处 本文地址 http://www.dreamfairy.cn/blog/2016/06/06/unity3d-height-fog/
而我采用的办法是通过后期处理来实现高度雾,不需要场景中每个物体单独添加一个雾效材质。 而雾效算法和老外的一样(好吧,其实Linear版的雾效算法全世界都基本一样)。 唯一的难点就是如何在后期处理中的纹理上判断每个像素在世界坐标系中的 Y轴值。

首先,相机部分,肯定要开启输出深度图
Camera.main.depthTextureMode |= DepthTextureMode.Depth;

Shader部分
准备几个参数
_FogStart 雾效在世界坐标系Y轴的开始值
_FogEnd 雾效在世界坐标系Y轴的结束值
_FogColor 雾效颜色

像素着色器雾效的算法

half diff = (_FogEnd – worldPos.y) / (_FogEnd – _FogStart);
fixed4 col = tex2D(_MainTex, i.uv);
fixed4 finalCol = lerp(col, _FogColor, diff);
return finalCol;

到这里都和所有的 Linear雾效做法都一样, 问题就在于如何在 深度图中计算 worldPos.y

一步步来
SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv)
可以获取当前像素的深度值, 值域为 (0~1) 的线性值, 这个值现在还用不了,我们需要真实的深度值
只需要加上这个 LinearEyeDepth 即可
float realDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv));
但是这个 realDepth 并不是 世界坐标系下被看到物体的真实深度, 仅仅是相机距离被看到物体的真实深度, 不过相机的位置对于Shader来说是已知的不是吗? 就是这个 _WorldSpaceCameraPos
那么要求被看到物体的 worldPos.y 只需要 _WorldSpaceCameraPos + realDepth 即可。 嗯,大致就是如此,不过还有一点点小问题。

当我们把 Camera.Main.transform.Fowrard * realDepth (朝向向量 * 距离 = 到达距离), 所有的深度都被投向垂直于屏幕中央的地方去了,而原本深度是通过相机透视投影生成的近大远小的效果就失去了,于是出现了 距离雾的效果
如图
error_height_fog

最好的情况是,对应每个深度,都刚好有一个朝向它的向量 * 它的深度。
于是你可能会这么想,使用uv 来计算每个像素的朝向,像这样

float2 center = float2(0.5, 0.5);
float2 pixelDir = normalize(i.uv - center);

但是二维向量是没法得知 Z 轴方向的朝向的, 而且随着场景中每个物体远近,相机看它们的朝向,不是uv相减形成的固定角度。

辣么还是老老实实的计算相机的朝向,然后传进Shader吧, 但是我们不需要创建 纹理像素数量那么的相机朝向,使用近似值就能达到很好的效果,剩下交给像素插值就好了, 我们只需要传进 uv在 0,1 ,1.0, 1.1, 1.0 4个角落的向量即可。
因此本质上就是计算相机视锥体的 左上角(上平面 – 右平面), 右上角(上平面 + 有平面), 左下角, 右下角

        Camera cam = Camera.main;
        Transform CamTrans = cam.transform;
        float near = cam.nearClipPlane;
        float far = cam.farClipPlane;
        float halfHeight = cam.nearClipPlane * Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad);
        Vector3 toRight = CamTrans.right * halfHeight * cam.aspect;
        Vector3 upVector = CamTrans.up * halfHeight;
        Vector3 topVector = CamTrans.forward * near + upVector;
        Vector3 bottomVector = CamTrans.forward * near - upVector;

        Vector3 bottomLeft = bottomVector - toRight; //左下
        Vector3 bottomRight = bottomVector + toRight; //右下
        Vector3 topLeft = topVector - toRight; //左上
        Vector3 topRight = topVector + toRight; //右上

然后在Shader部分,判断uv位置,取对应的向量

                               if (o.uv.y < 0.5 && o.uv.x < 0.5) {
					o.cameraDir = _CameraMat[0];  //左下
				}
				else if (o.uv.y < 0.5 && o.uv.x > 0.5) { //右下
					o.cameraDir = _CameraMat[1];
				}
				else if (o.uv.y > 0.5 && o.uv.x < 0.5) { //左上
					o.cameraDir = _CameraMat[2];
				}
				else if (o.uv.y > 0.5 && o.uv.y > 0.5) { //右上
					o.cameraDir = _CameraMat[3];
				}

最后在 fragment 中计算 worldPos.y即可

float3 worldPos = _WorldSpaceCameraPos + realDepth * i.cameraDir.xyz;

接下来运行一下,就可以出现本文章第一张图那样的效果啦。

发表评论

电子邮件地址不会被公开。 必填项已用*标注