【转载】Unity Shader Shadow(物体接收阴影与产生阴影)

1,098 阅读7分钟

原文链接

版权声明:本文为CSDN博主「Unity小林」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:blog.csdn.net/weixin_4383…

正文

光照衰减

采样内置纹理得到光照衰减

Unity 在内部使用一张名为 _LightTexture0 的纹理来计算光源衰减。我们通常只关心 _LightTexture0 对角线上的纹理颜色值,这些值表明了在 光源空间 中不同位置的点的衰减值。

例如 (0,0) 点表明了与 光源位置重合 的点的衰减值,而 (1,1) 点表明了在 光源空间 中所关心的 距离最远 的点的衰减。

float3 lightCoord = mul(_LightMatrix0,float4(i.worldPos,1)).xyz;
fixed atten = tex2D(_LightTexture0,dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

现将世界坐标与 _LightMatrix0 相乘得到在 光源空间 中的位置,用 光源空间 中顶点距离的平方来对纹理采样,然后,使用宏 UINITY_ATTEN_CHANNEL 来得到衰减纹理中的衰减值所在的分量,以得到最终的衰减值。

数学公式计算光照衰减

float distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
atten = 1.0/distance;

Unity 中的阴影贴图

Shadow Map

它会首先把摄像机位置放在与 光源重合 的位置上,那么场景中该光源的阴影区域就是摄像机看不到的地方。

Screenspace Shadow Map

Unity 首先会通过调用 LightModeShadowCaster 的 Pass 来得到(光源)投射阴影 是 光源ShadowMap 纹理以及 摄像机Depth 纹理。然后,根据 光源ShadowMap 纹理和 摄像机Depth 纹理来得到 屏幕空间阴影图。如果 摄像机Depth 纹理中记录的表面深度 大于转换光源ShadowMap 纹理中的深度值,就说明该表面 虽然是可见的,但是却处于该光源的阴影中。通过这样的方式,阴影图 就包含了 屏幕空间 中所有阴影的区域。如果我们想要一个物体 接收来自其他物体 的阴影,只需要在 Shader 中对该 阴影图 进行 采样

接收阴影+投射阴影

一个物体接收来自其他物体的阴影,以及它向其他物体投射阴影是两个过程

  1. 如果我们想要 一个物体接收来自其他物体的阴影,就必须在 Shader 中对 ShadowMap 纹理(包括屏幕空间的 阴影图)进行 采样,把采样结果和最后的光照结果 相乘 来产生阴影效果。
  2. 如果我们想要 一个物体向其他物体投射阴影,就必须把该物体 加入光源ShadowMap 纹理的计算中,从而让其他物体在对 ShadowMap 纹理采样时可以得到该物体的相关信息。在 Unity 中,这个过程通过为该物体执行 LightModeShadowCaster 的 Pass 来实现的。如果使用了 屏幕空间的投射映射技术,Unity 还会使用这个 Pass 产生一张 摄像机的深度纹理
  • 借用一张 URP 的图 ShadowCaster.png

实例

接收两层阴影

效果图

image.png

主要代码在 第一个 Pass 中

代码如下

Shader "MyShader/shadow"
{
        Properties
	{
		_Diffuse("diffuse",Color) = (1,1,1,1)
		_Specular("specular",Color) = (1,1,1,1)
		_Gloss("gloss",Range(1,256)) = 20
	}
	SubShader
	{
		Tags { "RenderType" = "Opaque" }
		LOD 100

		Pass//ForwardBase
		{
                    Tags{"LightMode"="ForwardBase"}
                    CGPROGRAM
                    #pragma multi_compile_fwdbase
                    #pragma vertex vert
                    #pragma fragment frag

                    #include "UnityCG.cginc"
                    #include "Lighting.cginc"
                    #include "AutoLight.cginc"

                    fixed4 _Diffuse;
                    fixed4 _Specular;
                    float _Gloss;

                    struct appdata
                    {
                        float4 vertex : POSITION;
                                        float3 normal:NORMAL;
                    };

                    struct v2f
                    {
                        float4 pos : SV_POSITION;
                                        float3 worldNormal:TEXCOORD0;
                                        float3 worldPos:TEXCOORD1;
                                        float3 vertexLight : TEXCOORD2;
                                        SHADOW_COORDS(3)//仅仅是阴影
                    };

                    v2f vert (appdata v)
                    {
                        v2f o;
                        o.pos = UnityObjectToClipPos(v.vertex);
                        o.worldNormal = UnityObjectToWorldNormal(v.normal);
                        o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

        #ifdef LIGHTMAP_OFF
                        float3 shLight = ShadeSH9(float4(v.normal, 1.0));
                        o.vertexLight = shLight;
        #ifdef VERTEXLIGHT_ON
                        float3 vertexLight = Shade4PointLights(
                                                unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
                                                unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
                                                unity_4LightAtten0, o.worldPos, o.worldNormal
                        );
                        o.vertexLight += vertexLight;
        #endif
        #endif
                        TRANSFER_SHADOW(o);//仅仅是阴影
                        return o;
                    }

                    fixed4 frag (v2f i) : SV_Target
                    {
                        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                        float3 worldNormal = normalize(i.worldNormal);
                        float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                        float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);

                        fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal, worldLightDir));

                        fixed3 halfDir = normalize(viewDir + worldLightDir);
                        fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(worldNormal, halfDir)), _Gloss);

                        //fixed shadow = SHADOW_ATTENUATION(i);
                        //return fixed4(ambient + (diffuse + specular)*shadow + i.vertexLight, 1);

                        //这个函数计算包含了光照衰减以及阴影,因为ForwardBase逐像素光源一般是方向光,衰减为1,atten在这里实际是阴影值
                        UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                        return fixed4(ambient + (diffuse + specular)*atten +i.vertexLight, 1);
                    }
                    ENDCG
                }

		Pass//ForwardAdd
		{
			Tags{"LightMode"="ForwardAdd"}
			Blend One One

			CGPROGRAM
			#pragma multi_compile_fwdadd_fullshadows
			#pragma vertex vert
			#pragma fragment frag
				 
			#include"UnityCG.cginc"
			#include"Lighting.cginc"
			#include"AutoLight.cginc"

			float4 _Diffuse;
			float4 _Specular;
			float _Gloss;

			struct a2v
			{
				float4 vertex:POSITION;
				float3 normal:NORMAL;
			};

			struct v2f
			{
				float4 pos:SV_POSITION;
				float3 worldPos:TEXCOORD0;
				float3 worldNormal:TEXCOORD1;
				LIGHTING_COORDS(2, 3)//包含光照衰减以及阴影
			};

			v2f vert(a2v v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				TRANSFER_VERTEX_TO_FRAGMENT(o);//包含光照衰减以及阴影
				return o;
			}

			fixed4 frag(v2f i):SV_Target
			{
				float3 worldNormal = normalize(i.worldNormal);
				float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

				fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal, worldLightDir));

				float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				float3 halfDir = normalize(viewDir + worldLightDir);
				fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(worldNormal, halfDir)), _Gloss);
				//fixed atten = LIGHT_ATTENUATION(i);
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//包含光照衰减以及阴影
				return fixed4((diffuse + specular)*atten, 1);
			}
			ENDCG
		}

		Pass//产生阴影的通道(物体透明也产生阴影)
		{
			Tags { "LightMode" = "ShadowCaster" }

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0
			#pragma multi_compile_shadowcaster
			#pragma multi_compile_instancing // allow instanced shadow pass for most of the shaders
			#include "UnityCG.cginc"

			struct v2f {
				V2F_SHADOW_CASTER;
				UNITY_VERTEX_OUTPUT_STEREO
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				UNITY_SETUP_INSTANCE_ID(v);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
				TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
				return o;
			}

			float4 frag(v2f i) : SV_Target
			{
				SHADOW_CASTER_FRAGMENT(i)
			}
			ENDCG
		}
    }
}


