内容概述

  1. 逐顶点光照和逐像素光照
  2. 漫反射光照模型
  3. 高光反射光照模型
  4. Phone标准光照模型

标准光照的构成结构

  1. 自发光:材质本身发出的光,模拟环境使用的光
  2. 漫反射光:光照到粗糙材质后,光的反射方向随机,还有一些光发生了折射,造成材质表面没有明显的光斑
    image.png
  3. 高光反射光:照到材质表面后,无(低)损失直接反射给观察者眼睛,材质表面能观察到光斑
    image.png
  4. 环境光:模拟场景光照(简单理解为太阳光)
  5. 裴祥凤提出的光照理论:标准光照 = 自发光 + 漫反射光 + 高光反射光 + 环境光

逐顶点光照和逐像素光照

顶点着色器:会在模型渲染点上运行,其他的点会线性插值
我的理解:
image.png

片元着色器:会在模型的所有像素点上运行
我的理解:上面的图形就会是全红。

逐顶点光照:会在顶点着色器上进行光照运算
逐像素光照:会在片元着色器上运行光照运算

逐像素比逐顶点效果好,逐顶点比逐像素性能好

兰伯特定律和半兰伯特定律

漫反射光照(兰伯特定律)

漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0, 标准化后物体表面法线向量· 标准化后光源方向向量)

光源颜色:场景中光GameObject取得
材质的漫反射颜色:材质球配置
Max是数学函数,标准化也有函数
表面法线向量:CPU加载模型后,传递到GPU中的
光源方向向量:场景中光GameObject取得

实现:所有数据拿到,拿公式计算

半兰伯特定律

漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * ((标准化后物体表面法线向量· 标准化后光源方向向量) * 0.5 + 0.5))

将整体颜色降低一半,再加一半

兰伯特定律 和 半兰伯特定律 的区别

image.png

这个的目的是让物体的背面不在那么黑

常用矩阵

矩阵名说明
UNITY_MATRIX_MVP将点从模型空间下,转换到裁剪空间下
UNITY_MATRIX_M点从模型空间下,转换到世界空间下
UNITY_MATRIX_V将点从世界空间下,转换到观察空间下
UNITY_MATRIX_P将点从观察空间下,转换到裁剪空间下
_Object2World将点从模型到世界空间转换(互为逆矩阵)
_World2Object将点从世界空间到模型转换(互为逆矩阵)

常用参数

column1column2
_WorldSpaceLightPos0.xyz获取直射光的方向(物体到光的方向)
_LightColor0.rgb获取光源的颜色
UNITY_LIGHTMODEL_AMBIENT.xyz环境光的颜色
_WorldSpaceCameraPos世界模式下。相机的位置

常用api

|几何函数|说明|
|normalize(v)|归一化向量|
|reflect(I,N)|根据入射光方向向量和顶点法向量N,计算发射光方向向量。其中I和N必须被归一化。
注意:这里的I是指向顶点的;函数只对三元向量有效|

编写简单的逐顶点漫反射

效果演示:
image.png
为什么会产生锯齿,因为采用了差值计算来实现的。

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

