体积雾

普通雾的实现是在每个着色器(例如unity 的stanard着色器)中都添加一段雾的代码,然后根据顶点离摄像机的距离来覆盖一层颜色达到一种雾的效果。而体积雾是一个有体积的雾,其本身是一个对象,在场景的中空区域也可以有雾的效果。
两者主要区别如下:
1.普通雾需要更改所有的着色器,而体积雾只用为体积雾对象写一个着色器。
2.普通雾可以看成是一层纹理贴在场景的物体上,而体积雾本身具有体积,在普通雾覆盖不到的地方(比如没有任何物体的空场景),也可以添加雾的效果。

实现思路

用来生成体积雾的模型一般都是简单的几何体,如以球体为例。
我们这里的实现思路大致就是计算模型顶点到模型原点的位置,距离越远的雾越淡,距离越近的雾越浓。
但是不能直接在模型空间下计算,因为球体上的顶点到球体原点的距离全都是一样的。我们需要将球体压扁成圆形,然后再进行计算,此时每个顶点所对应的二维投影到原点的距离就不同了。
因此我们选择在裁剪空间下计算,裁剪空间下的坐标很容易可以转化为视口空间下的坐标,而视口空间的范围是正好是从零到一,还方便我们进行插值。

代码实现

雾是一个半透明效果,首先要关闭深度写入,然后打开混合。

ZWrite Off
Blend One OneMinusSrcColor

首先在顶点着色器中,计算原点和当前顶点在裁剪空间下的位置
这个原点虽然对每个顶点都是一样的,但是因为着色器变量存储的一些限制,还是只能对每个顶点都计算一次。

float4 center = UnityObjectToClipPos(float4(0, 0, 0, 1));
float4 vertex = UnityObjectToClipPos(v.vertex);

接着在片元着色器中,将裁剪空间下的坐标转换为视口空间坐标。
只要将xy分量除以w分量,就可以得到顶点在视口空间上的坐标(不知道是为什么的话可以去复习一遍Unity坐标空间的变换过程)

float2 center = i.center.xy / i.center.w;
float2 vertex = i.vertex.xy / i.vertex.w;

这个视口空间坐标是一个[0,0]到[1,1]的坐标,只要再乘上屏幕的分辨率就能得到真正的屏幕坐标,但是我们这里不需要变换到屏幕坐标,视口空间坐标反而更方便计算。
使用可以就计算出了当前顶点到原点的距离

length(vertex - center)

接着用1减去这个值,因为我们是希望距离越大雾越淡。而至于为什么用1来减,因为视口空间里的距离都小于1,这就是用视口空间的好处。

float c = 1 - length(vertex - center)

此时是线性雾,为了雾的美观,我们将其转换为幂次的雾,经过测试后,发现四次幂是一个比较好的值。

c = pow(c, 4);

最后再根据我们设置的强度值再进行一次插值

c = lerp(0, Intensity, c);

然后我们创建一个球体给他添加这个着色器,就完成了简易的体积雾效果。

最后效果如下

unity 2D游戏迷雾 unity3d雾教程_opengl

和其他物体混合的效果如下

unity 2D游戏迷雾 unity3d雾教程_opengl_02

完整代码

Shader "LX/VolumeFog"
{
    Properties
    {
        Intensity("Intensity",range(0,30))=1
    }
    SubShader
    {
        Tags
        {
            "Queue"="Geometry+1000"
        }
        pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }
            Blend One OneMinusSrcColor
            ZWrite Off
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f
            {
                float4 pos : POSITION;
                float4 center:TEXCOORD0;
                float4 vertex:TEXCOORD1;
            };

            sampler2D MainTex;
            float Intensity;

            v2f vert(appdata_full v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                float4 center = UnityObjectToClipPos(float4(0, 0, 0, 1));
                float4 vertex = UnityObjectToClipPos(v.vertex);
                o.vertex = vertex;
                o.center = center;

                return o;
            }


            float4 frag(v2f i) : COLOR
            {
                float2 center = i.center.xy / i.center.w;
                float2 vertex = i.vertex.xy / i.vertex.w;
                float c = 1 - length(vertex - center);
                c = pow(c, 4);
                c = lerp(0, Intensity, c);
                return fixed4(c, c, c, 0);
            }
            ENDCG
        }
    }
    FallBack Off
}