|
大家好,我是Shawn。如何实现“雾效”?”在Unity中,是有自带的雾效的,在Lighting窗口下,在other Settings就可以找到fog选项了,启用fog就能使用Unity自带的雾效了,但是,需要注意的是,只有在向前渲染下才能实现Unity自带的雾效,Unity有三种模式:Linear,Exp 和 Exp2 分别对应,线性、指数、和指数的平方的增长模式。
雾效因子分别对应:
、 、
E是end、S是start、c是coordinate、d就是density,通过上述式子我们不难发现这是一个关于c的减函数,即距离越远,雾效因子越小。
我们已经知道Unity是如何实现雾效的了,其实就是计算雾效因子,然后进行差值就行了
现在我们试着创建一个自己的雾效。
首先需要添加#pragma multi_compile_fog让Unity为我们生成正确的变体。然后我们还需要
设置一个宏控制雾效的开启。- #if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
- #define APPLY_FOG 1
- #endif
复制代码 在顶点着色器之前,编写一个自定义的函数:- float4 ApplyFog(float4 color , v2f i){
- float viewDistance = length(_WorldSpaceCameraPos - i.worldPos);
- UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
- color.rgb = lerp(unity_FogColor.rgb, color.rgb, saturate(unityFogFactor));
- return color ;
-
- }
复制代码 这个函数计算了顶点到摄像机的距离,并且我们用Unity自带的宏为我们计算雾效因子,由于雾效因子的范围不在0-1之间所以我们用需要限定一下,这个宏可以在UnityCG.cginc里面查看到:- #if defined(FOG_LINEAR)
- // factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
- #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
- #elif defined(FOG_EXP)
- // factor = exp(-density*z)
- #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
- #elif defined(FOG_EXP2)
- // factor = exp(-(density*z)^2)
- #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
- #else
- #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = 0.0
- #endif
复制代码 其实就是Unity判断我们选择的模式,传入c就算雾效因子,unity_FogParams可以在UnityShaderVariables里查看,这里我们直接给出:
unity_FogParams:(density / sqrt(ln(2)), density / ln(2), –1/(end-start), end/(end-start))
在片元着色器中代码就更简单了,只需要调用即可:- fixed4 frag (v2f i) : SV_Target
- {
- // sample the texture
- fixed4 col = tex2D(_MainTex, i.uv);
- // apply fog
- #if APPLY_FOG
- col = col*ApplyFog(col ,i);
- #endif
- return col;
- }
复制代码 返回Unity查看效果,我们没有用光照模型,只输出颜色:
其中一个球是标准材质,用来做对比
在上一个方法中我们使用的c是物体到摄像机的距离,下面我们将基于深度来实现雾效。
我们需要用一个变量来储存深度值,这里我们把顶点的世界坐标改成float4类型,用w储存深度值:- float4 worldPos : TEXCOORD2;
复制代码 在顶点着色器中添加一段代码:- #if APPLY_FOG
- o.worldPos.w = o.vertex.z;
- #endif
复制代码 我们记录了clip space下的深度值,由于平台的差异我们取到的z值的范围也不同,我们使用
宏UNITY_Z_0_FAR_FROM_CLIPSPACE来针对不同平台做不同处理,把深度转换为从近切面到远切面线性增大的值。- float4 ApplyFog(float4 color , v2f i){
- //float viewDistance = length(_WorldSpaceCameraPos - i.worldPos.xyz);
- float viewDistance =UNITY_Z_0_FAR_FROM_CLIPSPACE(i.worldPos.w);
- UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
- color.rgb = lerp(unity_FogColor.rgb, color.rgb, saturate(unityFogFactor));
- return color ;
-
- }
复制代码 其他代码不变,我们得到的效果几乎和上面的一样。
但其实还是差别的,当我们基于深度来实现雾效,我们不需要开根号,运行速度更快了,但是,我们还会发现一些问题,当我们的摄像机旋转时,我们的深度变了,从而影响了雾效,尽管他在逻辑上是不会变化的
源自catlike coding
我们可以查看做一个简单的测试,查看旋转时摄像机的深度图:
我们可以发现虽然是同一个位置,长方体的颜色却有细微的不同
下面是完整代码:(同时包含了基于距离和深度的方法)- Shader "Unlit/TestFog"
- {
- Properties
- {
- _MainTex ("Texture", 2D) = "white" {}
- }
- SubShader
- {
- Tags { "RenderType"="Opaque" }
- LOD 100
- Pass
- {
- //Tags{"LightMode" = "ForwardBase"}
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
- // make fog work
- #pragma multi_compile_fog
- #include "UnityCG.cginc"
- #define FOG_DISTANCE
- #if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
- #if !defined(FOG_DISTANCE)
- #define FOG_DEPTH 1
- #endif
- #define APPLY_FOG 1
-
- #endif
- struct appdata
- {
- float4 vertex : POSITION;
- float2 uv : TEXCOORD0;
- };
- struct v2f
- {
- float2 uv : TEXCOORD0;
- float4 vertex : SV_POSITION;
- float4 worldPos : TEXCOORD2;
-
-
- };
- sampler2D _MainTex;
- float4 _MainTex_ST;
- float4 ApplyFog(float4 color , v2f i){
-
-
- float viewDistance = length(_WorldSpaceCameraPos - i.worldPos.xyz);
- #if FOG_DEPTH
- viewDistance =UNITY_Z_0_FAR_FROM_CLIPSPACE(i.worldPos.w);
- #endif
- UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
- color.rgb = lerp(unity_FogColor.rgb, color.rgb, saturate(unityFogFactor));
- return color ;
-
- }
- v2f vert (appdata v)
- {
- v2f o;
- o.vertex = UnityObjectToClipPos(v.vertex);
- o.uv = TRANSFORM_TEX(v.uv, _MainTex);
- o.worldPos.xyz = mul(unity_ObjectToWorld , v.vertex).xyz;
- #if APPLY_FOG
- o.worldPos.w = o.vertex.z;
- #endif
-
- return o;
- }
- fixed4 frag (v2f i) : SV_Target
- {
- // sample the texture
- fixed4 col = tex2D(_MainTex, i.uv);
- // apply fog
- #if APPLY_FOG
- col = col*ApplyFog(col ,i);
- #endif
- return col;
- }
- ENDCG
- }
- }
- }
复制代码 前文提到:这种方法只能用在向前渲染中,在延迟渲染中我们就要使用屏幕后处理。
我们先写一个最基本的后处理脚本:- [ExecuteInEditMode]
- public class PostScreenEffect : MonoBehaviour
- {
- public Shader shader;
- Material material;
- [ImageEffectOpaque]
- private void OnRenderImage(RenderTexture source, RenderTexture destination)
- {
- if (material == null)
- {
- material= new Material(shader);
- }
- Graphics.Blit(source, destination, material);
- }
复制代码 然后创建一个Imageeffect的shader文件,思路也很简单,就是通过采样深度纹理来获取深度值,从而计算距离。我们要声明_CameraDepthTexture来采样深度纹理,然后再采样他,核心代码如下:- fixed4 frag (v2f i) : SV_Target
- {
- fixed4 col = tex2D(_MainTex, i.uv);
- float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
- depth = Linear01Depth(depth);
- if(depth<1){
- float distance = depth * _ProjectionParams.z-_ProjectionParams.y;
- UNITY_CALC_FOG_FACTOR_RAW(distance);
- unityFogFactor = saturate(unityFogFactor);
-
- col.rgb = lerp(unity_FogColor.rgb,col.rgb,unityFogFactor);
- }
- return col;
- }
复制代码 我们用Linear01Depth函数是为了能够得到一个0-1范围的线性的深度值,你可能已经不止一次听到类似“MVP矩阵的P导致了非线性”,所以我再唠叨一遍,哈哈。然后转换为距离,同样你也可以在UnityShaderVariables里查看_ProjectionParams. 这里z就代表远裁面,y代表近裁面。
我们把深度值限定在小于1的范围内是因为我们不想渲染天空盒。
返回Unity查看效果:
最后,我们再用真正的距离实现屏幕后处理实现雾效:
这里的距离我们可以从摄像机发射一道光线。如果没有没有物体的话,这道光线可以一直延伸最后达到远裁面,如果有物体,我们我们只要记录下它的深度值与远裁面的比,然后乘这个光线向量得到一个新的向量,就是一个简单的相似三角形的知识~最后计算这个向量的模,我们就得到了距离了。
理论上来说要为每个像素发射一次射线,但是,我们这里我们只计算远裁面的四个角的射线,剩下的就交给差值去做吧。
幸运的是,我们不用自己手动计算这四个射线,Unity有给定函数方便了我们的计算。
点击这里了解这个函数。
我们在camera的脚本中添加一些代码:- Camera cam = null;
-
- Vector3[] frustumCorners;
- Vector4[] vectorArray;
- /****下面代码添加至OnRenderImage函数中****/
- if (material == null)
- {
- material= new Material(shader);
- cam = GetComponent<Camera>();
- frustumCorners = new Vector3[4];
- }
- cam.CalculateFrustumCorners(new Rect(0, 0, 1, 1), cam.farClipPlane, cam.stereoActiveEye, frustumCorners);
- vectorArray[0] = frustumCorners[0];
- vectorArray[1] = frustumCorners[3];
- vectorArray[2] = frustumCorners[1];
- vectorArray[3] = frustumCorners[2];
- material.SetVectorArray(&#34;_FrustumCorners&#34;, vectorArray);
复制代码 在calculateFrustumCorners函数里我们传入的是Vector3,而我们SetVectorArray需要传入Vector4 ,所以我们变化一下,这样我们的数值就能正确被传入了 ;
在我们的shader文件下我们声明一个宏,来控制是否启用distance的算法。接着要在顶点结构体声明ray向量:- #ifdef FOG_DISTANCE
- float3 ray : TEXCOORD1;
- #endif
复制代码 最后我们修改片元着色器中的核心语句:- if(depth<1){
- float distance = depth * _ProjectionParams.z-_ProjectionParams.y;
- #ifdef FOG_DISTANCE
- distance = length(depth*i.ray);
- #endif
- UNITY_CALC_FOG_FACTOR_RAW(distance);
- unityFogFactor = saturate(unityFogFactor);
- col.rgb = lerp(unity_FogColor.rgb,col.rgb,unityFogFactor);
- }
复制代码 效果:
以上就是雾效的所有内容了
最后的最后(真的是最后了)我们来实现一个类似firewatch的效果:
代码如下:- Shader &#34;Custom/GradientColor&#34;
- {
- Properties
- {
- _MainTex (&#34;Texture&#34;, 2D) = &#34;white&#34; {}
- _FogAmount(&#34;Fog amount&#34;, float) = 1
- _ColorRamp(&#34;Color ramp&#34;, 2D) = &#34;white&#34; {}
- _FogIntensity(&#34;Fog intensity&#34;, float) = 1
- }
- SubShader
- {
-
- // No culling or depth
- Cull Off ZWrite Off ZTest Always
-
- Pass
- {
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
- #pragma multi_compile_fog
- #include &#34;UnityCG.cginc&#34;
-
-
- struct appdata
- {
- float4 vertex : POSITION;
- float2 uv : TEXCOORD0;
- };
-
-
- struct v2f
- {
- float2 uv : TEXCOORD0;
- float4 vertex : SV_POSITION;
- float4 scrPos : TEXCOORD1;
- };
-
- v2f vert (appdata v)
- {
- v2f o;
- o.vertex = UnityObjectToClipPos(v.vertex);
- o.uv = v.uv;
- o.scrPos = ComputeScreenPos(o.vertex);
- return o;
- }
-
- sampler2D _MainTex;
- sampler2D _CameraDepthTexture;
- sampler2D _ColorRamp;
- float _FogAmount;
- float _FogIntensity;
-
- fixed4 frag (v2f i) : SV_Target
- {
- fixed4 orCol = tex2D(_MainTex, i.uv);
- float depthValue = Linear01Depth (tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.scrPos)));
- //float depthValue = Linear01Depth (SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv));
- float distance = depthValue*_ProjectionParams.z-_ProjectionParams.y;
- UNITY_CALC_FOG_FACTOR_RAW(distance);
- unityFogFactor = saturate(unityFogFactor);
-
- float depthValueMul = depthValue * _FogAmount;
- fixed4 fogCol = tex2D(_ColorRamp, (float2(depthValueMul, 0)));
- if(depthValue<1){
- #if !defined(FOG_LINEAR) && !defined(FOG_EXP) && !defined(FOG_EXP2)
-
- orCol = lerp(orCol, fogCol, fogCol.a * _FogIntensity) ;
- #else
- unityFogFactor =1-unityFogFactor;
- orCol = lerp(orCol,fogCol,unityFogFactor*_FogIntensity);
- #endif
-
- }
-
- return orCol;
-
- }
- ENDCG
- }
- }
- }
复制代码 我们通过深度对一张梯度图采样,这样不同的深度就能显示不同的颜色,如果你觉的这样很不自然的话,当然也可以使用雾效因子来对他进行采样。
如果你的渲染路径是向前渲染的话,你就需要在脚本中加入Camera.main.depthTextureMode = DepthTextureMode.Depth;
这样你就可以访问深度图了
我们找一张梯度图看看效果:
也可以这样:
当然如果你找的颜色是彩虹色的话:
Enjoy! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|