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 OffCGPROGRAM#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.0uniform 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"
}
初步效果图如下:
但以上的效果在一些情况,比如四周都是封闭的还是不错的,但如图所示,其中一边是开放的,从侧面看时就不是很自然了,看不出体的感觉了。这个将在后续现实中再分享出来。
后续体积的实现参考链接
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
