Unity中的基础光照

1 我们是如何看到这个世界的

1.1 光源

  • 光学中,用**辐照度(irradiance)**来量化光
  • 平行光:计算在垂直于 unity2d怎么做物体发光_unity

因为辐照度是和照射到物体表面时光线之间的距离 unity2d怎么做物体发光_光照模型_02 成反比的,因此辐照度就和 unity2d怎么做物体发光_学习_03 成正比, unity2d怎么做物体发光_学习_04 可以使用光源方向 unity2d怎么做物体发光_学习_05 和表面法线 unity2d怎么做物体发光_unity_06

1.2 吸收和散射

光线由光源发射出来后,就会与一些物体相交,通过相交的结果只有吸收和散射两个

  • 散射(scattering):只改变光线的方向,不改变光线的密度和颜色
  • 散射到物体内部:被称为折射(refraction)或投射(transmission)
  • 散射到物体外部:被称为反射(reflection)
  • 吸收(absorption):只改变光线的密度和颜色,不改变光线的方向

为了区分不同的散射方向,在光照模型中用了不同的部分来计算它们:

  • **高光反射(specular)**表示物体表面是如何反射光线的 – 一般认为只反射某一特定方向的光
  • **漫反射(diffuse)**表示有多少光线会被折射、吸收和散射出表面 – 一般认为没有方向性,光线在所有方向上平均分布

出射度(exitance):根据入射光线的数量和方向,计算出的出射光线的数量和方向

1.3 着色

**着色(shading)**指根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等)使用一个等式去计算沿某个方向的出射度的过程

也将这个等式称为光照模型(Lighting Mode)

BRDF(Bidirectional Reflectance Distribution Function)光照模型:直译过来就是双向反射分布函数,当给定入射光线的方向和辐照度后,BRDF可以给出某个方向上的光照能量分布

2 标准光照模型

也叫Phong光照模型,有裴祥风提出,只关心直接光照,即直接从光源发射出来照射到物体表面后,经过物体表面地一次反射直接进入摄像机的光线,将进入摄像机的光线分为四个部分,每个部分使用一种方法计算它的贡献度

  • 自发光(emissive):使用 unity2d怎么做物体发光_学习_07
  • 高光反射(specular):使用 unity2d怎么做物体发光_unity2d怎么做物体发光_08
  • 漫反射(diffuse):使用 unity2d怎么做物体发光_unity_09
  • 环境光(ambient):使用 unity2d怎么做物体发光_游戏引擎_10

2.1 环境光

利用环境光来模拟所有的间接光照,场景中的所有物体都使用这个环境光,通常是一个全局变量
unity2d怎么做物体发光_光照模型_11

2.2 自发光

光线直接由由光源发射进入摄像机,不需要经过任何物体反射,直接使用该材质的自发光颜色来计算
unity2d怎么做物体发光_游戏引擎_12
通常在实时渲染中,自发光的表面并不会照亮周围的表面,即这个物体并不会被当成一个光源

2.3 漫反射

漫反射完全随机,可以认为在任何反射方向上的分布都是一样的,符合兰伯特定律:反射光线的强度与表面法线和光源方向之间的家教的余弦值成正比:
unity2d怎么做物体发光_光照模型_13
其中,unity2d怎么做物体发光_unity_06 为表面法线,unity2d怎么做物体发光_unity_15 为指向光源的单位矢量,unity2d怎么做物体发光_学习_16 是材质的漫反射颜色,unity2d怎么做物体发光_unity_17

取最大值是为了防止点乘结果为负值,可以防止物体被后面来的光照亮

2.4 高光反射

Phong模型:

对于下图中的四个矢量,我们实际只需要知道三个矢量即可,反射方向可以根据其他信息计算得到

unity2d怎么做物体发光_unity2d怎么做物体发光_18

