《Unity Shader入门精要》笔记08

news2024/11/26 18:46:23

文章目录

  • Unity的渲染路径
    • 前向渲染路径
      • 前向渲染路径原理
      • Unity中的前向渲染
        • BassPass
        • Additional Pass
      • 内置的光照变量和函数
    • 延迟渲染路径
      • 延迟渲染的原理
      • Unity中的延迟渲染
      • 可访问的内置变量和函数
  • Unity的光源类型
    • 光源类型有什么影响
      • 平行光
      • 点光源
      • 聚光灯
    • 在前向渲染中处理不同的光源类型
  • Unity的光照衰减
    • 用于光照衰减的纹理
    • 使用数学公式计算衰减
  • Unity的阴影
    • 阴影是如何实现的
    • 不透明物体的阴影
    • 让物体接收阴影
    • 统一管理光照衰减和阴影
    • 透明度物体的阴影
  • BumpedDiffuse
  • BumpedSpecular

Unity的渲染路径

渲染路径(Rendering Path) 决定了光照是如何应用到Unity Shader中的,为此我们需要为每个Pass指定它使用的渲染路径。

Unity支持多种类型的渲染路径,主要有三种:前向渲染路径(Forward Rendering Path)延迟渲染路径(Deferred Rendering Path)顶点照明渲染路径(Vertex Lit Rendering Path)(5.0版本后被抛弃)

大多情况下,一个项目只是用一种渲染路径,但有时也希望可以使用多个渲染路径。可以在Editor -> Project Setting -> Graphics窗口可设置默认的渲染路径,每个Camera可覆盖默认的渲染路径。
在这里插入图片描述
在这里插入图片描述
如果当前显卡不支持所选渲染路径,则Unity将自动使用更低一级的渲染路径。

LightMode标签支持的渲染路径设置选项

标签名描述
Always不管使用那种渲染路径,该Pass总是会被渲染,但不会计算任何光照
ForwardBase用于前向渲染。该Pass会计算环境光,最重要的平行光,逐顶点/SH光源和Lightmaps
ForwardAdd用于前向渲染。该Pass会计算额外的逐像素光源,每个Pass对应一个光源
Deferred用于延迟渲染。该Pass会渲染G缓冲(G-buffer)
ShadowCaster把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中

前向渲染路径

前向渲染路径原理

每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:颜色缓冲区和深度缓冲区。通过深度缓冲区来决定一个片元是否可见,可见则更新颜色缓冲区的颜色值。

//伪代码描述前向渲染过程
Pass{
	for(each primitive in this model){//遍历每个模型中的图元
		for(each fragment covered by this primitive){//遍历每个片元
			if(failed in depth test){
				//如果没有通过深度测试 说明该片元是不可见的
				discard;
			}else{
				//如果该片元可见 就进行光照计算
				float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
				//更新帧缓冲
				writeFrameBuffer(fragment,color);
			}
		}
	}
}

对于每个逐像素光源,都需要进行上面一次完整的流程。如果一个物体在多个逐像素光源的影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的结果。然后在缓冲区中把这些光照结果混合起来得到最终的颜色值。如果有大量的逐像素光照,那么需要执行的Pass数目也会很大。因此渲染引擎通常会限制每个物体的逐像素光照的数目。

Unity中的前向渲染

一个Pass不仅仅可以用来计算逐像素光照,也可以用来计算逐顶点和其他光照。取决于光照计算所处的流水线阶段以及计算时使用的数学模型。

在Unity中,前向渲染路径有3中处理光照的方式:逐顶点处理逐像素处理球谐函数(Spherical Harmonics,HS)处理。而决定一个光源使用哪种处理模式取决于他的类型和渲染模式。

光源类型指的是该光源是平行光还是其他类型的光源
光源的渲染模式指的是该光源是否是重要的(Important)(设置之后会被当成逐像素光源来处理)
在这里插入图片描述
当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(比如远近、光源强度),对这些光源进行一个重要度排序。这其中一定数目的光源会按照逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源按SH方式处理。

  • Unity的判断规则:
  • 场景中最亮的平行光总是按逐像素处理的。
  • 渲染模式被设置成Not Important的光源,会按逐顶点或者SH处理
  • 渲染模式被设置成Important的光源,会按逐像素处理
  • 如果根据以上规则得到的逐像素光源数量小于Quality Setting中的逐像素光源数量(Pixel Light Count),就会有更多的光源以逐像素的方式进行渲染。

链接:深入了解Unity的QualitySettings类

前向渲染有两种Pass:Base Pass和Additional Pass


BassPass

可实现光照纹理,环境光,自发光,阴影(平行光的阴影)。
光照计算方面,需要计算一个逐像素的平行光以及所有逐顶点和SH光源。

渲染设置:

Tags{ "LightMode" = "ForwardBase" }
#pragma multi_compile_fwdbase
Additional Pass

默认情况下不支持阴影,但可以通过使用#pragma multi_compile_fwdadd_fullshadows编译指令来开启阴影(这会需要Unity在内部使用更多的Shader变种)
光照计算方面,需要对其他影响该物体的逐像素光源的每个光源执行一次Pass

渲染设置:

Tags{ "LightMode" = "ForwardAdd" }
Blend One One
#pragma multi_compile_fwdadd

在渲染设置中开启了混合模式(Blend One One),因为希望每个Additional Pass都可以与上一次的光照结果在帧缓存中进行叠加,来得到最终有多个光照的渲染模式。如果不开启和设置,那么渲染结果会覆盖掉之前的渲染结果。


根据官方文档中的相关解释,我们可以知道这些(#pragma multi_compile_fwdbase #pragma multi_compile_fwdadd)编译指令会保证Unity可以为相应类型的Pass生成所有需要的Shader变种,这些变种会处理不同条件下的渲染逻辑。

环境光和自发光是在Base Pass中计算的,因为如果在AdditionPass中计算这两种光照,会造成叠加多次环境光和自发光。

对于前向渲染,一个UnityShader通常会定义一个Base Pass(也可定义多次)以及一个Additional Pass。一个Base Pass金辉执行一次(除非定义了多个),而一个Additional Pass会根据影响该物体的其他逐像素光源的数目被多次调用(每个逐像素光源会执行一次Additional Pass)

内置的光照变量和函数

前向渲染可以使用的内置光照变量

名称类型描述
_LightColor()float4该Pass处理的逐像素光源的颜色
_WorldSpaceLightPos0float4_WorldSpaceLightPos0.xyz是该Pass处理的逐像素光源的位置。如果该光源是平行光,那么_WorldSpaceLightPos0.w是0,其他光源类型w值为1
_LightMatrix0float4x4从世界空间到光源空间的变换矩阵。可以用于采样cookie和光强衰减(attenuation)纹理
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0float4仅用于Base Pass。前4个非重要的点光源在世界空间中的位置
unity_4LightAtten0float4仅用于Base Pass。存储了前4个非重要的点光源的衰减因子
unity_LightColorhalf[4]仅用于Base Pass。存储了前4个非重要的点光源的颜色

向前渲染可以使用的内置光照函数

函数名描述
float3 WorldSpaceLightDir(float4 v)仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。内部实现使用了UnityWorldSpaceLightDir函数。没有被归一化
float3 UnityWorldSpaceLightDir(float4 v)仅可用于前向渲染中。输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化
float3 ObjSpaceLightDir(float4 v)仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化
float3 Shade4PointLights(…)仅可用于前向渲染中。计算四个点光源的光照,参数是已经打包进矢量的光照数据,通常就是上表中的内置变量unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, unity_LightColor和unity_4LightAtten0等。前向渲染通常会使用这个函数来计算逐顶点光照。

延迟渲染路径

前向渲染的问题是当场景中包含大量实施光源是,前向渲染的性能会急速下降。因为如果在一块区域放置了多个光源,这些光源的影响区域互相叠加会为该区域的每个物体执行多个Pass来计算不同光源对物体的光照,但实际上很多计算都是重复的。

延迟渲染除了前向渲染中使用的颜色缓冲和深度缓冲外,还会利用额外的缓冲区——G缓冲(Geometry buffer,G-buffer)。
G缓冲区中存储了我们所关心的表面(通常指的是离摄像机最近的表面)的其他信息,例如该表面的法线、位置、用于光照计算的材质属性等。

延迟渲染的原理

主要包含两个Pass。

第一个Pass不进行任何光照计算,只计算哪些片元可见。主要是通过深度缓冲技术来实现的,当发现一个片元是可见的,就把它相关信息存储到G缓冲区中。

//伪代码
Pass1{
	//第一个Pass不进行真正的光照计算 仅把光照计算需要的信息存储到G缓冲区中
	for(each primitive in this model){
		for(each fragment covered by this primitive){
			if(failed in depth test){
				//如果没有通过深度测试,说明该片元是不可见的
				discard;
			}else{
				//如果该片元可见 就把需要的信息存储到G缓冲区中
				writeGBuffer(materialInfo, pos, normal);
			}
		}
	}
}

第二个Pass利用G缓冲区中的各个片元信息,例如表面法线、视角方向、漫反射系数等,进行光照计算。\

//伪代码
Pass2{
	//利用G缓冲中的信息进行真正的光照计算
	for(each pixel in the screen){
		if(the pixel is valid){
			//如果该像素是有效的 读取它对应的G缓冲中的信息
			readGBuffer(pixel, materialInfo, pos, normal);

			//根据读取到的信息进行光照计算
			float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
			//更新帧缓冲
			writeFrameBuffer(pixel, color);
		}
	}
}

延迟渲染的效率不依赖于场景的复杂度,而是与屏幕大小有关。

Unity中的延迟渲染

对于延迟渲染来说,他最适合在场景中光源数目很多、使用向前渲染会造成瓶颈的情况下使用。并且延迟渲染路径中的每个光源都可以按逐像素的方式处理。

延迟渲染的缺点:不支持真正的抗锯齿功能(anti-aliasing)功能、不能处理半透明物体、对显卡有一定要求(必须支持MRT(Multiple Render Targets)、Shader Mode 3.0 及以上、深度渲染纹理以及双面的模板缓冲)。

默认的G缓冲区(不同Unity版本的渲染纹理储存内容会有所不同)包含了以下几个渲染纹理(Render Texture,RT)。

  • RT0:格式为ARGB32,RGB通道用于存储漫反射颜色,A通道没有被使用
  • RT1:格式为ARGB32,RGB通道用于存高光反射颜色,A通道用于存储高光反射的指数部分
  • RT2:格式为ARGB2101010,RGB通道用于存储法线,A通道没有被使用
  • RT3:格式为ARGB2101010(非 HDR)或 ARGBHalf (HDR) ,用于存储自发光+lightmap+反射探针(reflection probes)。
  • 深度缓冲和模板缓冲

在第二个Pass中计算光照时,默认情况下仅可以使用Unity内置的Standard光照模型。
具体参考官方文档:延迟着色渲染路径

可访问的内置变量和函数

可以在UnityDeferredLibrary.cginc中找到声明

名称类型描述
_LightColorfloat4光源颜色
_LightMatrix0float4x4从世界空间到光源空间的变换矩阵。可以用于采样cookie和光强衰减纹理

Unity的光源类型

光源类型有什么影响

最常使用的光源属性有光源的位置、到某点的方向、颜色、强度以及到某点的衰减。

平行光

平行光没有位置,因此没有衰减的概念。几何属性只有方向,并且到所有点的方向都是一致的。

点光源

点光源的照亮空间是有限的。光源的位置、到某点的方向、颜色、强度以及到某点的衰减都有。

聚光灯

和点光源差不多一样,只不过一个是球体一个是椎体

在前向渲染中处理不同的光源类型

Shader "Unity Shaders Book/Chapter 9/Forward Rendering" {
	Properties {
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		
		//计算环境光和自发光
		Pass {
			// Pass for ambient light & first pixel light (directional light)
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			// Apparently need to add this declaration 
			#pragma multi_compile_fwdbase//保证使用光照衰减等光照变量可以被正确赋值。不可缺少
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.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;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				return o;
			}

			//使用了Blinn-Phong光照模型
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);//没有位置 可以直接使用_WorldSpaceLightPos0
				
				//环境光和自发光(本例无)先计算一次 保证在AdditionalPass中不再计算此部分
				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;//平行光没有衰减 直接为1
				
				return fixed4(ambient + (diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	
		//为其他逐像素光源定义Additional Pass
		Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			
			//开启和设置了混合模式 目的是希望该pass计算得到的光照结果可以在帧缓存中与之前的光照结果叠加
			Blend One One//也可用Blend SrcAlpha One
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#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;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				
				//判断逐像素光源的类型
				#ifdef USING_DIRECTIONAL_LIGHT//是平行光
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);//直接使用xyz
				#else//是点光源或者聚光灯
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);//使用位置相减
				#endif
				
				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);

				//处理不同光源的衰减
				#ifdef USING_DIRECTIONAL_LIGHT//是平行光
					fixed atten = 1.0;
				#else//是点光源或者聚光灯
					#if defined (POINT)
				        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
				        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #elif defined (SPOT)
				        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
				        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #else
				        fixed atten = 1.0;
				    #endif
				#endif

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

Unity的光照衰减

使用纹理查找计算衰减的弊端:

  • 需要预处理得到采样纹理,而且纹理的大小也会影响衰减的精度
  • 不直观 也不方便。因为一旦把数据存储到查找表中,我们就无法使用其他数学公式来计算衰减。

但由于这种方法可以在一定程度上提升性能,而且得到的效果在大部分情况下都是好的。因此Unity默认使用这种纹理查找的方式来计算逐像素的点光源和聚光灯的衰减。

用于光照衰减的纹理

Unity在内部是使用一张名为_LightTexture0的纹理来计算光源衰减。我们通常只关心_LightTexture0对角线上的纹理颜色值,这些值表明了在光源空间中不同位置的点的衰减值。(0,0)表明了与光远位置重合的点的衰减值(1,1)表明在光源空间中所关心的距离最远的点的衰减

通过得到某点在光源空间中的位置,可以得到_LightTexture0纹理采样中某点到光源的衰减值。
float3 lightCoord = mul(_LightMatrix0,float4(i.worldPosition,1)).xyz;

_LightMatrix0可以把顶点从世界空间变换到光源空间

然后,使用坐标的模的平方对衰减纹理进行采样,得到衰减值
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

由点积得到光源的距离平方,这是一个标量,我们对这个变量进行.rr操作相当于构建了一个二维矢量,这个二维矢量每个分量的值都是这个标量值,由此得到一个二维采样坐标。

UNITY_ATTEN_CHANNEL是衰减值所在的纹理通道,可以在内置的HLSLSupport.cginc文件中查看。一般PC和主机平台的话UNITY_ATTEN_CHANNEL是r通道,移动平台的话是a通道

使用数学公式计算衰减

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

由于Unity文档中没有给出内置衰减计算的相关说明,因此无法在shader中通过内置变量得到光源的范围、聚光灯的朝向、张开角度等信息,因此得到的效果往往不尽人意,尤其是在物体离开光源的照明范围时会发生突变。因为如果物体不在该光源的照明范围内,Unity就不会为物体执行一个Additional Pass。

Unity的阴影

阴影是如何实现的

当一个光源发射的一条光线遇到一个不透明物体时,这条光线就不可以再继续照亮其他物体(这里不考虑光线反射)。因此这个物体就会向它旁边的物体投射阴影,那些阴影区域的产生是因为光线无法到达这些区域。

在实时渲染中,最常使用的是Shadow Map的技术。他首先会把摄像机的位置放在与光源重合的位置上,那么场景中光源的阴影区域就是摄像机看不到的地方。Unity就是使用的这种技术。

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

Unity使用一个额外的Pass来专门更新光源的阴影映射纹理,这个Pass就是LightMode标签被设为ShadowCaster的Pass。

Unity会首先把摄像机放到光源的位置上,然后调用该Pass,通过对顶点变换后得到光源空间下的位置,并据此来输出深度信息到阴影映射纹理中。因此当开启了光源的阴影效果之后,底层渲染引擎首先会在当前渲染物体的UnityShader中找到LightMode为ShadowCaster的Pass,如果没有,他就会在Fallback指定的UnityShader中继续找,如果还没有,这个物体就无法向其他物体投射阴影(但仍可接收来自其他物体的阴影)。若找到了,就会使用该Pass来更新光源的阴影映射纹理。

传统映射纹理:在Pass中把顶点位置变换到光源空间下,使用xy分量对阴影映射纹理进行采样,得到阴影映射纹理中该位置的深度信息。若该深度值小于顶点的深度值(通常由z分量得到),说明该点在阴影中

屏幕空间的阴影映射技术:(需要显卡支持MRT)Unity首先通过调用Pass来得到可投射阴影光源的阴影映射纹理以及摄像机的深度纹理,然后据此来得到屏幕空间的阴影图。如果摄像机的深度图中记录的表面深度大于转换到阴影映射纹理中的深度值,就说明虽然表面可见,但是处在该光源的阴影中。

  • 如果我们想要一个物体接收来自其他物体的阴影:在Shader中对阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果和最后的光照相乘来产生阴影效果。
  • 如果我们想要一个物体向其他物体投射阴影,就必须把该物体加入到光源的阴影映射纹理的计算中,从而让其他物体在对阴影映射纹理采样时可以得到该物体的相关信息。在Unity中这个过程是通过为该物体执行LightMode为ShadowCaster的Pass来实现的

不透明物体的阴影

让平行光可以收集阴影信息
在这里插入图片描述

设置让一个物体投射或接收阴影
在这里插入图片描述
CastShadows设置为Two Sided可以允许所有面都计算阴影信息
在这里插入图片描述

让物体接收阴影

Shader "Unity Shaders Book/Chapter 9/Shadow" {
	Properties {
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		
		Pass {
			// Pass for ambient light & first pixel light (directional light)
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			// Apparently need to add this declaration 
			#pragma multi_compile_fwdbase	
			
			#pragma vertex vert
			#pragma fragment frag
			
			// Need these files to get built-in macros
			#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) //内置宏 声明一个用于对纹理采样的坐标 参数是下一个可用的插值寄存器的索引值
			};
			
			v2f vert(a2v v) {
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);

			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			 	
			 	// Pass shadow coordinates to pixel shader
			 	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;
				
				fixed shadow = SHADOW_ATTENUATION(i);//使用_ShadowCoord对相关纹理进行采样,得到阴影信息。
				
				return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
			}
			
			ENDCG
		}
	
		Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#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 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 position : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};
			
			v2f vert(a2v v) {
			 	v2f o;
			 	o.position = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);
			 	
			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				#else
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
				#endif

			 	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);

				#ifdef USING_DIRECTIONAL_LIGHT
					fixed atten = 1.0;
				#else
					float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
					fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				#endif
			 	
				return fixed4((diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	}
	FallBack "Specular"
}

SHADOW_COORDS实际上就是声明了一个名为_ShadowCoord的阴影纹理坐标变量。而TRANSFER_SHADOW的实现会根据平台不同而有所差异。如果当前平台可以使用屏幕空间的阴影映射技术,它会调用内置的ComputeScreenPos函数来计算_ShadowCoord;如果不支持,就会使用传统映射技术。

由于这些宏会使用上下文变量来进行相关计算,例如TRANSFER_SHADOW会使用v.vertex或a.pos来计算坐标,我们需要保证:a2v结构体的顶点坐标变量名必须是vertex,顶点着色器的输入结构体v2f必须命名为v,且v2f中的顶点位置变量必须命名为pos

统一管理光照衰减和阴影

通过内置的UNITY_LIGHT_ATTENUATION宏可实现同时计算光照衰减和阴影。

在这里插入代码片Shader "Unity Shaders Book/Chapter 9/Attenuation And Shadow Use Build-in Functions" {
	Properties {
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		
		Pass {
			// Pass for ambient light & first pixel light (directional light)
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#pragma multi_compile_fwdbase	
			
			#pragma vertex vert
			#pragma fragment frag
			
			// Need these files to get built-in macros
			#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)
			};
			
			v2f vert(a2v v) {
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);
			 	
			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			 	
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
			 	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 not only compute attenuation, but also shadow infos
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				
				return fixed4(ambient + (diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	
		Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#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 _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;
			 	
			 	// Pass shadow coordinates to pixel shader
			 	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 not only compute attenuation, but also shadow infos
				//参数1 atten未声明 因为UNITY_LIGHT_ATTENUATION会声明这个变量
				//参数2 用来计算阴影值 参数3用来计算光源空间下的坐标
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//计算光照衰减和阴影
				
			 	
				return fixed4((diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	}
	FallBack "Specular"
}

如果希望可以在Additional Pass中添加阴影效果,需要用#pragma multi_compile_fwdadd_fullshadows编译指令代替Additional Pass中的#pragma multi_compile_fwdadd指令

透明度物体的阴影

对于大多数不透明武替来说,把Fallback设为VertexLit就可以得到正确的阴影。但对于透明武替来说,透明物体的实现通常会使用透明度测试或透明度混合,需要小心设置Fallback。

由于透明度测试会在片元着色器中舍弃片元,所以如果直接使用VertexLit、Diffuse、Specular,往往无法得到正确的投影。

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 9/Alpha Blend With Shadow" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
	}
	SubShader {
		Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			
			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 _AlphaScale;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
				SHADOW_COORDS(3)//由于已经使用了TEXCOORD0 1 2所以要传入3 意味着占用第四个寄存器TEXCOORD3
			};
			
			v2f vert(a2v 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);
			 	
			 	// Pass shadow coordinates to pixel shader
			 	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);
				
				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 not only compute attenuation, but also shadow infos
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
			 	
				return fixed4(ambient + diffuse * atten, texColor.a * _AlphaScale);
			}
			
			ENDCG
		}
	} 
	FallBack "VertexLit"
	//FallBack "Transparent/Cutout/VertexLit"
}

FallBack "VertexLit"没有进行任何透明度测试的计算。
在这里插入图片描述

FallBack "Transparent/Cutout/VertexLit"计算了透明度测试,但默认情况下把物体渲染到深度图和阴影纹理映射中仅考虑了正面 需要把cast shadow属性设为two sided,强制计算所有面的深度信息。
需要注意的是由于FallBack "Transparent/Cutout/VertexLit"中计算透明度测试时,使用了_Cutoff的属性来进行透明度测试。所以我们的Shader中也必须提供名为_Culloff的属性
在这里插入图片描述

BumpedDiffuse

使用了Phong光照模型

Shader "Unity Shaders Book/Common/Bumped Diffuse" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_BumpMap ("Normal Map", 2D) = "bump" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}

		Pass { 
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			#pragma multi_compile_fwdbase
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float4 _BumpMap_ST;
			
			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 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
			
			 	fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
				
				UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
				
				return fixed4(ambient + diffuse * 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;
			
			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));
				
				UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
				
				return fixed4(diffuse * atten, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Diffuse"
}

BumpedSpecular

使用了Blinn-Phong光照模型

Shader "Unity Shaders Book/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"
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1089348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux友人帐之系统管理与虚拟机相关

一、虚拟机相关操作 1.1虚拟机克隆 虚拟机克隆是指将一个已经安装好的虚拟机复制出一个或多个完全相同的副本,包括虚拟机的配置、操作系统、应用程序等,从而节省安装和配置的时间和资源。 虚拟机克隆的主要用途有: 创建多个相同或相似的虚拟…

使用Python实现网页中图片的批量下载和水印添加保存

数字时代,图片已经成为我们生活中的一部分。无论是社交媒体上的照片,还是网页中的图片元素,我们都希望能够方便地下载并进行个性化的处理。 假设你是一位设计师,你经常需要从网页上下载大量的图片素材,并为这些图片添加…

深入了解基数排序:原理、性能分析与 Java 实现

基数排序(Radix Sort)是一种非比较性排序算法,它根据元素的每个位上的值来进行排序。基数排序适用于整数或字符串等数据类型的排序。本文将详细介绍基数排序的原理、性能分析及java实现。 基数排序原理 基数排序的基本原理是按照低位先排序&…

如何解决找不到msvcr100.dll问题,msvcr100.dll丢失的多种修复方案

当我的电脑出现MSVCR100.DLL丢失这个问题时,我感到非常困扰。我试图通过重新安装Visual C 2010 Redistributable Package来解决这个问题,但是这个方法并不总是有效。有些时候,即使我重新安装了整个软件包,MSVCR100.DLL文件仍然找不…

政策加码聚焦工业现代化发展,团队聚能驱动AI机器视觉高质量发展

随着智能制造进程的持续推进,新一代信息技术引领着第四次工业革命,机器视觉技术乘着东风实现高速发展,其视觉创新应用产品全面铺开,新应用、新模式不断涌现。深眸科技紧抓时代发展机遇,基于领先的图像算法和自主研究的…

Windows下DataGrip连接Hive

DataGrip连接Hive 1. 启动Hadoop2. 启动hiveserver2服务3. 启动元数据服务4. 启动DG 1. 启动Hadoop 在控制台中输入start-all.cmd后,弹出下图4个终端(注意终端的名字)2. 启动hiveserver2服务 单独开一个窗口启动hiveserver2服务,…

clone()方法使用时遇到的问题解决方法(JAVA)

我们平时在自定义类型中使用这个方法时会连续遇到 4 个问题。 基础代码如下: class A {int[] a {1,2,3}; }public class Test {public static void main(String[] args) {} } 第一个: 当我们直接调用时报错原因是Object类中的clone方法是被protecte…

什么叫AI自动直播?

AI自动直播是一种使用人工智能技术进行自动直播的程序或系统。 它可以自动录制视频,并在直播平台上进行展示,以吸引观众并提高品牌知名度。AI自动直播通常需要使用特定的软件或平台来实现,并且需要具备一定的编程和人工智能知识。 AI自动直…

win10搭建gtest测试环境+vs2019

首先是下载gtest,这个我已经放在了博客上方资源绑定处,这个适用于win10vs版本,关于liunx版本的不能用这个。 或者百度网盘链接: 链接:https://pan.baidu.com/s/15m62KAJ29vNe1mrmAcmehA 提取码:vfxz 下…

苹果CMS海螺模版V20修复版/加广告代码 ​适合视频影视类网站使用​

最新苹果CMS海螺模版V20修复版,增加广告代码,适合视频影视类网站使用,有兴趣的可以研究研究。 修复说明: 修复多线路时播放页列表点其他线路还是播放默认线路的问题 修复前台黑白切换和字体颜色切换失效 修复微信二维码没有对…

《进化优化》第4章 遗传算法的数学模型

文章目录 4.1 图式理论4.2 马尔可夫链4.3 进化算法的马尔可夫模型的符号4.4 遗传算法的马尔可夫模型4.4.1 选择4.4.2 变异4.4.3 交叉 4.5 遗传算法的动态系统模型4.5.1 选择4.5.2 变异4.5.3 交叉 4.1 图式理论 图式是描述一组个体的位模式,其中用*来表示不在乎的位…

基于PLC的机械手控制系统设计

目录 摘 要......................................................................................................................... 1 第一章 绪论.............................................................................................................…

什么是promise?如何使用?应用场景?

什么是Promise? Promise是一种用于处理异步操作的JavaScript编程模式。它允许你更优雅地处理异步代码,避免了回调地狱(Callback Hell)的问题,使代码更易于理解和维护。Promise是ES6(ECMAScript 2015&#…

云表:MES系统是工业4.0数字化转型的核心

随着信息技术与工业技术的深度融合,网络、计算机技术、信息技术、软件与自动化技术相互交织,产生了全新的价值模式。在制造领域,这种资源、信息、物品和人相互关联的模式被德国人定义为“工业4.0”,也就是第四次工业革命。工业4.0…

力扣每日一题36:有效的数独

题目描述: 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考…

GEE:基于GLDAS数据集分析土壤湿度的时间序列变化

作者:CSDN @ _养乐多_ 本篇博客将介绍如何使用Google Earth Engine(GEE)进行土壤湿度数据的分析。我们将使用NASA GLDAS(Global Land Data Assimilation System)数据集,其中包括了关于土壤湿度的信息。通过该数据集,我们将了解土壤湿度在特定区域和时间段内的变化,并生…

Excel 插入和提取超链接

构造超链接 HYPERLINK(D1,C1)提取超链接 Sheet页→右键→查看代码Sub link()Dim hl As HyperlinkFor Each hl In ActiveSheet.Hyperlinkshl.Range.Offset(0, 1).Value hl.AddressNext End Sub工具栏→运行→运行子过程→提取所有超链接地址参考: https://blog.cs…

虚拟现实VR技术在医疗行业的应用介绍

虚拟现实 (VR) 虽然经常与游戏联系在一起,但不可否认,未来科技少不了虚拟现实,其应用可以彻底改变许多行业。在医疗领域,无数人正在探索 VR 可以帮助患者和医疗从业者实现更好的治疗结果治疗方式,比如在手术、疼痛管理…

《UnityShader入门精要》学习3

笛卡尔坐标系(Cartesian Coordinate System) 二维笛卡儿坐标系 一个二维的笛卡儿坐标系包含了两个部分的信息: 一个特殊的位置,即原点,它是整个坐标系的中心。两条过原点的互相垂直的矢量,即x轴和y轴。这…

网工内推 | 运维专场,厂商、软考证书优先,五险一金,节日福利

01 中安网脉 招聘岗位:运维工程师 职责描述: 1、负责驻场单位内网日常运维工作; 2、负责驻场单位网络设备、安全设备、服务器、存储设备、主机等的日常运维工作,按照运维流程要求进行日常运维、巡检、故障处理等运维服务工作。 3…