如果没能接收两层阴影,Editor–>Project Settings–>Player, 在 Other Settings 中找到 Color Space,将 Gamma 改为 Linear 就可以了。

半透半不透物体产生阴影

效果图

image.png

代码如下

Shader "MyShader/shadow2"
{
	Properties
	{
		_Cutoff("cutoff",Range(0,1))=0.5//控制透明
		_MainTex("main texture",2D)="white"{}
		_Color("diffuse",Color) = (1,1,1,1)
		_Specular("specular",Color) = (1,1,1,1)
		_Gloss("gloss",Range(1,256)) = 20
	}
	SubShader
	{
		Tags { "RenderType" = "TransparentCutOut" "Queue"="AlphaTest" "IgnoreProjector"="True" }
		LOD 100

		Pass//ForwardBase
		{
			Tags{"LightMode" = "ForwardBase"}
			CGPROGRAM
			#pragma multi_compile_fwdbase
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"

			fixed4 _Color;
			fixed4 _Specular;
			float _Gloss;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _Cutoff;

			struct appdata
			{
				float4 vertex : POSITION;
				float3 normal:NORMAL;
				float2 uv:TEXCOORD0;
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float3 worldNormal:TEXCOORD0;
				float3 worldPos:TEXCOORD1;
				float2 uv:TEXCOORD2;
				SHADOW_COORDS(3)//仅仅是阴影
			};


			v2f vert(appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				TRANSFER_SHADOW(o);//仅仅是阴影
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				float3 worldNormal = normalize(i.worldNormal);
				float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);

				fixed4 texColor = tex2D(_MainTex, i.uv);
				clip(texColor.a - _Cutoff);//控制透明

				fixed3 diffuse = texColor.rgb*_LightColor0.rgb*_Color.rgb*saturate(dot(worldNormal, worldLightDir));

				fixed3 halfDir = normalize(viewDir + worldLightDir);
				fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(worldNormal, halfDir)), _Gloss);

				//fixed shadow = SHADOW_ATTENUATION(i);
				//return fixed4(ambient + (diffuse + specular)*shadow + i.vertexLight, 1);

				//这个函数计算包含了光照衰减以及阴影,因为ForwardBase逐像素光源一般是方向光,衰减为1,atten在这里实际是阴影值
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				return fixed4(ambient + (diffuse + specular)*atten, 1);
			}
			ENDCG
		}

		Pass//ForwardAdd
		{
			Tags{"LightMode" = "ForwardAdd"}
			Blend One One

			CGPROGRAM
			#pragma multi_compile_fwdadd_fullshadows
			#pragma vertex vert
			#pragma fragment frag

			#include"UnityCG.cginc"
			#include"Lighting.cginc"
			#include"AutoLight.cginc"

			float4 _Diffuse;
			float4 _Specular;
			float _Gloss;

			struct a2v
			{
				float4 vertex:POSITION;
				float3 normal:NORMAL;
			};

			struct v2f
			{
				float4 pos:SV_POSITION;
				float3 worldPos:TEXCOORD0;
				float3 worldNormal:TEXCOORD1;
				LIGHTING_COORDS(2, 3)//包含光照衰减以及阴影
			};

			v2f vert(a2v v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				TRANSFER_VERTEX_TO_FRAGMENT(o);//包含光照衰减以及阴影
				return o;
			}

			fixed4 frag(v2f i) :SV_Target
			{
				float3 worldNormal = normalize(i.worldNormal);
				float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

				fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal, worldLightDir));

				float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				float3 halfDir = normalize(viewDir + worldLightDir);
				fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(worldNormal, halfDir)), _Gloss);
				//fixed atten = LIGHT_ATTENUATION(i);
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//包含光照衰减以及阴影
				return fixed4((diffuse + specular)*atten, 1);
			}
			ENDCG
		}
			 
		Pass//产生阴影的通道(物体透明了就不会产生阴影)
		{
			Tags { "LightMode" = "ShadowCaster" }

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0
			#pragma multi_compile_shadowcaster
			#pragma multi_compile_instancing // allow instanced shadow pass for most of the shaders
			#include "UnityCG.cginc"

			struct v2f {
				V2F_SHADOW_CASTER;
				float2  uv : TEXCOORD1;
				UNITY_VERTEX_OUTPUT_STEREO
			};

			uniform float4 _MainTex_ST;

			v2f vert(appdata_base v)
			{
				v2f o;
				UNITY_SETUP_INSTANCE_ID(v);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
				TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				return o;
			}

			uniform sampler2D _MainTex;
			uniform fixed _Cutoff;
			uniform fixed4 _Color;

			float4 frag(v2f i) : SV_Target
			{
				fixed4 texcol = tex2D(_MainTex, i.uv);
				clip(texcol.a*_Color.a - _Cutoff);

				SHADOW_CASTER_FRAGMENT(i)
			}
			ENDCG
		}
	}
	//FallBack "Diffuse"
	//FallBack "Transparent/Cutout/VertexLit"
}

