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.通過向量的基本運算求得
方法1需要在片元着色器中進行矩陣乘法,若想得到性能更優的實現方式,考慮使用方法2。
向量的基本運算方式如下:
float4 worldPos=_WorldSpaceCameraPos+linearDepth*interpolatedRay;
_WorldSpaceCameraPos表示攝像機在世界空間中的位置,linearDepth*interpolatedRay是為了求得世界空間下的像素相對於攝像機的偏移量。根據向量的加法,就可以求出該像素在世界空間中的位置。
linearDepth線性深度值可以利用攝像機的深度紋理來求,關鍵在於求一個插值射線interpolatedRay。
分析interpolatedRay的含義可以知道,它主要表示該像素到攝像機的方向向量,可以由頂點着色器的各個頂點輸出並插值得到。
基於這一點,可以直接在C#腳本中計算出屏幕四個頂點(左上,左下,右上,右下)的向量,傳值給頂點着色器即可,這樣避免在Shader中進行繁雜的數學運算。
參數控制腳本,同時計算頂點相對於攝像機的方向向量。此腳本掛載在攝像機上:
1 using UnityEngine; 2 3 public class FogWithDepthTexCtrl : ScreenEffectBase 4 { 5 private const string _FrustumCornersRay = "_FrustumCornersRay"; 6 7 private const string _FogDensity = "_FogDensity"; 8 private const string _FogColor = "_FogColor"; 9 private const string _FogUnderStart = "_FogUnderStart"; 10 private const string _FogTopEnd = "_FogTopEnd"; 11 12 private Camera myCamera; 13 public Camera MyCamera 14 { 15 get 16 { 17 if (myCamera == null) 18 myCamera = GetComponent<Camera>(); 19 return myCamera; 20 } 21 } 22 23 private Transform myCameraTran; 24 public Transform MyCameraTran 25 { 26 get 27 { 28 if (myCameraTran == null) 29 myCameraTran = MyCamera.transform; 30 return myCameraTran; 31 } 32 } 33 34 [Range(0, 3)] 35 public float fogDensity = 1.0f;//控制霧的濃度 36 public Color fogColor = Color.white; 37 public float fogUnderStart = 0.0f;//霧起始高度 38 public float fogTopEnd = 2.0f;//霧結束高度 39 40 private void OnEnable() 41 { 42 MyCamera.depthTextureMode |= DepthTextureMode.Depth; 43 } 44 45 private void OnDisable() 46 { 47 MyCamera.depthTextureMode &= ~DepthTextureMode.Depth; 48 } 49 50 private void OnRenderImage(RenderTexture source, RenderTexture destination) 51 { 52 if (Material != null) 53 { 54 //需要傳遞的四個角相對於攝像機的方向向量,這里用矩陣的每一行來表示 55 Matrix4x4 frustumCorners = Matrix4x4.identity; 56 57 float fov = MyCamera.fieldOfView; 58 float near = MyCamera.nearClipPlane; 59 float aspect = MyCamera.aspect; 60 61 //計算近裁剪平面三個標准方向 62 float halfHeight = near * Mathf.Tan(fov * .5f * Mathf.Deg2Rad); 63 Vector3 toTop = halfHeight * MyCameraTran.up; 64 Vector3 toRight = halfHeight * MyCameraTran.right * aspect; 65 Vector3 toForward = near * MyCameraTran.forward; 66 67 //用三個標准方向重構四個頂點關於攝像機的向量 68 Vector3 topRight = toForward + toRight + toTop; 69 topRight /= near; 70 71 Vector3 topLeft = toForward - toRight + toTop; 72 topLeft /= near; 73 74 Vector3 bottomRight = toForward + toRight - toTop; 75 bottomRight /= near; 76 77 Vector3 bottomLeft = toForward - toRight - toTop; 78 bottomLeft /= near; 79 80 //用矩陣的每一行來存儲這些向量,這里的順序要與之后解析的順序對應 81 frustumCorners.SetRow(0, topLeft); 82 frustumCorners.SetRow(1, topRight); 83 frustumCorners.SetRow(2, bottomLeft); 84 frustumCorners.SetRow(3, bottomRight); 85 86 //傳遞向量矩陣和對應的參數 87 Material.SetMatrix(_FrustumCornersRay, frustumCorners); 88 89 Material.SetFloat(_FogDensity, fogDensity); 90 Material.SetColor(_FogColor, fogColor); 91 Material.SetFloat(_FogUnderStart, fogUnderStart); 92 Material.SetFloat(_FogTopEnd, fogTopEnd); 93 94 Graphics.Blit(source, destination, Material); 95 } 96 else 97 Graphics.Blit(source, destination); 98 } 99 }
基類見:
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 ZTest Always Cull Off ZWrite Off 12 13 CGPROGRAM 14 #pragma vertex vert 15 #pragma fragment frag 16 17 #include "UnityCG.cginc" 18 19 //對應四個頂點的射線矩陣 20 float4x4 _FrustumCornersRay; 21 22 sampler2D _MainTex; 23 half4 _MainTex_TexelSize; 24 sampler2D _CameraDepthTexture; 25 half _FogDensity; 26 fixed4 _FogColor; 27 float _FogUnderStart; 28 float _FogTopEnd; 29 30 struct appdata 31 { 32 float4 vertex : POSITION; 33 float2 uv : TEXCOORD0; 34 }; 35 36 struct v2f 37 { 38 half4 uv : TEXCOORD0; 39 float4 vertex : SV_POSITION; 40 //頂點着色器輸出的插值射線 41 float4 interpolatedRay:TEXCOORD1; 42 }; 43 44 v2f vert (appdata v) 45 { 46 v2f o; 47 o.vertex = UnityObjectToClipPos(v.vertex); 48 o.uv.xy = v.uv; 49 o.uv.zw=v.uv;//zw存深度紋理 50 51 //對插值射線的索引進行解析,判定該頂點是四個角中的哪一個 52 int idx=0; 53 if(v.uv.x>.5f&&v.uv.y>.5f) 54 idx=1; 55 else if(v.uv.x<.5f&&v.uv.y<.5f) 56 idx=2; 57 else if(v.uv.x>.5f&&v.uv.y<.5f) 58 idx=3; 59 60 //主紋理外的紋理要進行平台差異化處理,同時對頂點的索引也需要進行處理(左上對左下,右上對右下) 61 #if UNITY_UV_STARTS_AT_TOP 62 if(_MainTex_TexelSize.y<0){ 63 o.uv.w=1-o.uv.w; 64 idx=idx<2?idx+2:idx-2; 65 } 66 #endif 67 68 //按照解析的索引值得到需要傳遞的插值射線 69 o.interpolatedRay=_FrustumCornersRay[idx]; 70 71 return o; 72 } 73 74 fixed4 frag (v2f i) : SV_Target 75 { 76 //計算像素在世界空間中的位置 77 float linearDepth=LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv.zw)); 78 float3 worldPos=_WorldSpaceCameraPos+linearDepth*i.interpolatedRay.xyz; 79 80 //計算霧效系數,這里主要用的關於世界空間高度的線性霧計算 81 float fogDensity=(_FogTopEnd-worldPos.y)/(_FogTopEnd-_FogUnderStart); 82 fogDensity=saturate(fogDensity*_FogDensity); 83 84 //插值得到最終霧效 85 fixed4 col = tex2D(_MainTex, i.uv); 86 col.rgb=lerp(col.rgb,_FogColor.rgb,fogDensity); 87 88 return col; 89 } 90 ENDCG 91 } 92 } 93 }
效果如下: