Unity Shader入门精要 第九章——Unity的光照衰减、Unity的阴影、标准UnityShader

目录

9.3 光照衰减

9.3.1 用于光照衰减的纹理

9.3.2 使用数学公式计算衰减

9.4 Unity的阴影

9.4.1 阴影是如何实现的

9.4.2 不透明物体的阴影

 1.让物体投射阴影

2、让物体接收阴影

9.4.4 统一管理光照衰减和阴影

9.4.5 透明度物体的阴影

1、透明度测试

 2、透明度混合

 9.5 本书使用的标准Unity Shader


9.3 光照衰减

        9.2中我们有使用下面的这样的两行代码来计算光照衰减。这是Unity使用一张纹理作为查找表来在片元着色器中计算逐像素光照的衰减。好处就是计算衰减不依赖于数学公式的复杂性,只要使用一个参数值去纹理中采样即可。但也有一些弊端:

  • 1、需要预处理达到采样纹理,纹理大小会影响衰减精度;
  • 2、不直观,不方便,一旦把数据存储到查找表中就无法使用其他数学公式计算衰减。

        但这种方法可以提升性能,效果大部分情况还行,所以Unity默认就是使用这种纹理查找的方式来计算逐像素的点光源和聚光灯的衰减的。

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

9.3.1 用于光照衰减的纹理

        Unity在内部使用一张名为_LightTexture0的纹理来计算光源衰减。如果对光源使用了cookie,则衰减查找纹理是_LightTextureB0(这种情况暂不讨论)。我们只需要看_LightTexture0对角线上的纹理颜色值,这些值表示了在光源空间中不同位置的店的衰减值。如(0,0)点表明了与光源位置重合的点的衰减值,(1,1)表示了光源空间内距离最远的点的衰减值。

        为了对_LightTexture0纹理采样得到定点到该光源的衰减值,我们要得到该点在光源空间的位置。这里使用了unity_WorldToLight变换矩阵,把顶点从世界空间变换到光源空间。

float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
//经过unity_worldToLight变换得到的坐标模范围会在[0,1],所以可以用于后面的tex2D进行采样

        然后使用这个坐标的模的平方对衰减纹理进行采样,得到衰减值。

fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
//我们使用了光源空间中顶点距离的平方(dot函数),避免了开方操作
//r表示的是rgba的第一个元素,rr就表示用两个r来组成,如果rgba是(1,2,3,4),rr就是(1,1)
// 使用宏 UNITY_ATTEN_CHANNEL 来得到衰减纹理中衰减值所在的分量

9.3.2 使用数学公式计算衰减

        我们也可以在代码中利用数学公式计算光源的衰减,如下面这串代码:

//计算到光源的距离
float dsitance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
//距离越远,强度越小
atten = 1.0 / distance;

        但由于我们无法在Shader里面通过内置变量得到光源的范围、聚光灯的朝向、张开角度等信息,因此得到的效果有些时候不尽如人意,尤其是物体离开光源的照明范围时候会发生突变。

9.4 Unity的阴影

9.4.1 阴影是如何实现的

        Shadow Map技术:把摄像机放在与光源重合的位置上,那么场景中摄像机看不到的地方就是该光源的阴影区域。Unity使用的也是这种技术。

        在前向渲染中,如果场景中最重要的平行光开启了阴影,Unity就会计算该光源的阴影映射纹理(或深度纹理)。阴影映射纹理本质上是一张深度图,记录了从该光源的位置出发、能看到的场景中里它最近的表面信息(深度信息)。

        Unity会专门用一个Pass来更新光源的阴影映射纹理,这个Pass的LightMode标签被设置为ShadowCaster。Unity会首先把摄像机放在光源的位置,然后调用这个Pass,通过顶点变换后得到光源空间下的位置,并根据此来输出深度信息到阴影映射纹理中。

        传统的阴影映射纹理的实现:得到阴影映射纹理后,在其他的Pass中就可以使用这个阴影映射纹理,使用xy分量对阴影映射纹理采样,得到对应位置的深度信息。如果深度值小于该顶点的深度值(通常由z分量得到)就说明该点位于阴影中。

        Unity使用的是屏幕空间的阴影映射技术(ScreenSpace Shadow Map)。这项技术需显卡支持MRT。首先调用LightMode为ShadowCaster的Pass,得到可投射阴影的光源的阴影映射纹理和摄像机的深度纹理。如果摄像机的深度图中记录的表面深度大于转换到阴影映射纹理中的深度值,就说明该表面虽然可见,但是处于光源的阴影中。(阴影映射纹理记录了从该光源的位置出发、能看到的场景中里它最近的表面信息。如果大于,说明从该光源的方向看不到这个表面,但是从摄像机可以看到)。使用上面的比较方法就可以根据光源的阴影映射纹理和摄像机的深度纹理得到屏幕空间的阴影图

        通过上面的方式,阴影图就包含了屏幕空间中所有有阴影的区域。

        可以总结为下面两个过程:

        1、如果我们想要一个物体接受来自其他物体的阴影,就必须在Shader中对阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果和最后的光照结果相乘来产线阴影效果。

        2、如果我们想要一个物体向其他物体投射阴影,就必须把该物体加入到光源的阴影映射纹理的计算中,从而让其他物体在对阴影映射纹理采样时可以得到该物体的相关信息。

9.4.2 不透明物体的阴影

        我们先创建一个正方体和两个平面,然后创建一个新的Shader取名为_Shadow,把使用9.2的Forward Rendering的代码复制过来,然后新建材质,赋给正方体。为了能让场景产生阴影,需要让平行光可以收集阴影信息,这需要在Light组件中开启阴影。如下图,我们选中的是软阴影

 1.让物体投射阴影

        在Unity中我们可以选中是否让一个物体投射或接收阴影。如下图,我们可以在立方体和平面找到下面这个Mesh Renderer组件。开启Cast Shadows之后Unity就会把该物体加入到光源的阴影映射纹理的计算,也就可以向其他物体投射阴影。Receive Shadows则可以选择是否让物体接受来自其他物体的阴影。

         我们先把立方体和两个平面的Cast Shadows和Receive Shadows都开启。因为计算光源的阴影映射纹理的时候会剔除物体的背面,为了让平面的正面和背面都能产生原因,所有我们可以把Cast Shadows设置为Two Sided,就可以允许对物体的所有面都计算阴影信息。最后可以得到下面的效果

         我们正方体使用的Shader并没有LightMode为ShadowCaster的Pass,但还是可以产生阴影。这是因为我们使用了回调 Fallback "Specular",因为在自己的Shadow找不到对应的Pass,就去Specular里面找,发现Specular里面也没有,但Specular的Shader里面又回调 VertexLit,然后再VertexLit我们就可以找到LightMode为ShadowCaster的Pass了:

	// Pass to render object as a shadow caster
	Pass {
		Name "ShadowCaster"
		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

	}
//这段代码的实际用处就是为了把深度信息写入渲染目标中,渲染目标可以说光源的阴影映射纹理,或摄像机的深度纹理

2、让物体接收阴影

        我们在上面的效果图中可以看到,右边平面的阴影并没有投射在正方体上,但是可以投射在下面的平面平面上,这是因为平面使用了内置的Standard Shader。正方体使用的Shader并没有对阴影进行任何处理,所所以不会显示。

        所以我们需要修改一下_Shadow的Shader代码,只需要修改第一个Base Pass的代码即可:

Pass {
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			#pragma multi_compile_fwdbase	
			// 如果希望Additional Pass有阴影效果,就要把上面这行代码替换成下面这个
//			#pragma multi_compile_fwdadd_fullshadows
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			//包含一个新的内置文件,后面计算阴影所需要的宏都是这个文件声明的
			#include "AutoLight.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				//添加了一个内置宏SHADOW_COORDS,
				//用于声明一个用于对阴影纹理采样的uv坐标。参数是下一个可用的插值寄存器的索引值,前面以及声明到了TEXCOORD1,所以下一个是2
				SHADOW_COORDS(2)// 相当于float4 _ShadowCoord : TEXCOORD2 
			};
			
			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_SHADOW,用于计算前面声明的阴影纹理坐标,TRANSFER_SHADOW会得到一个用于提取阴影贴图的uv坐标
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

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

			 	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				fixed atten = 1.0;
				//使用内置宏SHADOW_ATTENUATION,计算阴影值,SHADOW_ATTENUATION 负责使用_ShadowCoord对对应的纹理进行采,得到阴影信息。
				fixed shadow = SHADOW_ATTENUATION(i);
				
				return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
			}
			
			ENDCG
		}

        代码中主要使用了SHADOW_COORDS、TRANSFER_SHADOW、SHADOW_ATTENUATION,三个内置宏,是计算阴影时的“三剑客”。可以在AutoLight.cginc里面找到对应的声明。

  • SHADOW_COORDS 实际上就是声明了一个名为_ShadowCoord的阴影纹理坐标变量。
  • TRANSFER_SHADOW 首先会判断当前平台是否可以使用屏幕空间的阴影映射技术(通过判断是否定义了 UNITY_NO_SCREENSPACE_SHADOWS);如果支持就会调用内置的ComputeScreenPos函数来计算_ShadowCoord;如果不支持就会使用传统的阴影映射技术,先把顶点坐标从模型空间变换到光源空间后存储到_ShadowCoord中。
  • SHADOW_ATTENUATION 负责使用_ShadowCoord对应的纹理进行采,得到阴影信息。

        需要注意的是,这些宏会使用上下文变量来进行相关技术。为了让宏能够正确工作,我们需要保证:a2v 结构体中的顶点坐标变量必须是vertex,顶点着色器的输入结构体v2f必须命名为v,且v2f中的顶点位置变量必须命名为pos。

        最后我们就可以得到下面的效果,正方体也可以接收来自右边平面的阴影了。

        我们只修改了Base Pass的代码,使其可以得到阴影效果,对Additional Pass的阴影处理和Base Pass大体上一样的。 

9.4.4 统一管理光照衰减和阴影

        之前讲过光照衰减的内容,在Base Pass中,平行光衰减因子总为1,但在Additional Pass中,我们需要判断光源类型,然后再计算。实际上,光照衰减和阴影对物体最终渲染的结果本质是相同的——都是把光照衰减因子和阴影值和与光照结果相乘得到的最终的渲染结果,对应的代码中的这一行(如下图)。Unity提供了一种方法让我们可以同时计算两个信息——提供内置宏UNITY_LIGHT_ATTENUATION 来实现的。

         实现起来也很简单,只需要抛弃前面使用的宏SHADOW_ATTENUATION,使用UNITY_LIGHT_ATTENUATION。下面展示一下Additional Pass修改后的代码

Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			#pragma multi_compile_fwdadd

			#pragma vertex vert
			#pragma fragment frag
			
            //头文件
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				SHADOW_COORDS(2) //使用宏SHADOW_COORDS 声明阴影坐标
			};
			
			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_SHADOW计算并向片元着色器传递阴影坐标
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

			 	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				// UNITY_LIGHT_ATTENUATION 不光可以计算衰减,也可以计算阴影
                //它会将光照衰减和阴影值相乘后的结果存储到第一个参数,我们也不需要声明atten,会自带帮我们声明
                //第二个参数是结构体v2f,这个结构体会传给SHADOW_ATTENUATION,用来计算阴影值
                //第三个参数是世界空间的坐标,用于计算光源空间下的坐标,然后得到光照衰减
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
			 	
				return fixed4((diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}

9.4.5 透明度物体的阴影

        对于不透明的物体,只要能在Fallback里面找到VertexLit就可以得到正确的阴影,但对于透明物体,我们就要小心处理它们的阴影。

1、透明度测试

        在透明度测试中,如果我们还是直接回调VertexLit、Specular等,往往得不到正确的阴影。因为透明度测试需要在片元着色器中舍弃某些片元,可是VertexLit中阴影投射纹理并没有这样的操作。下面我们来实现透明度测试物体的阴影,相比8.3的透明度测试的代码,只是添加了关于阴影的计算。

Shader "Unity Shaders Book/Chapter 9/Alpha Test With Shadow" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
	}
	SubShader {
		Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			Cull Off
			
			CGPROGRAM
			
			#pragma multi_compile_fwdbase
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _Cutoff;
			
			struct a2v {
				float4 vertex : POSITION;//一定得是vertex
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;//一定得是pos
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
				SHADOW_COORDS(3) //前面已经用了TEXCOORD0、1、2,所以这里参数为3
			};
			
			v2f vert(a2v v) { //变量名一定得取为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.texcoord, _MainTex);
			 	
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed4 texColor = tex2D(_MainTex, i.uv);

				clip (texColor.a - _Cutoff);
				
				fixed3 albedo = texColor.rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
							 	
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
			 	
				return fixed4(ambient + diffuse * atten, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Transparent/Cutout/VertexLit"
}

        上面的代码我们基本都讲过,不同的是最后的FallBack。之前我们一般使用的是Specular、Disffuse,这些最终都可以回调到VertexLit,但VertexLit提供的ShadowCaster的Pass并没有进行任何有关透明度测试的计算。在Transparent/Cutout/VertexLit的ShadowCaster Pass中计算了透明度测试,所有我们使用这个作为最后的回调。需要注意的是Transparent/Cutout/VertexLit是使用了名为_Cutoff的属性来进行透明度测试的,所以我们的Shader中也必须提供名为_Cutoff的属性。

        但其实还是有点问题,会出现一些不应该透过光的部分,这是因为默认情况下把物体渲染到深度图和阴影映射纹理中只考虑了物体的正面,就考虑不到背面对光源的遮挡,所以我们还要把正方体的MeshRender组件的Cast Shadows属性设置为Two Sided。

 2、透明度混合

         事实上目前所有内置透明度混合的Shader,如Transparent/VertexLit等,都没有包含阴影投射的Pass,所以它们不会向其他物体投射阴影,也不会接收其他物体的阴影。这是因为透明度混合要关闭深度写入,想要产生正确的阴影,需要在每个光源空间下严格按照从后往前的顺序进行渲染,这样会导致阴影处理变得非常复杂,且影响性能。

        但我们可以使用Specular、Diffuse、VertexLit这些不透明物体使用的Shader作为回调,强制为半透明物体生成阴影。。

         代码方面只需要使用8.4的透明度混合的代码,加上光源阴影的计算。

 9.5 本书使用的标准Unity Shader

        前面的代码都只是为了阐述Unity中各种光照实现原理,缺少一些光照计算,不可以直接使用到项目当中。下面给出标准的Unity Shader,包含了对发现纹理、多光源、光照衰减和阴影的相关处理。

Shader "MyShader/Common/Bumped Specular" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_BumpMap ("Normal Map", 2D) = "bump" {}
		_Specular ("Specular Color", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
		
		Pass { 
			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;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float4 _BumpMap_ST;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float4 uv : TEXCOORD0;
				float4 TtoW0 : TEXCOORD1;  
                float4 TtoW1 : TEXCOORD2;  
                float4 TtoW2 : TEXCOORD3; 
				SHADOW_COORDS(4)
			};
			
			v2f vert(a2v v) {
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 
			 	o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
			 	o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

				TANGENT_SPACE_ROTATION;
				
				float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
                fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
                
                o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);  
                o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);  
                o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  
  				
  				TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
				fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
				
				fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
				bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));

				fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
			 	fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
			 	
			 	fixed3 halfDir = normalize(lightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
			
				UNITY_LIGHT_ATTENUATION(atten, i, worldPos);

				return fixed4(ambient + (diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
		
		Pass { 
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			#pragma multi_compile_fwdadd
			// Use the line below to add shadows for point and spot lights
//			#pragma multi_compile_fwdadd_fullshadows
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float4 _BumpMap_ST;
			float _BumpScale;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float4 uv : TEXCOORD0;
				float4 TtoW0 : TEXCOORD1;  
                float4 TtoW1 : TEXCOORD2;  
                float4 TtoW2 : TEXCOORD3;
				SHADOW_COORDS(4)
			};
			
			v2f vert(a2v v) {
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 
			 	o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
			 	o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

				float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
                fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
	
  				o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
			  	o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
			  	o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  
			 	
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
				fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
				
				fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
				bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
				
				fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
				
			 	fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
			 	
			 	fixed3 halfDir = normalize(lightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
			
				UNITY_LIGHT_ATTENUATION(atten, i, worldPos);

				return fixed4((diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Specular"
}

        上面的代码我们在前面几章都学习过。如果不需要高光把Specular去掉即可

猜你喜欢

转载自blog.csdn.net/buzhengli/article/details/132077807