Shader "DY/ZuoLighting"
{
	Properties
	{
		_DiffuseColor("慢反射颜色", Color) = (1, 1, 1, 1)
	}

	SubShader
	{
		//设定光照模式为前向模式(才能正常获取光的颜色和光的方向)

		Tags { "LightMode" = "ForwardBase" }

		Pass
		{
			CGPROGRAM
			// 顶点着色器上的回调函数(用来处理,模型空间下的点转化为裁剪空间下的点)
			#pragma vertex vert
			// 片元着色器上的回调函数 (给点上颜色的)
			#pragma fragment frag

			//加载Cg语言的脚本,用来处理光照参数
			//处理光照的Cg库文件(cginc扩展名),目录在Unity的安装目录下Editor/Data/CGIncludes/Lighting.cginc
			#include "Lighting.cginc"

			fixed4 _DiffuseColor;



			// 需要什么?从CPU传过来的
			// 1. 模型空间下的点
			// 2. 当前点的模型空间下的法向向量

			// 如果在Cg编程中,顶点或片元着色器接收多个数值的时候,一般会用结构体实现
			// 从CPU接收到的数据
			struct c2v
			{
				float4 vertex: POSITION; //从CPU接收到的模型空间下的点的位置
				float3 normal: NORMAL; //从CPU接收到的当前点的模型空间下的法线向量
			};

			//因为在顶点着色器中,需要计算裁剪空间下点的位置和Phong着色计算出来的兰伯特定律计算后的颜色
			struct v2f
			{
				float4 pos : SV_POSITION; //经过顶点着色器计算后的,当前点的裁剪空间下的位置
				fixed3 color : COLOR; //经过兰伯特定律计算后的当前点的颜色
			};

			// Phong着色(逐像素光照),光照计算应该编写在顶点着色器中

            		v2f vert (c2v data)
            		{
				v2f v;
				// v.pos = mul(UNITY_MATRIX_MVP, data.vertex); 
				//将模型空间下的点转化为裁剪空间下的点。
				v.pos = UnityObjectToClipPos(data.vertex);

				//兰伯特定律计算
				//漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0, 标准化后物体表面法线向量 • 标准化后光源方向向量)

				//光照是在世界中发生,需要将所有的数值,变换到世界坐标系下,再运算
				//_Object2World矩阵是Unity提供的,用于转换模型空间下到世界空间下的转换矩阵
				//因为法线传递过来的是3x1的列矩阵,_Object2World是4x4的齐次矩阵,如果想做矩阵乘法,需要将齐次矩阵,变成3x3矩阵
				fixed3 worldNormal = normalize(mul((float3x3)unity_ObjectToWorld, data.normal));
				//获得直射光的光方向
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

				// 漫反射光
				// _LightColor0.rgb光源颜色
				fixed3 diffuse = _LightColor0.rgb * _DiffuseColor * max(0, dot(worldNormal, worldLightDir));

				//根据Phong光照模型,将环境光追加在最终计算后的颜色上
				v.color = UNITY_LIGHTMODEL_AMBIENT.xyz + diffuse;
				return v;


            		}

            		fixed4 frag (v2f data) : SV_Target
            		{
				//将顶点着色器计算好的颜色,传递给GPU
				return fixed4(data.color, 1.0);
            		}
           		 ENDCG
        	}
    }
}

编写简单的逐顶点漫反射

效果演示:
image.png
很明显比逐像素更加的平滑

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