透明或者半透明或者不透明物体都能产生和接收阴影

效果图

image.png

代码如下

Shader "MyShader/shadow3"
{
	Properties
	{
		_AlphaScale("cutoff",Range(0,1)) = 0.5//透明程度
		_MainTex("main texture",2D) = "white"{}
		_Color("diffuse",Color) = (1,1,1,1)
		_Specular("specular",Color) = (1,1,1,1)
		_Gloss("gloss",Range(1,256)) = 20
	}
		SubShader
		{
			Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "IgnoreProjector" = "True" }
			LOD 100

			Pass//ForwardBase
			{
				Tags{"LightMode" = "ForwardBase"}
				ZWrite Off
				Blend SrcAlpha OneMinusSrcAlpha

				CGPROGRAM
				#pragma multi_compile_fwdbase
				#pragma vertex vert
				#pragma fragment frag

				#include "UnityCG.cginc"
				#include "Lighting.cginc"
				#include "AutoLight.cginc"

				fixed4 _Color;
				fixed4 _Specular;
				float _Gloss;
				sampler2D _MainTex;
				float4 _MainTex_ST;
				float _AlphaScale;

				struct appdata
				{
					float4 vertex : POSITION;
					float3 normal:NORMAL;
					float2 uv:TEXCOORD0;
				};

				struct v2f
				{
					float4 pos : SV_POSITION;
					float3 worldNormal:TEXCOORD0;
					float3 worldPos:TEXCOORD1;
					float2 uv:TEXCOORD2;
					SHADOW_COORDS(3)//仅仅是阴影
				};


				v2f vert(appdata v)
				{
					v2f o;
					o.pos = UnityObjectToClipPos(v.vertex);
					o.worldNormal = UnityObjectToWorldNormal(v.normal);
					o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
					o.uv = TRANSFORM_TEX(v.uv, _MainTex);
					TRANSFER_SHADOW(o);//仅仅是阴影
					return o;
				}

				fixed4 frag(v2f i) : SV_Target
				{
					fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

					float3 worldNormal = normalize(i.worldNormal);
					float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
					float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);

					fixed4 texColor = tex2D(_MainTex, i.uv);

					fixed3 diffuse = texColor.rgb*_LightColor0.rgb*_Color.rgb*saturate(dot(worldNormal, worldLightDir));

					fixed3 halfDir = normalize(viewDir + worldLightDir);
					fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(worldNormal, halfDir)), _Gloss);

					//fixed shadow = SHADOW_ATTENUATION(i);
					//return fixed4(ambient + (diffuse + specular)*shadow + i.vertexLight, 1);

					//这个函数计算包含了光照衰减以及阴影,因为ForwardBase逐像素光源一般是方向光,衰减为1,atten在这里实际是阴影值
					UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
					return fixed4(ambient + (diffuse + specular)*atten, texColor.a*_AlphaScale);
				}
				ENDCG
			}

			//Pass//ForwardAdd
			//{
			//	Tags{"LightMode" = "ForwardAdd"}
			//	Blend One One

			//	CGPROGRAM
			//	#pragma multi_compile_fwdadd_fullshadows
			//	#pragma vertex vert
			//	#pragma fragment frag

			//	#include"UnityCG.cginc"
			//	#include"Lighting.cginc"
			//	#include"AutoLight.cginc"

			//	float4 _Diffuse;
			//	float4 _Specular;
			//	float _Gloss;

			//	struct a2v
			//	{
			//		float4 vertex:POSITION;
			//		float3 normal:NORMAL;
			//	};

			//	struct v2f
			//	{
			//		float4 pos:SV_POSITION;
			//		float3 worldPos:TEXCOORD0;
			//		float3 worldNormal:TEXCOORD1;
			//		LIGHTING_COORDS(2, 3)//包含光照衰减以及阴影
			//	};

			//	v2f vert(a2v v)
			//	{
			//		v2f o;
			//		o.pos = UnityObjectToClipPos(v.vertex);
			//		o.worldNormal = UnityObjectToWorldNormal(v.normal);
			//		o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			//		TRANSFER_VERTEX_TO_FRAGMENT(o);//包含光照衰减以及阴影
			//		return o;
			//	}

			//	fixed4 frag(v2f i) :SV_Target
			//	{
			//		float3 worldNormal = normalize(i.worldNormal);
			//		float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

			//		fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal, worldLightDir));

			//		float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
			//		float3 halfDir = normalize(viewDir + worldLightDir);
			//		fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(worldNormal, halfDir)), _Gloss);
			//		//fixed atten = LIGHT_ATTENUATION(i);
			//		UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//包含光照衰减以及阴影
			//		return fixed4((diffuse + specular)*atten, 1);
			//	}
			//	ENDCG
			//}

			
		}
		FallBack "Diffuse"
		//FallBack "Transparent/Cutout/VertexLit"
}

Render Queue 改为 Transparent 以下的值,比如改成 AlphaTest,半透明物体也能接收阴影了。