Shader GrabPass应用实例——高度体积雾

高度雾或者垂直雾是体积雾的一重要分支,比如以下场景(真实图片,非特效),在很大场景中都有这样的需求。以下是找到的两个不错的参考实现。


参考一

其中有这篇文章的介绍了一种垂直雾的实现方法,思路、代码写的很详细了,网上有不少转载,这里也就不再说明了。

 https://mp.weixin.qq.com/s/yfGNeyip5ebVIvBj-XBd4Q

http://www.sohu.com/a/231966965_667928

参考二

另外,还有就是以下这个牛B的插件

https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/volumetric-fog-mist-49858


下面是插件的其中一个设置可以设置雾在一个区域内。


我的需求?

通过这张图片引出我的需求,需要实现上面类似的区域垂直雾的效果,而且是可以支持多个区域。当时看到这个插件很开心,本想就是自己找的东西,期待的效果也是一样一样的。可看小半天源码,只能设置一个区域,不支持多个区域,一下子心凉了半截。

最终,无奈之下,只能又开始自己造轮子。思路如下:第一步实现第一参考中垂直雾的效果,再完成噪声和体积的处理。

区域垂直雾实现

第一个参考中使用的是屏幕后处理,由于不好处理区域,所以还是需要把方法改造为GrabPass的方法,通过一个Box定义一个垂直雾的区域,其Shader为ZWrite off,然后获取其_CameraDepthTexture,从而得到其对应点的世界坐标高度,(这里需要特别注意,这个世界坐标不是Box片段的,而是其后面GrabPass中获取的背景点坐标)。以下Shader的代码。

其中与参考1主要是获取屏幕射线的方法不同,参考1的核心方法(第二简化算法),一个特定的ViewPort Position和一个特定的深度值, 是能够唯一确定一个世界坐标的, 这就要求出Camera到屏幕上一点(对应的世界坐标)的一条射线,可以先求出透视矩阵中四个角的射线,再通过插件获取每一点的射线(即Camera到远投影面的一点的线段)。以上是在屏幕后Shader进行处理的。如下图的四个角所示。(为了更好理解本例,还请先看明白第一个参考


所以以上方法,直接拿到本例的区域模型的Shader中是不行的,但核心当然也是要求出Camera到屏幕一点的射线,如下图所示


就是要求出Camera到P'点的射线,P点为区域模型上一点,可以求出其Z深度值,Z'为Camera的远投影面深度,就Shader的_ProjectionParams.z,Camera到P点的距离很好求出,这个就可以求出Camera到P'点的距离。可以参考以下的代码。其余的过程与参考一基本一致了。

Shader "Shader Forge/HeightFogBase1" {
    Properties {
        _MainTexture ("MainTexture", 2D) = "white" {}
        _FogColor ("FogColor", Color) = (0.5,0.5,0.5,1)
        _Start ("Start", Float ) = 0
        _Density ("Density", Float ) = 0
    }
    SubShader {
        Tags {
            "IgnoreProjector"="True"
            "Queue"="Transparent+1"
            "RenderType"="Transparent"
        }
		GrabPass{ }

        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }
             ZWrite Off
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDBASE
            #include "UnityCG.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma only_renderers d3d9 d3d11 glcore gles 
            #pragma target 3.0
            uniform sampler2D _CameraDepthTexture;
			uniform sampler2D _GrabTexture;

			uniform float3 _CameraDir;

            uniform float4x4 frustumCorners;

            //第一个参考方案的获取屏幕射线的方法
            float4 GetCameraRay( float4x4 frustumCorners , float2 vertex1 ){
	            int xx = (int)vertex1.x;
	            int yy = (int)vertex1.y;
	            int z = abs(3 - xx - 3*yy);
	            return frustumCorners[z];
            }

            uniform sampler2D _MainTexture; uniform float4 _MainTexture_ST;
            uniform float _Start;
            uniform float _Density;
            uniform float4 _FogColor;
            struct VertexInput {
                float4 vertex : POSITION;
                float2 texcoord0 : TEXCOORD0;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float4 projPos : TEXCOORD1;
                //float4 raydir: TEXCOORD2;
                float4 posWorld : TEXCOORD3;
            };

            VertexOutput vert (VertexInput v) {
                VertexOutput o = (VertexOutput)0;
                o.uv0 = v.texcoord0;

                o.pos = UnityObjectToClipPos( v.vertex );
                o.projPos = ComputeScreenPos (o.pos);
                o.posWorld = mul(unity_ObjectToWorld, v.vertex);

                float3 viewDirection = normalize( o.posWorld.xyz - _WorldSpaceCameraPos.xyz);

                //这里之前的插件的方式是不对的
//                o.raydir.xyz = viewDirection * _ProjectionParams.z * distance(_WorldSpaceCameraPos.xyz, o.posWorld.xyz) / o.projPos.z;
//                o.raydir.w = v.vertex.z;

                COMPUTE_EYEDEPTH(o.projPos.z);
                return o;
            }

            float4 frag(VertexOutput i) : COLOR {
            	//获取背景点的深度
                float2 sceneUVs = (i.projPos.xy / i.projPos.w);
                float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sceneUVs));

                float3 viewDirection = normalize( i.posWorld.xyz - _WorldSpaceCameraPos.xyz);

                //获取当前区域模型片段点的屏幕射线
                float3 raydir = viewDirection * _ProjectionParams.z * distance(_WorldSpaceCameraPos.xyz, i.posWorld.xyz) / i.projPos.z;

                //获取背景点的世界坐标
                float3 worldPos = (depth * raydir).xyz + _WorldSpaceCameraPos;

                float4 sceneColor = tex2D(_GrabTexture, sceneUVs);
                float3 emissive = lerp(_FogColor.rgb, sceneColor.rgb, saturate(exp(worldPos.y -_Start)*_Density));
                float3 finalColor = emissive;
                //return fixed4(float3(1, worldPos.y, 0).rgb,1);    //用于Debug显示高度值
                return fixed4(finalColor.rgb,1);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
    CustomEditor "ShaderForgeMaterialInspector"
}

初步效果图如下:


但以上的效果在一些情况,比如四周都是封闭的还是不错的,但如图所示,其中一边是开放的,从侧面看时就不是很自然了,看不出体的感觉了。这个将在后续现实中再分享出来。

后续体积的实现参考链接

猜你喜欢

转载自blog.csdn.net/FeiBin2013/article/details/80383235