unity2d怎么做物体发光_光照模型_19
利用Phong模型来计算高光反射的部分:
unity2d怎么做物体发光_unity2d怎么做物体发光_20

  • unity2d怎么做物体发光_unity_21:材质的光泽度(gloss),也被称为反光度(shininess),用于控制高光区域的亮点有多宽,unity2d怎么做物体发光_unity_21
  • unity2d怎么做物体发光_游戏引擎_23:是材质的高光反射颜色,用于控制该材质对于高光反射的强度和颜色
  • unity2d怎么做物体发光_游戏引擎_24:光源的颜色和强度

Blinn模型:

为了避免计算反射方向 unity2d怎么做物体发光_unity_25 ,Blinn模型引入了一个新的矢量 unity2d怎么做物体发光_游戏引擎_26 它是通过对 unity2d怎么做物体发光_光照模型_27unity2d怎么做物体发光_学习_28 的取平均后再归一化得到的,即

unity2d怎么做物体发光_unity_29

unity2d怎么做物体发光_unity2d怎么做物体发光_30

之后,再使用 unity2d怎么做物体发光_游戏引擎_31unity2d怎么做物体发光_游戏引擎_26 之间的夹角进行进行计算,而非 unity2d怎么做物体发光_光照模型_27unity2d怎么做物体发光_unity_25

最后,Blinn模型的公式如下:
unity2d怎么做物体发光_unity2d怎么做物体发光_35
硬件实现时,如果摄像机和光源距离模型足够远的话,Blinn模型会快于Phong模型,此时可以认为 unity2d怎么做物体发光_光照模型_27unity2d怎么做物体发光_学习_28 均为定值,而 unity2d怎么做物体发光_游戏引擎_26 是一个常量,但是,当 unity2d怎么做物体发光_光照模型_27unity2d怎么做物体发光_学习_28

2.5 逐像素和逐顶点

  • 逐像素光照(per-pixel lighting):在片元着色器中计算,以每个像素为基础,得到它的法线,然后进行光照模型的计算,最后在面片之间对顶点法线进行插值(这种技术也被称为Phong着色)
  • 逐顶点光照(per-vertex lighting):在顶点着色器中计算(也被称为高洛德着色),在每个顶点上计算光照,然后再渲染图元内部进行线性插值,最后输出像素颜色

两种方法比较:

  • 计算量:由于顶点数往往小于像素数目,因此逐顶点光照的计算量往往小于逐像素光照
  • 效果:由于逐顶点光照依赖于在顶点之间进行线性插值,当光照模型中有非线性的计算时,逐顶点光照就会出问题

2.6 总结

虽然Blinn-Phong模型并不完全符合真实世界中的光照现象,但由于易用性和计算速度快的特点,使它仍被广泛使用

但该模型也有很大的局限性,例如菲涅尔反射等物理现象就无法用Blinn-Phong模型表现出来,其次Blinn-Phong模型是各向同性的,但是某些物体表面是各向异性的,使用Blinn-Phong模型就无法很好的表现

3 Unity中的环境光和自发光

环境光:Unity中环境光可以在Window->Lighting->Ambient Source/Ambient Color\Ambient Intensity中控制,在Shder中,我们只需要通过Unity的内置变量UNITY_LIGHTIMODEL_AMBIENT就可以得到环境光的颜色和强度信息

自发光:由于大多数物体没有自发光特性,因此在本书中绝大部分Shader都没有计算自发光Shader,只需要在片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色上即可

4 在Unity Shader中实现漫反射光照模型

漫反射基本公式:
unity2d怎么做物体发光_光照模型_13
要计算漫反射需要知道4个参数:入射光线的颜色和强度 unity2d怎么做物体发光_unity_17 ,材质的漫反射系数 unity2d怎么做物体发光_学习_16 ,表面法线 unity2d怎么做物体发光_游戏引擎_31 以及光源方向 unity2d怎么做物体发光_unity_15

为防止出现负值,我们需要使用max操作,而CG提供了这样的函数 unity2d怎么做物体发光_unity2d怎么做物体发光_46 即可将x截取在 unity2d怎么做物体发光_unity2d怎么做物体发光_47 的范围内,如果 unity2d怎么做物体发光_学习_48