Shader "DY/ZuoLighting"
{
	Properties
	{
		_DiffuseColor("慢反射颜色", Color) = (1, 1, 1, 1)
	}

	SubShader
	{
		//设定光照模式为前向模式(才能正常获取光的颜色和光的方向)
		Tags { "LightMode" = "ForwardBase" }

		Pass
		{
			CGPROGRAM
			// 顶点着色器上的回调函数(用来处理,模型空间下的点转化为裁剪空间下的点)
			#pragma vertex vert
			// 片元着色器上的回调函数 (给点上颜色的)
			#pragma fragment frag

			//加载Cg语言的脚本,用来处理光照参数
			//处理光照的Cg库文件(cginc扩展名),目录在Unity的安装目录下Editor/Data/CGIncludes/Lighting.cginc
			#include "Lighting.cginc"

			fixed4 _DiffuseColor;



			// 需要什么?从CPU传过来的
			// 1. 模型空间下的点
			// 2. 当前点的模型空间下的法向向量

			// 如果在Cg编程中,顶点或片元着色器接收多个数值的时候,一般会用结构体实现
			// 从CPU接收到的数据
			struct c2v
			{
				float4 vertex: POSITION; //从CPU接收到的模型空间下的点的位置
				float3 normal: NORMAL; //从CPU接收到的当前点的模型空间下的法线向量
			};

			//因为在顶点着色器中,需要计算裁剪空间下点的位置和Phong着色计算出来的兰伯特定律计算后的颜色
			struct v2f
			{
				float4 pos : SV_POSITION; //经过顶点着色器计算后的,当前点的裁剪空间下的位置
				fixed3 worldNormal : NORMAL; //经过兰伯特定律计算后的当前点的颜色
			};

			// Phong着色(逐像素光照),光照计算应该编写在顶点着色器中
            		v2f vert (c2v data)
            		{
				v2f v;
				// v.pos = mul(UNITY_MATRIX_MVP, data.vertex); 
				//将模型空间下的点转化为裁剪空间下的点。
				v.pos = UnityObjectToClipPos(data.vertex);

				//光照是在世界中发生,需要将所有的数值,变换到世界坐标系下,再运算
				//_Object2World矩阵是Unity提供的,用于转换模型空间下到世界空间下的转换矩阵
				//因为法线传递过来的是3x1的列矩阵,_Object2World是4x4的齐次矩阵,如果想做矩阵乘法,需要将齐次矩阵,变成3x3矩阵
				v.worldNormal = normalize(mul((float3x3)unity_ObjectToWorld, data.normal));

				return v;


            		}

			// 高洛德着色(逐像素光照),光照计算应该编写在片元着色器中
            		fixed4 frag (v2f data) : SV_Target
            		{

				//兰伯特定律计算
				//漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0, 标准化后物体表面法线向量 • 标准化后光源方向向量)


				//获得直射光的光方向
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

				// 法线向量
				float3 worldNormal = normalize(data.worldNormal);

				// 漫反射光
				// _LightColor0.rgb光源颜色
				fixed3 diffuse = _LightColor0.rgb * _DiffuseColor * max(0, dot(worldNormal, worldLightDir));

				//根据Phong光照模型,将环境光追加在最终计算后的颜色上
				float3 color = UNITY_LIGHTMODEL_AMBIENT.xyz + diffuse;

				return fixed4(color, 1.0);
            		}
            		ENDCG
        	}
    }
}

逐顶点高光反射


高光光照 = 光源的颜色 * 材质高光反射颜色 * (max(0, 标准化后的观察方向向量, 标准化后的反射方向)^光晕系数)

光源颜色:场景中光GameObject取得
材质的高光反射颜色:材质球配置
观察方向向量:相机观察点到当前渲染的点坐标所构成的方向
光反射方向:reflect(入射光的方向,当前点的法线方向)
光晕系数:材质球配置

效果演示:
image.png

代码示例:

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

Shader "DY/SpecularVetex"
{
	Properties
	{
		_SpecularColor("高光显示颜色", Color) = (1,1,1,1)
		// Range给这个属性限定一个范围
		_Gloss("光晕系数", Range(4, 256)) = 10
	}
		SubShader
	{

		Pass
		{
			//设定光照模式为前向模式(才能正常获取光的颜色和光的方向)
			Tags { "LightMode" = "ForwardBase" }

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			// 处理光照的一些库
			#include "Lighting.cginc"

			fixed4 _SpecularColor;
			fixed _Gloss;

			// 顶点着色器回调函数
			// 需要什么?【1】模型空间下的点 【2】模型空间下点的法向量
			struct c2v
			{
				float4 vertex:POSITION;
				float3 normal:NORMAL;
			};
			// 返回什么? 【1】裁剪空间下的点 【2】通过高光计算出来的颜色
			struct v2f {
				float4 pos:SV_POSITION;
				fixed3 color : COLOR;
			};

			// 想要做什么?
			// 【1】将模型空间下的点,转化为裁剪空间下的点
			// 【2】通过高光计算公式,计算出该点的颜色
			v2f vert(c2v c)
			{
				v2f v;
				// 【1】
				v.pos = UnityObjectToClipPos(c.vertex);

				// 【2】高光反射运算
				// 高光光照 = 光源的颜色 * 材质高光反射颜色 * 〖MAX(0,标准化后的观察方向向量 • 标准化后的反射方向)〗^ 光晕系数
				// 光反射方向:reflect(入射光的方向,当前点的法线方向)

				// 世界模式下的法向量的标准化	
				fixed3 worldNormal = normalize(mul((float3x3)unity_ObjectToWorld, c.normal));
				// 标准化后的观察方向向量 = (世界模式的相机的位置 - 世界模式的点的位置)的标准化
				float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, c.vertex).xyz);
				// 标准化后的反射方向(_WorldSpaceLightPos0是物体到光的方向)
				fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz), worldNormal));
				//高光计算公式
				fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(viewDir, refDir)), _Gloss);

				// 【3】根据Phong光照模型,将环境光追加在最终计算后的颜色上
				v.color = UNITY_LIGHTMODEL_AMBIENT.xyz + specular;

				return v;

			}

			fixed4 frag(v2f data) : SV_Target
			{
				// //将顶点着色器计算好的颜色,传递给GPU
				return fixed4(data.color, 1.0);
			}

            		ENDCG
        }
    }
}

逐像素高光反射

效果演示:
image.png
代码示例:

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

Shader "DY/SpecularPixel"
{
	Properties
	{
		_SpecularColor("高光显示颜色", Color) = (1,1,1,1)
		// Range给这个属性限定一个范围
		_Gloss("光晕系数", Range(4, 256)) = 10
	}

	SubShader
	{

		Pass
		{
			//设定光照模式为前向模式(才能正常获取光的颜色和光的方向)
			Tags { "LightMode" = "ForwardBase" }

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			// 处理光照的一些库
			#include "Lighting.cginc"

			fixed4 _SpecularColor;
			fixed _Gloss;

			// 顶点着色器回调函数
			// 需要什么?【1】模型空间下的点 【2】模型空间下点的法向量
			struct c2v
			{
				float4 vertex:POSITION;
				float3 normal:NORMAL;
			};

			// 返回什么? 【1】裁剪空间下的点 【2】世界空间的法向量 【3】 世界空间下,点的位置
			struct v2f {
				float4 pos:SV_POSITION; // 裁剪空间下的点
				float3 worldPos : TEXCOORD0; //借用纹理的语义,实现世界空间下,点的位置传递
				float3 worldNormal:NORMAL; // 世界空间下法线的方向
			};

			// 想要做什么?
			// 【1】将模型空间下的点,转化为裁剪空间下的点
			// 【2】将在模型空间下的法向量,转化为世界空间下的法向量
			v2f vert(c2v c)
			{
				v2f v;
				// 【1】
				v.pos = UnityObjectToClipPos(c.vertex);
				// 【2】
				v.worldNormal = mul((float3x3)unity_ObjectToWorld, c.normal);
				// 【3】
				v.worldPos = mul(unity_ObjectToWorld, c.vertex).xyz;
				return v;

			}

			// 要做什么?在片元着色器中计算高光颜色
			fixed4 frag(v2f data) : SV_Target
			{
				// 高光反射运算
				// 高光光照 = 光源的颜色 * 材质高光反射颜色 * 〖MAX(0,标准化后的观察方向向量 • 标准化后的反射方向)〗^ 光晕系数
				// 光反射方向:reflect(入射光的方向,当前点的法线方向)

				// 世界模式下的法向量的标准化	
				fixed3 worldNormal = normalize(data.worldNormal);
				// 标准化后的观察方向向量 = (世界模式的相机的位置 - 世界模式的点的位置)的标准化
				float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - data.worldPos);
				// 标准化后的反射方向(_WorldSpaceLightPos0是物体到光的方向)
				fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz), worldNormal));
				//高光计算公式
				fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(viewDir, refDir)), _Gloss);

				// 根据Phong光照模型,将环境光追加在最终计算后的颜色上
				fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz + specular;


				return fixed4(color, 1.0);
			}
            ENDCG
        }
    }
}

逐顶底高光 与 逐像素高光 对比

image.png

Phong光照模型(逐顶点、逐像素)

效果演示:
image.png
逐像素代码示例:

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

Shader "DY/PhongPixel"
{
	Properties
	{
		_DiffuseColor("漫反射颜色", Color) = (1,1,1,1)
		_SpecularColor("高光显示颜色", Color) = (1,1,1,1)
		// Range给这个属性限定一个范围
		_Gloss("光晕系数", Range(4, 256)) = 10
	}
		SubShader
	{

		Pass
		{
			//设定光照模式为前向模式(才能正常获取光的颜色和光的方向)
			Tags { "LightMode" = "ForwardBase" }

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			// 处理光照的一些库
			#include "Lighting.cginc"

			fixed4 _SpecularColor;
			fixed _Gloss;
			fixed4 _DiffuseColor;

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

			struct v2f {
				float4 pos:SV_POSITION;
				float3 worldNormal:NORMAL;
				float3 worldPos : TEXCOORD0;
			};


			v2f vert(c2v c)
			{
				v2f v;
				v.pos = UnityObjectToClipPos(c.vertex);

				v.worldNormal = mul((float3x3)unity_ObjectToWorld, c.normal);
				v.worldPos = mul(unity_ObjectToWorld, c.vertex).xyz;

				return v;

			}

			fixed4 frag(v2f data) : SV_Target
			{
				fixed3 worldNormal = normalize(data.worldNormal);

				// 高光光照 = 光源的颜色 * 材质高光反射颜色 * 〖MAX(0,标准化后的观察方向向量 • 标准化后的反射方向)〗^ 光晕系数
				float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - data.worldPos);
				fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz), worldNormal));
				fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(viewDir, refDir)), _Gloss);

				// 漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0, 标准化后物体表面法线向量 • 标准化后光源方向向量)
				fixed3 diffuse = _LightColor0.rgb * _DiffuseColor * max(0, dot(worldNormal, normalize(_WorldSpaceLightPos0.xyz)));

				// 根据Phong光照模型,将环境光追加在最终计算后的颜色上
				fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz + diffuse + specular;

				return fixed4(color, 1.0);
			}
            ENDCG
        }
    }
}

逐顶点代码示例:

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

Shader "DY/PhongVetex"
{
	Properties
	{
		_DiffuseColor("漫反射颜色", Color) = (1,1,1,1)
		_SpecularColor("高光显示颜色", Color) = (1,1,1,1)
		// Range给这个属性限定一个范围
		_Gloss("光晕系数", Range(4, 256)) = 10
	}
		SubShader
	{

		Pass
		{
			//设定光照模式为前向模式(才能正常获取光的颜色和光的方向)
			Tags { "LightMode" = "ForwardBase" }

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			// 处理光照的一些库
			#include "Lighting.cginc"

			fixed4 _SpecularColor;
			fixed _Gloss;
			fixed4 _DiffuseColor;

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

			struct v2f {
				float4 pos:SV_POSITION;
				fixed3 color : COLOR;
			};


			v2f vert(c2v c)
			{
				v2f v;
				v.pos = UnityObjectToClipPos(c.vertex);

				fixed3 worldNormal = normalize(mul((float3x3)unity_ObjectToWorld, c.normal));
				float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, c.vertex).xyz);
				fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz), worldNormal));
				//高光计算公式
				fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(viewDir, refDir)), _Gloss);

				// 漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0, 标准化后物体表面法线向量 • 标准化后光源方向向量)
				fixed3 diffuse = _LightColor0.rgb * _DiffuseColor * max(0, dot(worldNormal, normalize(_WorldSpaceLightPos0.xyz)));

				// 根据Phong光照模型,将环境光追加在最终计算后的颜色上
				v.color = UNITY_LIGHTMODEL_AMBIENT.xyz + diffuse + specular;

				return v;

			}

			fixed4 frag(v2f data) : SV_Target
			{
				// //将顶点着色器计算好的颜色,传递给GPU
				return fixed4(data.color, 1.0);
			}
            ENDCG
        }
    }
}