专栏首页U3DUnity Shader 屏幕后效果——全局雾

Unity Shader 屏幕后效果——全局雾

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 }

效果如下:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Unity TextMeshPro 一键生成工具

    https://blog.csdn.net/akof1314/article/details/80868869

    汐夜koshio
  • Unity TextMeshPro替代Text组件创建简体中文字体纹理集

    Unity原生的Text组件有一个毛病,只要文本放大字体放大就会有毛边或锯齿,一个更好的解决方案是用TextMeshPro替代ugui中的Text组件。

    汐夜koshio
  • Unity Shader 屏幕后效果——边缘检测

    https://www.cnblogs.com/koshio0219/p/11131619.html

    汐夜koshio
  • 极值波动策略

    设想得很好,在每次波动的极值点进行操作,用买入和卖出价格两个数组记录买入和卖出价格。这样可以保证每个操作都盈利。

    用户1075292
  • LeetCode Longest Palindromic Substring

    Given a string s, find the longest palindromic substring in s. You may assume th...

  • Dijkstra双栈表达式求值算法

    SuperHeroes
  • R条件语句

    但如果你有一长串 if 语句,那么就要考虑重写了。重写的一种方法是使用 switch() 函数, 它先对第一个参数求值,然后按照名称或位置在后面的参数列表中匹...

    生信编程日常
  • R语言写2048游戏

           2048 是一款益智游戏,只需要用方向键让两两相同的数字碰撞就会诞生一个翻倍的数字,初始数字由 2 或者 4 构成,直到游戏界面全部被填满,游戏结...

    用户1680321
  • 一道简单但易错的C语言面试题

    正确答案是B选项。首先,要注意的一点是这里的if判断条件里用的是=号,而不是==号,这个小陷阱可能会迷惑一些初学C语言的朋友。如果这里用的是==号的话,正确答案...

    正念君
  • Freemarker常用方法

    剑行者

扫码关注云+社区

领取腾讯云代金券