4.1 逐顶点光照

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

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

Shader "MyShader/DiffuseVertexLevel"
{
	 Properties {
		// 声明一个Color属性,初始化为白色,用于控制材质的漫反射颜色 

		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
	 }
	 SubShader {
		Pass {
			// LightMode标签是Pass标签中的一种,只有定义了正确的LightMode,才能得到一些Unity的内置光照变量
			Tags {"LightMode" = "UniversalForward"}   // 如果是使用URP,则这里设置为UniversalForward
			// Tags {"LightMode" = "ForwardBase"}
			CGPROGRAM
			// 分别定义顶点着色器和片元着色器的名字
			#pragma vertex vert
			#pragma fragment frag

			// 为了使用Unity中的一些内置变量,需要包含Unity的内置文件Lighting.cginc
			#include "Lighting.cginc"

			// 为了在CG语句中使用Properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
			fixed4  _Diffuse;

			// 定义顶点着色器的输入和输出结构体
			struct a2v {
				float4 vertex : POSITION;   // 模型空间中的顶点位置 
				float3 normal : NORMAL;     // 用于访问顶点的法线
			};
			struct v2f {
				float4 pos : SV_POSITION;    // 裁剪空间中的顶点坐标
				fixed3 color : COLOR;        // 将顶点信息计算的光照颜色传递给片元着色器
			};

			// 漫反射的计算都在顶点着色器中进行
			v2f vert(a2v v) {
				v2f f;
				// 将顶点从模型空间通过MVP矩阵变换到裁剪空间
				f.pos = UnityObjectToClipPos(v.vertex);

				// 获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				// 将表面法线从世界空间转换到模型空间
				fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
				
				// 利用_WorldSpaceLightPos0获取光源方向
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

				// 利用_LightColor0来访问该Pass处理的光源的颜色和强度信息
				// 利用_Diffuse.rgb获取材质漫反射颜色
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

				// 输出结果为环境光和漫反射叠加
				f.color = ambient + diffuse;
				return f;
			}

			fixed4 frag(v2f f) : SV_Target {
				return fixed4(f.color, 1.0);
			}

			ENDCG
		}
	 }
	 FallBack "VertexLit"
}

4.2 逐片元光照

原理与逐顶点光照相同,只是把漫反射的计算放在了片元着色器中

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

Shader "MyShader/DiffusePixelLevel"
{
    Properties {
		// 声明一个Color属性,初始化为白色,用于控制材质的漫反射颜色 
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
	 }
	 SubShader {
		Pass {
			// LightMode标签是Pass标签中的一种,只有定义了正确的LightMode,才能得到一些Unity的内置光照变量
			Tags {"LightMode" = "UniversalForward"}   // 如果是使用URP,则这里设置为UniversalForward
			// Tags {"LightMode" = "ForwardBase"}
			CGPROGRAM
			// 分别定义顶点着色器和片元着色器的名字
			#pragma vertex vert
			#pragma fragment frag

			// 为了使用Unity中的一些内置变量,需要包含Unity的内置文件Lighting.cginc
			#include "Lighting.cginc"

			// 为了在CG语句中使用Properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
			fixed4  _Diffuse;

			// 定义顶点着色器的输入和输出结构体
			struct a2v {
				float4 vertex : POSITION;   // 模型空间中的顶点位置 
				float3 normal : NORMAL;     // 用于访问顶点的法线
			};
			struct v2f {
				float4 pos : SV_POSITION;         // 裁剪空间中的顶点坐标
				fixed3 worldNormal : TEXCOORD0;   // 将顶点信息计算的光照颜色传递给片元着色器
			};

			// 漫反射的计算都在顶点着色器中进行
			v2f vert(a2v v) {
				v2f f;
				// 将顶点从模型空间通过MVP矩阵变换到裁剪空间
				f.pos = UnityObjectToClipPos(v.vertex);
				
				// 将法线从世界空间变换到模型空间
				f.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				return f;
			}

			fixed4 frag(v2f f) : SV_Target {
				// 获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
				// 获取模型空间法线
				fixed3 worldNormal = normalize(f.worldNormal);

				// 获取光源方向
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

				// 计算漫反射
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
				fixed3 color = ambient + diffuse;
				return fixed4(color, 1);
			}

			ENDCG
		}
	 }
	 FallBack "VertexLit"
}

逐像素光照可以得到更平滑的效果

存在的问题:光照无法到达的区域模型外观通常是全黑的,使模型背光的区域看起来和一个平面一样,失去了模型的细节表现,对于此问题,使用半兰伯特(Half Lambert)光照模型改善

4.3 半兰伯特模型

之前使用的漫反射光照模型也被称为兰伯特光照模型,因为它符合兰伯特定律(在平面某点漫反射光的光强与该反射点的法向量和入射光的余弦值成正比),为了改善前文提到的问题,在原兰伯特基础上做了一个简单的修改,因此被称为半兰伯特光照模型

广义的半兰伯特光照模型如下:
unity2d怎么做物体发光_unity_49
半兰伯特光照模型没有使用max操作防止 unity2d怎么做物体发光_unity_50 出现负值,而是对其结果进行了 unity2d怎么做物体发光_unity2d怎么做物体发光_51 倍的缩放再加上 unity2d怎么做物体发光_光照模型_52 的便宜,绝大多数情况下 unity2d怎么做物体发光_unity2d怎么做物体发光_51unity2d怎么做物体发光_光照模型_52 的值均为 unity2d怎么做物体发光_光照模型_55,即公式为:
unity2d怎么做物体发光_unity2d怎么做物体发光_56
通过这样的方式,可以将 unity2d怎么做物体发光_unity_50 的结果从 unity2d怎么做物体发光_学习_58 映射到 unity2d怎么做物体发光_unity2d怎么做物体发光_47

Shader "MyShader/DiffusePixelHalfLambertLevel"
{
    Properties {
		// 声明一个Color属性,初始化为白色,用于控制材质的漫反射颜色 
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
	}
	SubShader {
		Pass {
			// LightMode标签是Pass标签中的一种,只有定义了正确的LightMode,才能得到一些Unity的内置光照变量
			Tags {"LightMode" = "UniversalForward"}   // 如果是使用URP,则这里设置为UniversalForward
			// Tags {"LightMode" = "ForwardBase"}
			CGPROGRAM
			// 分别定义顶点着色器和片元着色器的名字
			#pragma vertex vert
			#pragma fragment frag

			// 为了使用Unity中的一些内置变量,需要包含Unity的内置文件Lighting.cginc
			#include "Lighting.cginc"

			// 为了在CG语句中使用Properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
			fixed4  _Diffuse;

			// 定义顶点着色器的输入和输出结构体
			struct a2v {
				float4 vertex : POSITION;   // 模型空间中的顶点位置 
				float3 normal : NORMAL;     // 用于访问顶点的法线
			};
			struct v2f {
				float4 pos : SV_POSITION;         // 裁剪空间中的顶点坐标
				fixed3 worldNormal : TEXCOORD0;   // 将顶点信息计算的光照颜色传递给片元着色器
			};

			// 漫反射的计算都在顶点着色器中进行
			v2f vert(a2v v) {
				v2f f;
				// 将顶点从模型空间通过MVP矩阵变换到裁剪空间
				f.pos = UnityObjectToClipPos(v.vertex);
				
				// 将法线从世界空间变换到模型空间
				f.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				return f;
			}

			fixed4 frag(v2f f) : SV_Target {
				// 获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
				// 获取模型空间法线
				fixed3 worldNormal = normalize(f.worldNormal);

				// 获取光源方向
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

				// 计算漫反射
				fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
				fixed3 color = ambient + diffuse;
				return fixed4(color, 1);
			}

			ENDCG
		}
	}
	FallBack "VertexLit"
}

