Unity内置的雾效需要在每个shader中分别编写,造成了极大的不便。这里利用屏幕后处理产生可单独控制且*度更高的雾效。
屏幕后雾效的本质在于,根据深度纹理重构每个像素在世界空间中的位置,利用得到的坐标计算出一个雾效系数,最终利用雾效系数与雾的颜色相乘并与原始颜色进行插值运算得出最终效果。
float3 afterFog=f*fogColor+(1-f)*origColor;
上面的插值运算中f代表雾效系数,它有多种计算方法:
1.线性计算:
f=(dmax-Abs(z))/dmax-dmin;
其中dmax和dmin分别代表受雾影响的最大和最小距离,z为给定的距离位置(像素位置)
2.指数计算:
f=pow(e,-d*Abs(z));
其中d控制雾的浓度,e为数学常量
3.二次指数:
f=pow(e,-pow(d*z,2));
为了更方便的对参数进行控制,我们需要求得每个像素在世界空间中的位置,常规实现方法有两种:
1.构建像素的NDC坐标然后用VP矩阵的逆矩阵反向推导
2.利用向量的基本运算求得
因为第一种方法需要在片元着色器中进行矩阵乘法,若想得到性能更优的实现方式,考虑使用第二种。
向量的基本运算方式如下:
float4 worldPos=_WorldSpaceCameraPos+linearDepth*interpolatedRay;
上面的运算中,_WorldSpaceCameraPos表示摄像机在世界空间中的位置,linearDepth*interpolatedRay是为了求得世界空间下的像素相对于摄像机的偏移量,根据向量的加法,就可以求出这个向量在世界空间中的位置。
linearDepth线性深度值可以利用摄像机的深度纹理来求,关键就是求一个插值射线interpolatedRay。
分析interpolatedRay的含义可以知道,它主要表示该像素到摄像机的方向向量,可以由顶点着色器的各个顶点输出并插值得到。
基于这一点,在顶点着色器中对屏幕四个顶点(左上,左下,右上,右下)的向量进行传值,利用外部脚本提前计算好四个向量即可,这样不需要在Shader中进行繁杂的数学运算。
参数控制脚本,同时计算顶点相对于摄像机的方向向量。此脚本挂载在摄像机上:
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 5 public class FogWithDepthTexCtrl : ScreenEffectBase 6 { 7 private const string _FrustumCornersRay = "_FrustumCornersRay"; 8 9 private const string _FogDensity = "_FogDensity"; 10 private const string _FogColor = "_FogColor"; 11 private const string _FogUnderStart = "_FogUnderStart"; 12 private const string _FogTopEnd = "_FogTopEnd"; 13 14 private Camera myCamera; 15 public Camera MyCamera 16 { 17 get 18 { 19 if (myCamera == null) 20 myCamera = GetComponent<Camera>(); 21 return myCamera; 22 } 23 } 24 25 private Transform myCameraTran; 26 public Transform MyCameraTran 27 { 28 get 29 { 30 if (myCameraTran == null) 31 myCameraTran = MyCamera.transform; 32 return myCameraTran; 33 } 34 } 35 36 [Range(0, 3)] 37 public float fogDensity = 1.0f;//控制雾的浓度 38 public Color fogColor = Color.white; 39 public float fogUnderStart = 0.0f;//雾起始高度 40 public float fogTopEnd = 2.0f;//雾结束高度 41 42 private void OnEnable() 43 { 44 MyCamera.depthTextureMode |= DepthTextureMode.Depth; 45 } 46 47 private void OnDisable() 48 { 49 MyCamera.depthTextureMode &= ~DepthTextureMode.Depth; 50 } 51 52 private void OnRenderImage(RenderTexture source, RenderTexture destination) 53 { 54 if (Material != null) 55 { 56 //需要传递的四个角相对于摄像机的方向向量,这里用矩阵的每一行来表示 57 Matrix4x4 frustumCorners = Matrix4x4.identity; 58 59 float fov = MyCamera.fieldOfView; 60 float near = MyCamera.nearClipPlane; 61 float aspect = MyCamera.aspect; 62 63 //计算近裁剪平面三个标准方向 64 float halfHeight = near * Mathf.Tan(fov * .5f * Mathf.Deg2Rad); 65 Vector3 toTop = halfHeight * MyCameraTran.up; 66 Vector3 toRight = halfHeight * MyCameraTran.right * aspect; 67 Vector3 toForward = near * MyCameraTran.forward; 68 69 //用三个标准方向重构四个顶点关于摄像机的向量 70 Vector3 topRight = toForward + toRight + toTop; 71 topRight /= near; 72 73 Vector3 topLeft = toForward - toRight + toTop; 74 topLeft /= near; 75 76 Vector3 bottomRight = toForward + toRight - toTop; 77 bottomRight /= near; 78 79 Vector3 bottomLeft = toForward - toRight - toTop; 80 bottomLeft /= near; 81 82 //用矩阵的每一行来存储这些向量,这里的顺利要与之后解析的顺序对应 83 frustumCorners.SetRow(0, topLeft); 84 frustumCorners.SetRow(1, topRight); 85 frustumCorners.SetRow(2, bottomLeft); 86 frustumCorners.SetRow(3, bottomRight); 87 88 //传递向量矩阵和对应的参数 89 Material.SetMatrix(_FrustumCornersRay, frustumCorners); 90 91 Material.SetFloat(_FogDensity, fogDensity); 92 Material.SetColor(_FogColor, fogColor); 93 Material.SetFloat(_FogUnderStart, fogUnderStart); 94 Material.SetFloat(_FogTopEnd, fogTopEnd); 95 96 Graphics.Blit(source, destination, Material); 97 } 98 else 99 Graphics.Blit(source, destination); 100 } 101 }
基类见:
https://www.cnblogs.com/koshio0219/p/11131619.html
Shader脚本:
1 Shader "MyUnlit/FogWithDepthTex" 2 { 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 } 7 SubShader 8 { 9 Pass 10 { 11 CGPROGRAM 12 #pragma vertex vert 13 #pragma fragment frag 14 15 #include "UnityCG.cginc" 16 17 //对应四个顶点的射线矩阵 18 float4x4 _FrustumCornersRay; 19 20 sampler2D _MainTex; 21 half4 _MainTex_TexelSize; 22 sampler2D _CameraDepthTexture; 23 half _FogDensity; 24 fixed4 _FogColor; 25 float _FogUnderStart; 26 float _FogTopEnd; 27 28 struct appdata 29 { 30 float4 vertex : POSITION; 31 float2 uv : TEXCOORD0; 32 }; 33 34 struct v2f 35 { 36 half4 uv : TEXCOORD0; 37 float4 vertex : SV_POSITION; 38 //顶点着色器输出的插值射线 39 float4 interpolatedRay:TEXCOORD1; 40 }; 41 42 v2f vert (appdata v) 43 { 44 v2f o; 45 o.vertex = UnityObjectToClipPos(v.vertex); 46 o.uv.xy = v.uv; 47 o.uv.zw=v.uv;//zw存深度纹理 48 49 //对插值射线的索引进行解析,判定该顶点是四个角中的哪一个 50 int idx=0; 51 if(v.uv.x>.5f&&v.uv.y>.5f) 52 idx=1; 53 else if(v.uv.x<.5f&&v.uv.y<.5f) 54 idx=2; 55 else if(v.uv.x>.5f&&v.uv.y<.5f) 56 idx=3; 57 58 //主纹理外的纹理要进行平台差异化处理,同时对顶点的索引也需要进行处理(左上对左下,右上对右下) 59 #if UNITY_UV_STARTS_AT_TOP 60 if(_MainTex_TexelSize.y<0){ 61 o.uv.w=1-o.uv.w; 62 idx=idx<2?idx+2:idx-2; 63 } 64 #endif 65 66 //按照解析的索引值得到需要传递的插值射线 67 o.interpolatedRay=_FrustumCornersRay[idx]; 68 69 return o; 70 } 71 72 fixed4 frag (v2f i) : SV_Target 73 { 74 //计算像素在世界空间中的位置 75 float linearDepth=LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv.zw)); 76 float3 worldPos=_WorldSpaceCameraPos+linearDepth*i.interpolatedRay.xyz; 77 78 //计算雾效系数,这里主要用的关于世界空间高度的线性雾计算 79 float fogDensity=(_FogTopEnd-worldPos.y)/(_FogTopEnd-_FogUnderStart); 80 fogDensity=saturate(fogDensity*_FogDensity); 81 82 //插值得到最终雾效 83 fixed4 col = tex2D(_MainTex, i.uv); 84 col.rgb=lerp(col.rgb,_FogColor.rgb,fogDensity); 85 86 return col; 87 } 88 ENDCG 89 } 90 } 91 }
效果如下: