Unity Shader学习笔记(25)全局雾效

发表于2018-01-02
评论1 4.8k浏览
雾效就是在远离我们视角的方向上,物体看起来像被蒙上了某种颜色,下面要和大家介绍的就是全局雾效的实现,想了解全局雾效实现方法的可要认真看了。

雾效(Fog)

实现方法: 
- Unity内置雾效可以产生基于距离的线性或指数雾效。 
- 自行编写雾效需要添加#pragma multi_compile_fog指令,并使用相关宏。缺点是需要为所有物体添加渲染代码,实现效果有限。 
- 根据深度纹理重建每个像素世界空间下位置,使用公式模拟雾效。

重建世界坐标

由世界空间下相机位置加上像素相对相机偏移量即可。 
float worldPos = _WorldSpaceCameraPos + linearDepth * interpolatedRay; 
- _WorldSpaceCameraPos:世界空间下相机位置,Unity内置变量。 
- linearDepth:深度纹理的线性深度值。 
- interpolatedRay:顶点着色器输出并插值后的射线,包含方向和距离。

interpolatedRay

实质是存了近裁切面的四个角的向量中最靠近的一个。在顶点着色器中,根据当前位置,选择对应的向量,做插值,最后传递给片元着色器得到interpolatedRay。 

相关计算:aspect为宽高比。 
 
 

雾的计算

颜色混合类似透明度混合:f为混合系数。 
float3 afterFog = f * fogColor + (1 - f) * origColor;

雾效系数f的计算方法:z为距离(一般是点的高度)。 
- 线性:f = (dmax - |z|) / (dmax - dmin)。d表示受雾影响的距离。 
- 指数:f = e-d * |z|。d是控制浓度的参数。 
- 指数的平方:f = e-(d - |z|)2。d是控制浓度的参数。

实现

FogWithDepthTexture类:

using UnityEngine;
/// <summary>
/// 雾效
/// </summary>
public class FogWithDepthTexture : PostEffectsBase
{
    private Camera targetCamera;
    public Camera TargetCamera { get { return targetCamera = targetCamera == null ? GetComponent<Camera>() : targetCamera; } }
    private Transform cameraTransform;
    public Transform CameraTransform { get { return cameraTransform = cameraTransform == null ? TargetCamera.transform : cameraTransform; } }
    [Range(0.0f, 3.0f)]
    public float fogDensity = 1.0f;         // 雾的密度
    public Color fogColor = Color.white;    // 雾的颜色
    public float fogStart = 0.0f;           // 起始高度
    public float fogEnd = 2.0f;             // 终止高度
    void OnEnable()
    {
        TargetCamera.depthTextureMode |= DepthTextureMode.Depth;
    }
    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (TargetMaterial != null)
        {
            Matrix4x4 frustumCorners = Matrix4x4.identity;  // 存近裁切平面的四个角
            float fov = TargetCamera.fieldOfView;         // 竖直方向视角范围(角度)
            float near = TargetCamera.nearClipPlane;      // 近平面距离 
            float aspect = TargetCamera.aspect;           // 宽高比
            // 计算四个角对应的向量,存到frustumCorners
            float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
            Vector3 toRight = CameraTransform.right * halfHeight * aspect;
            Vector3 toTop = CameraTransform.up * halfHeight;
            Vector3 topLeft = CameraTransform.forward * near + toTop - toRight;
            float scale = topLeft.magnitude / near;
            topLeft.Normalize();
            topLeft *= scale;
            ...  // 同样方法计算其他三个角
            frustumCorners.SetRow(0, bottomLeft);
            frustumCorners.SetRow(1, bottomRight);
            frustumCorners.SetRow(2, topRight);
            frustumCorners.SetRow(3, topLeft);
            TargetMaterial.SetMatrix("_FrustumCornersRay", frustumCorners);
            TargetMaterial.SetFloat("_FogDensity", fogDensity);
            TargetMaterial.SetColor("_FogColor", fogColor);
            TargetMaterial.SetFloat("_FogStart", fogStart);
            TargetMaterial.SetFloat("_FogEnd", fogEnd);
        }
        Graphics.Blit(src, dest, TargetMaterial);
    }
}

Shader:

Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _FogDensity ("Fog Density", Float) = 1.0
    _FogColor ("Fog Color", Color) = (1, 1, 1, 1)
    _FogStart ("Fog Start", Float) = 0.0
    _FogEnd ("Fog End", Float) = 1.0
}
SubShader {
    ...
    struct v2f {
        float4 pos : SV_POSITION;
        half2 uv : TEXCOORD0;
        half2 uv_depth : TEXCOORD1;             // 深度纹理
        float4 interpolatedRay : TEXCOORD2;     // 插值后的像素向量
    };
    v2f vert(appdata_img v) {
        ...
        // 靠近哪个角选哪个
        int index = 0;
        if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5) {
            index = 0;
        } else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5) {
            index = 1;
        } else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5) {
            index = 2;
        } else {
            index = 3;
        }
        #if UNITY_UV_STARTS_AT_TOP
        if (_MainTex_TexelSize.y < 0)
            index = 3 - index;
        #endif
        o.interpolatedRay = _FrustumCornersRay[index];
        return o;
    }
    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);                    // 限制在0~1
        fixed4 finalColor = tex2D(_MainTex, i.uv);
        finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);
        return finalColor;
    }
    ENDCG
    Pass {
        ZTest Always Cull Off ZWrite Off
        CGPROGRAM  
        #pragma vertex vert  
        #pragma fragment frag  
        ENDCG  
    }
} 
Unity Shader学习笔记系列教程:

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

1个评论

  • Abel 2020-09-18 1楼
    "在顶点着色器中,根据当前位置,选择对应的向量,做插值", 这个插值在哪计算的??着色器代码看起来好像只是赋值