5 在Unity Shader中实现高光反射光照模型

高光反射基本公式:
unity2d怎么做物体发光_unity2d怎么做物体发光_20
需要知道的参数:

  • unity2d怎么做物体发光_游戏引擎_24:入射光线的颜色和强度
  • unity2d怎么做物体发光_游戏引擎_23:材质的高光反射系数
  • unity2d怎么做物体发光_光照模型_63:视角方向
  • unity2d怎么做物体发光_unity_64:反射方向

其中,反射方向 unity2d怎么做物体发光_学习_65 可以由表面法线 unity2d怎么做物体发光_游戏引擎_31 和光源方向 unity2d怎么做物体发光_学习_28 计算而得:
unity2d怎么做物体发光_游戏引擎_68
为此,CG提供了计算反射方向的函数 unity2d怎么做物体发光_unity2d怎么做物体发光_69,其中 unity2d怎么做物体发光_unity_70 为入射方向,unity2d怎么做物体发光_unity_06

5.1 逐顶点光照

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

Shader "MyShader/Specular/SpecularVertexLevel"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色
        _Specular("Specular", Color) = (1, 1, 1, 1)   // 控制高光反射颜色
        _Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode" = "UniversalForward" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            // 获取Properties语句中定义的变量
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex : POSITION;   // 模型空间中顶点的位置
                float3 normal : NORMAL;     // 模型空间中顶点的法线
            };
            struct v2f {
                float4 pos : SV_POSITION;   // 裁剪空间中顶点的位置
                fixed3 color : COLOR;       // 顶点着色器计算得到的颜色
            };

            v2f vert (a2v v)
            {
                v2f f;
                f.pos = UnityObjectToClipPos(v.vertex);                                                                   // 将顶点坐标从模型空间变化到裁剪空间
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                                                            // 获取环境光
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));                             // 将顶点法线从模型空间变换到世界空间
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);                                               // 获取环境光的方向
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));                                      // 获取反射方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);            // 获取视线方向
                
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));             // 计算漫反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(viewDir, reflectDir)), _Gloss);     // 计算高光反射

                f.color = ambient + diffuse + specular;
                return f;
            }

            fixed4 frag (v2f f) : SV_Target
            {
                return fixed4(f.color, 1);
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

5.2 逐像素光照

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

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

Shader "MyShader/Specular/SpecularPixelLevel"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色
        _Specular("Specular", Color) = (1, 1, 1, 1)   // 控制高光反射颜色
        _Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode" = "UniversalForward" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            // 获取Properties语句中定义的变量
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex : POSITION;   // 模型空间中顶点的位置
                float3 normal : NORMAL;     // 模型空间中顶点的法线
            };
            struct v2f {
                float4 pos : SV_POSITION;          // 裁剪空间中顶点的位置
                fixed3 worldNormal : TEXCOORD0;    // 顶点着色器计算得到的颜色
                fixed3 worldPos : TEXCOORD1;       // 顶点着色器计算得到的颜色
            };

            v2f vert (a2v v)
            {
                v2f f;
                f.pos = UnityObjectToClipPos(v.vertex);                         // 将顶点坐标从模型空间变化到裁剪空间
                f.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);   // 将顶点法线从模型空间变换到世界空间
                f.worldPos = mul(unity_ObjectToWorld, v.vertex);                // 将顶点坐标从模型空间变换到世界空间
                return f;
            }

            fixed4 frag (v2f f) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                                                          // 获取环境光
                fixed3 worldNormal = normalize(f.worldNormal);                                                          // 获取顶点法线在世界空间的坐标
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);                                             // 获取环境光的方向
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));                                    // 获取反射方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos.xyz);                                  // 获取视线方向
                
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));           // 计算漫反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);   // 计算高光反射

                return fixed4(ambient + diffuse + specular, 1);
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

5.3 Blinn-Phong光照模型

Blinn模型没有使用反射方向,而是引入了一个新的矢量 unity2d怎么做物体发光_游戏引擎_26,通过对视角方向和光照方向 unity2d怎么做物体发光_学习_28 相加再归一化之后得到(见5.2.4):
unity2d怎么做物体发光_unity_29
Blinn-Phong模型的公式如下:
unity2d怎么做物体发光_unity2d怎么做物体发光_35

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

Shader "MyShader/Specular/SpecularBlinnPhongLevel"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色
        _Specular("Specular", Color) = (1, 1, 1, 1)   // 控制高光反射颜色
        _Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode" = "UniversalForward" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            // 获取Properties语句中定义的变量
            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 f;
                f.pos = UnityObjectToClipPos(v.vertex);                         // 将顶点坐标从模型空间变化到裁剪空间
                f.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);   // 将顶点法线从模型空间变换到世界空间
                f.worldPos = mul(unity_ObjectToWorld, v.vertex);                // 将顶点坐标从模型空间变换到世界空间
                return f;
            }

            fixed4 frag (v2f f) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                                                           // 获取环境光
                fixed3 worldNormal = normalize(f.worldNormal);                                                           // 获取顶点法线在世界空间的坐标
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);                                              // 获取光源的方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos.xyz);                                   // 获取视线方向
                fixed3 halfDir = normalize(worldLightDir + viewDir);                                                     // 获取矢量h

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));            // 计算漫反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);   // 计算高光反射

                return fixed4(ambient + diffuse + specular, 1);
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

6 使用Unity内置函数

手动计算光源的信息过程相对比较麻烦,UnityCG.cginc里提供了一些常用的函数,用于计算光照模型

需要包含头文件

#include "UnityCG.cginc"

函数名

描述

float3 WorldSpaceViewDir(float4 v)

输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。内部实现使用了UnityWorldSpaceViewDir函数

float3 UnityWorldSpaceViewDir(float4 v)

输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向

float3 ObjectSpaceViewDir(float4 v)

输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向

float3 WorldSpaceLightDir(float4 v)

仅可用于前向渲染中,输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。内部实现使用了UnityWorldSpaceLightDir,函数没有被归一化

float3 UnityWorldSpaceLightDir(float4 v)

仅可用于前向渲染中,输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向,没有被归一化。

float3 ObjectSpaceLightDir(float4 v)

仅可用于前向渲染中,输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化。

float3 UnityObjectToWorldNormal(float3 norm)

把法线方向从模型空间转换到世界空间中

float3 UnityObjectToWorldDir(in float3 dir)

把方向矢量从模型空间变换到世界空间中

float3 UnityWorldToObjectDir(float3 dir)

把方向矢量从世界空间变换到模型空间中

修改之前的SpecularBlinnPhongLevel的代码:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

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

Shader "MyShader/Specular/SpecularBlinnPhongLevel"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色
        _Specular("Specular", Color) = (1, 1, 1, 1)   // 控制高光反射颜色
        _Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode" = "UniversalForward" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            // 获取Properties语句中定义的变量
            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 f;
                f.pos = UnityObjectToClipPos(v.vertex);                         // 将顶点坐标从模型空间变化到裁剪空间
                f.worldNormal = UnityObjectToWorldNormal(v.normal) ;            // 将顶点法线从模型空间变换到世界空间
                // f.worldPos = mul(unity_ObjectToWorld, v.vertex);
                f.worldPos = UnityObjectToWorldDir(v.vertex);                // 将顶点坐标从模型空间变换到世界空间
                return f;
            }

            fixed4 frag (v2f f) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                                                           // 获取环境光
                fixed3 worldNormal = normalize(f.worldNormal);                                                           // 获取顶点法线在世界空间的坐标
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(f.worldPos));                                   // 获取光源的方向
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldPos));                                           // 获取视线方向

                fixed3 halfDir = normalize(worldLightDir + viewDir);                                                     // 获取矢量h

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));            // 计算漫反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);   // 计算高光反射

                return fixed4(ambient + diffuse + specular, 1);
            }
            ENDCG
        }
    }
    FallBack "Specular"
}