unity 2d摄像机类型_【浅入浅出】Unity 雾效

大家好,我是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)

其实就是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查看效果,我们没有用光照模型,只输出颜色:

v2-75403ba57d7267ba6817b4e4edc6e783_b.jpg
其中一个球是标准材质,用来做对比

在上一个方法中我们使用的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 ;
			
			}

其他代码不变,我们得到的效果几乎和上面的一样。

v2-4dac30523c248bf7e9e30d9ed9b853e6_b.jpg

但其实还是差别的,当我们基于深度来实现雾效,我们不需要开根号,运行速度更快了,但是,我们还会发现一些问题,当我们的摄像机旋转时,我们的深度变了,从而影响了雾效,尽管他在逻辑上是不会变化的

v2-5f671d49b28a79fda3bcd11001cac971_b.jpg
源自catlike coding

我们可以查看做一个简单的测试,查看旋转时摄像机的深度图:

v2-8005f926feeaaba0110e7f41f406232d_b.jpg

v2-b8dc74c6a8259ec0e27fe7928aa4dacb_b.jpg

我们可以发现虽然是同一个位置,长方体的颜色却有细微的不同

下面是完整代码:(同时包含了基于距离和深度的方法)

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查看效果:

v2-961eaa26567f182f192f46dcf2413187_b.jpg

最后,我们再用真正的距离实现屏幕后处理实现雾效:

这里的距离我们可以从摄像机发射一道光线。如果没有没有物体的话,这道光线可以一直延伸最后达到远裁面,如果有物体,我们我们只要记录下它的深度值与远裁面的比,然后乘这个光线向量得到一个新的向量,就是一个简单的相似三角形的知识~最后计算这个向量的模,我们就得到了距离了。

理论上来说要为每个像素发射一次射线,但是,我们这里我们只计算远裁面的四个角的射线,剩下的就交给差值去做吧。

幸运的是,我们不用自己手动计算这四个射线,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("_FrustumCorners", vectorArray);

在calculateFrustumCorners函数里我们传入的是Vector3,而我们SetVectorArray需要传入Vector4 ,所以我们变化一下,这样我们的数值就能正确被传入了 ;

在我们的shader文件下我们声明一个宏,来控制是否启用distance的算法。

#define FOG_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);
         	}

效果:

v2-6fe3800bdb554cc3d76c4562c2f9c98c_b.jpg

以上就是雾效的所有内容了

最后的最后(真的是最后了)我们来实现一个类似firewatch的效果:

v2-da0b697ef9c90d7dfa2d35d1bf71c5cf_b.jpg

代码如下:

Shader "Custom/GradientColor"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _FogAmount("Fog amount", float) = 1
        _ColorRamp("Color ramp", 2D) = "white" {}
        _FogIntensity("Fog intensity", 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 "UnityCG.cginc"

			
 
            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;

这样你就可以访问深度图了

我们找一张梯度图看看效果:

v2-8863ea970bb1e5babc7b70d82297c363_b.jpg

v2-2c0a2a0ca95bbf574056a354d93c635d_b.jpg

也可以这样:

v2-82e15de2b5c310a99f2f703d87198ad4_b.jpg

当然如果你找的颜色是彩虹色的话:

v2-bd3f4dda37849bd0a6dfad87a662b281_b.jpg

Enjoy!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值