观前提示:本文章是个人笔记,只是给自己看的,我自己看得懂就行。
一、逐顶点光照和逐像素光照
1、逐顶点光照
逐顶点光照或高洛德着色(Gouraud shading), 在每个顶点上计算光照,然后在渲染图元内部进行线性插值,输出成像素颜色。而顶点数目通常会远小于像素数目,所以逐顶点光照的计算量往往更小。
2、逐像素光照
逐像素光照或Phong着色,先在渲染图元内部进行线性插值,然后在每个像素上计算光照、像素颜色。像素数目远大于顶点数目,逐像素光照的计算量更大且计算更复杂,性能消耗也更大,但效果好,光照更加细腻精准。
二、漫反射光照模型
漫反射光照是用于被物体表面随机散射到各个方向的辐射度进行的建模,也被称为兰伯特光照模型。在漫反射中视角的位置是不重要的,因为反射是完全随机的,所以在任何反射方向都是随机的。但是入射光线的角度很重要。
漫反射光照(兰伯特定律)
漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0, 标准化后物体表面法线向量· 标准化后光源方向向量)
光源颜色:场景中光GameObject取得
材质的漫反射颜色:材质球配置
Max是数学函数,标准化也有函数
表面法线向量:CPU加载模型后,传递到GPU中的
光源方向向量:场景中光GameObject取得
逐顶点漫反射光照代码:
Shader "test/DiffuseVertex"
{
Properties
{
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
}
SubShader
{
Pass
{
//设定光照模式为前向模式(只有设置了光照模式,才能正常获取光的颜色和方向)
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//加载Cg语言的脚本,用来 处理光照参数
//处理光照的Cg库文件(cginc扩展名),目录在Unity的安装目录下Editor/Data/CGIncludes/Lighting.cginc
#include "Lighting.cginc"
//导入材质颜色
fixed4 _DiffuseColor;
//如果在Cg编程中,顶点或片元着色器接受多个参数的时候,一般会用结构体实现
//从CPU接收到的数据
struct c2v
{
float4 vertex:POSITION;//从CPU接收到的模型空间下的点的位置
float3 normal:NORMAL;//从CPU接收到的当前点的模型空间下的法线向量
};
struct v2f
{
float4 pos:SV_POSITION;//经过顶点着色器计算后的,当前点的裁剪空间下的位置
fixed3 color:COLOR;//经过兰伯特定律计算后的当前点的颜色
};
//高洛德着色(逐顶点光照),光照计算应该编写在顶点着色器上中
v2f vert(c2v data)
{
//顶点着色器传递给片元着色器的数据结构体声明
v2f r;
//必须要做的,将点从模型空间下,转化到裁剪空间下
r.pos = UnityObjectToClipPos(data.vertex);
//光照是在世界中发生的,需要将所有的数值,变换到世界坐标系下,再运算
//_Object2World矩阵是Unity提供的用于转换模型空间下到世界空间下的转换矩阵
//因为法线传递过来的是3x1的列矩阵,_Object2World是4x4的齐次矩阵,如果想做矩阵乘法,需要将齐次矩阵,变成3x3的矩阵
fixed3 worldNormal = normalize(mul((float3x3)unity_ObjectToWorld,data.normal));
//获得直射光的光方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//兰伯特定律计算
//漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0,标准化后物体表面法线向量 * 标准化后光源方向向量)
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * max(0,dot(worldNormal,worldLightDir));
//根据Phong光照模型,将环境光追加在最终计算后的颜色上
r.color = UNITY_LIGHTMODEL_AMBIENT.xyz + diffuse;
return r;
}
fixed4 frag(v2f data):SV_Target
{
return fixed4(data.color,1.0);
}
ENDCG
}
}
FallBack "DIFFUSE"
}
逐像素漫反射光照代码:
Shader "test/DiffusePixel"
{
Properties
{
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
}
SubShader
{
Pass
{
//设定光照模式为前向模式(只有设置了光照模式,才能正常获取光的颜色和方向)
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//加载Cg语言的脚本,用来 处理光照参数
//处理光照的Cg库文件(cginc扩展名),目录在Unity的安装目录下Editor/Data/CGIncludes/Lighting.cginc
#include "Lighting.cginc"
//导入材质颜色
fixed4 _DiffuseColor;
//如果在Cg编程中,顶点或片元着色器接受多个参数的时候,一般会用结构体实现
//从CPU接收到的数据
struct c2v
{
float4 vertex:POSITION;//从CPU接收到的模型空间下的点的位置
float3 normal:NORMAL;//从CPU接收到的当前点的模型空间下的法线向量
};
//因为在顶点着色器中,需要计算裁剪空间下点的位置和高洛德着色计算出来的兰伯特定律计算后的颜色
struct v2f
{
float4 pos:SV_POSITION;//经过顶点着色器计算后的,当前点的裁剪空间下的位置
fixed3 worldNormal:NORMAL;//经过矩阵转换后的法线向量
};
//顶点着色器将渲染点从模型空间下转换到裁剪空间下
//将舒安然点对应的法线向量,从模型空间下转换到世界空间下
v2f vert(c2v data)
{
//顶点着色器传递给片元着色器的数据结构体声明
v2f r;
//必须要做的,将点从模型空间下,转化到裁剪空间下
r.pos = UnityObjectToClipPos(data.vertex);
//几何运算,在顶点着色器中完成,再将运算好的数值,传递给片元着色器
//_Object2World矩阵是Unity提供的用于转换模型空间下到世界空间下的转换矩阵
//因为法线传递过来的是3x1的列矩阵,_Object2World是4x4的齐次矩阵,如果想做矩阵乘法,需要将齐次矩阵,变成3x3的矩阵
r.worldNormal = mul((float3x3)unity_ObjectToWorld,data.normal);
return r;
}
fixed4 frag(v2f data):SV_Target
{
//光照是在世界中发生的,需要将所有的数值,变换到世界坐标系下,再运算
//获得直射光的光方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//兰伯特定律计算
//漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0,标准化后物体表面法线向量 * 标准化后光源方向向量)
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * max(0,dot(normalize(data.worldNormal),worldLightDir));
//根据Phong光照模型,将环境光追加在最终计算后的颜色上
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz + diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "DIFFUSE"
}
半兰伯特定律,将整体颜色降低一半,再加一半
漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * ((标准化后物体表面法线向量· 标准化后光源方向向量) * 0.5 + 0.5)
半兰伯特定律代码:
Shader "test/HalfLambert"
{
Properties
{
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
}
SubShader
{
Pass
{
//设定光照模式为前向模式(只有设置了光照模式,才能正常获取光的颜色和方向)
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//加载Cg语言的脚本,用来 处理光照参数
//处理光照的Cg库文件(cginc扩展名),目录在Unity的安装目录下Editor/Data/CGIncludes/Lighting.cginc
#include "Lighting.cginc"
//导入材质颜色
fixed4 _DiffuseColor;
//如果在Cg编程中,顶点或片元着色器接受多个参数的时候,一般会用结构体实现
//从CPU接收到的数据
struct c2v
{
float4 vertex:POSITION;//从CPU接收到的模型空间下的点的位置
float3 normal:NORMAL;//从CPU接收到的当前点的模型空间下的法线向量
};
//因为在顶点着色器中,需要计算裁剪空间下点的位置和高洛德着色计算出来的兰伯特定律计算后的颜色
struct v2f
{
float4 pos:SV_POSITION;//经过顶点着色器计算后的,当前点的裁剪空间下的位置
fixed3 worldNormal:NORMAL;//经过矩阵转换后的法线向量
};
//顶点着色器将渲染点从模型空间下转换到裁剪空间下
//将舒安然点对应的法线向量,从模型空间下转换到世界空间下
v2f vert(c2v data)
{
//顶点着色器传递给片元着色器的数据结构体声明
v2f r;
//必须要做的,将点从模型空间下,转化到裁剪空间下
r.pos = UnityObjectToClipPos(data.vertex);
//几何运算,在顶点着色器中完成,再将运算好的数值,传递给片元着色器
//_Object2World矩阵是Unity提供的用于转换模型空间下到世界空间下的转换矩阵
//因为法线传递过来的是3x1的列矩阵,_Object2World是4x4的齐次矩阵,如果想做矩阵乘法,需要将齐次矩阵,变成3x3的矩阵
r.worldNormal = mul((float3x3)unity_ObjectToWorld,data.normal);
return r;
}
fixed4 frag(v2f data):SV_Target
{
//光照是在世界中发生的,需要将所有的数值,变换到世界坐标系下,再运算
//获得直射光的光方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//半兰伯特定律计算
//漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * ((标准化后物体表面法线向量· 标准化后光源方向向量) * 0.5 + 0.5)
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * (dot(normalize(data.worldNormal),worldLightDir) * 0.5 + 0.5);
//根据Phong光照模型,将环境光追加在最终计算后的颜色上
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz + diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "DIFFUSE"
}
三、高光反射光照模型
高光反射可用于那些沿着完全镜面反射方向被反射的光线,可以让物体看起来是有光泽的。在现实世界中,粗糙的物体一般会是漫反射,而光滑的物体呈现得较多的就是镜面反射,最明显的现象就是光线照射的反射方向有一个亮斑。
逐顶点高光反射光照代码:
Shader "test/SpecularVertex"
{
Properties
{
//高光反射材质颜色
_SpecularColor("SpecularColor",Color) = (1,1,1,1)
//光晕系数,控制光斑的大小
_Gloss("Gloss",Range(4,256)) = 10
}
SubShader
{
Pass
{
//设定光照模式为前向模式(只有设置了光照模式,才能正常获取光的颜色和方向)
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//加载Cg语言的脚本,用来 处理光照参数
//处理光照的Cg库文件(cginc扩展名),目录在Unity的安装目录下Editor/Data/CGIncludes/Lighting.cginc
#include "Lighting.cginc"
//导入材质颜色
fixed4 _SpecularColor;
//导入高光光晕大小系数
fixed _Gloss;
//如果在Cg编程中,顶点或片元着色器接受多个参数的时候,一般会用结构体实现
//从CPU接收到的数据
struct c2v
{
float4 vertex:POSITION;//从CPU接收到的模型空间下的点的位置
float3 normal:NORMAL;//从CPU接收到的当前点的模型空间下的法线向量
};
struct v2f
{
float4 pos:SV_POSITION;//经过顶点着色器计算后的,当前点的裁剪空间下的位置
fixed3 color:COLOR;//经过兰伯特定律计算后的当前点的颜色
};
v2f vert(c2v data)
{
//顶点着色器传递给片元着色器的数据结构体声明
v2f r;
//必须要做的,将点从模型空间下,转化到裁剪空间下
r.pos = UnityObjectToClipPos(data.vertex);
//光照是在世界中发生的,需要将所有的数值,变换到世界坐标系下,再运算
//_Object2World矩阵是Unity提供的用于转换模型空间下到世界空间下的转换矩阵
//因为法线传递过来的是3x1的列矩阵,_Object2World是4x4的齐次矩阵,如果想做矩阵乘法,需要将齐次矩阵,变成3x3的矩阵
fixed3 worldNormal = normalize(mul((float3x3)unity_ObjectToWorld,data.normal));
//观察方向(相机的位置 - 被渲染点的位置,是在世界空间下的坐标)
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, data.vertex).xyz);
//光线反射的方向:reflect(入射光方向,当前点的法线向量)
fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz),worldNormal));
//高光反射计算
//高光光照 = 光源的颜色 * 材质的高光反射颜色 * MAX(0,标准化后的观察方向 * 标准化后的反射方向)^光晕系数
fixed3 Specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(viewDir,refDir)),_Gloss);
//根据Phong光照模型,将环境光追加在最终计算后的颜色上
r.color = UNITY_LIGHTMODEL_AMBIENT.xyz + Specular;
return r;
}
fixed4 frag(v2f data):SV_Target
{
return fixed4(data.color,1.0);
}
ENDCG
}
}
FallBack "DIFFUSE"
}
逐像素高光反射代码:
Shader "test/SpecularPixel"
{
Properties
{
//高光反射材质颜色
_SpecularColor("SpecularColor",Color) = (1,1,1,1)
//光晕系数,控制光斑的大小
_Gloss("Gloss",Range(4,256)) = 10
}
SubShader
{
Pass
{
//设定光照模式为前向模式(只有设置了光照模式,才能正常获取光的颜色和方向)
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//加载Cg语言的脚本,用来 处理光照参数
//处理光照的Cg库文件(cginc扩展名),目录在Unity的安装目录下Editor/Data/CGIncludes/Lighting.cginc
#include "Lighting.cginc"
//导入材质颜色
fixed4 _SpecularColor;
//导入高光光晕大小系数
fixed _Gloss;
//如果在Cg编程中,顶点或片元着色器接受多个参数的时候,一般会用结构体实现
//从CPU接收到的数据
struct c2v
{
float4 vertex:POSITION;//从CPU接收到的模型空间下的点的位置
float3 normal:NORMAL;//从CPU接收到的当前点的模型空间下的法线向量
};
struct v2f
{
float4 pos:SV_POSITION;//经过顶点着色器计算后的,当前点的裁剪空间下的位置
float4 worldPos:TEXCOORD0;//借用纹理的语义实现世界空间下点的位置传递
float3 normal:NORMAL;//世界空间下,法线的方向
};
v2f vert(c2v data)
{
//顶点着色器传递给片元着色器的数据结构体声明
v2f r;
//必须要做的,将点从模型空间下,转化到裁剪空间下
r.pos = UnityObjectToClipPos(data.vertex);
//被渲染点的位置,是在世界空间下的坐标
r.worldPos = mul(unity_ObjectToWorld, data.vertex);
//世界坐标下,法线的方向向量
r.normal = mul((float3x3)unity_ObjectToWorld,data.normal);
return r;
}
fixed4 frag(v2f data):SV_Target
{
//将顶点着色器传过来的法线向量标准化
fixed3 worldNormal = normalize(data.normal);
//使用相机位置减去顶点着色器计算的世界坐标系下的点的位置,并标准化
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - data.worldPos.xyz);
//光线反射的方向:reflect(入射光方向,当前点的法线向量)
fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz),worldNormal));
//高光反射计算
//高光光照 = 光源的颜色 * 材质的高光反射颜色 * MAX(0,标准化后的观察方向 * 标准化后的反射方向)^光晕系数
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
}
}
FallBack "DIFFUSE"
}
四、Phong光照模型
标准光照的构成结构:
自发光:材质本身发出的光,模拟环境使用的光。
漫反射光:光照到粗糙材质后,光的反射方向随机,还有一些光发生了折射,造成材质表面没有明显的光斑。
高光反射光:光照到材质表面后,无(低)损失直接反射给观察者眼睛,材质表面能观察到光斑。
环境光:模拟场景光照(简单理解为太阳光)。
裴祥凤提出的光照理论:标准光照 = 自发光 + 漫反射光 + 高光反射光 + 环境光
这个理论是模拟光照效果,并不是真实效果。以他的名字命名:Phong光照模型。
逐顶点Phong光照代码:
Shader "test/PhongVertex"
{
Properties
{
//漫反射反射材质颜色
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
//高光反射材质颜色
_SpecularColor("SpecularColor",Color) = (1,1,1,1)
//光晕系数
_Gloss("Gloss",Range(4,256)) = 10
}
SubShader
{
Pass
{
//设定光照模式为前向模式(只有设置了光照模式,才能正常获取光的颜色和方向)
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//加载Cg语言的脚本,用来 处理光照参数
//处理光照的Cg库文件(cginc扩展名),目录在Unity的安装目录下Editor/Data/CGIncludes/Lighting.cginc
#include "Lighting.cginc"
//导入漫反射反射材质颜色
fixed4 _DiffuseColor;
//导入高光反射材质颜色
fixed4 _SpecularColor;
//导入高光光晕大小系数
fixed _Gloss;
//如果在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 r;
//必须要做的,将点从模型空间下,转化到裁剪空间下
r.pos = UnityObjectToClipPos(data.vertex);
//标准化后物体表面法线向量
fixed3 worldNormal = normalize(mul((float3x3)unity_ObjectToWorld,data.normal));
//观察方向(相机的位置 - 被渲染点的位置,是在世界空间下的坐标)
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, data.vertex).xyz);
//光线反射的方向:reflect(入射光方向,当前点的法线向量)
fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz),worldNormal));
//高光反射计算
//高光光照 = 光源的颜色 * 材质的高光反射颜色 * MAX(0,标准化后的观察方向 * 标准化后的反射方向)^光晕系数
fixed3 Specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(viewDir,refDir)),_Gloss);
//标准化后光源方向向量
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//兰伯特定律计算
//漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0,标准化后物体表面法线向量 * 标准化后光源方向向量)
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * max(0,dot(worldNormal,worldLightDir));
//根据Phong光照模型,将环境光追加在最终计算后的颜色上
r.color = UNITY_LIGHTMODEL_AMBIENT.xyz +diffuse + Specular;
return r;
}
fixed4 frag(v2f data):SV_Target
{
return fixed4(data.color,1.0);
}
ENDCG
}
}
}
逐像素Phong光照代码:
Shader "test/PhongPixel"
{
Properties
{
//漫反射反射材质颜色
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
//高光反射材质颜色
_SpecularColor("SpecularColor",Color) = (1,1,1,1)
//光晕系数
_Gloss("Gloss",Range(4,256)) = 10
}
SubShader
{
Pass
{
//设定光照模式为前向模式(只有设置了光照模式,才能正常获取光的颜色和方向)
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//加载Cg语言的脚本,用来 处理光照参数
//处理光照的Cg库文件(cginc扩展名),目录在Unity的安装目录下Editor/Data/CGIncludes/Lighting.cginc
#include "Lighting.cginc"
//导入漫反射反射材质颜色
fixed4 _DiffuseColor;
//导入高光反射材质颜色
fixed4 _SpecularColor;
//导入高光光晕大小系数
fixed _Gloss;
//如果在Cg编程中,顶点或片元着色器接受多个参数的时候,一般会用结构体实现
//从CPU接收到的数据
struct c2v
{
float4 vertex:POSITION;//从CPU接收到的模型空间下的点的位置
float3 normal:NORMAL;//从CPU接收到的当前点的模型空间下的法线向量
};
//因为在顶点着色器中,运算Phong光照模型
struct v2f
{
float4 pos:SV_POSITION;//经过顶点着色器计算后的,当前点的裁剪空间下的位置
float4 worldPos:TEXCOORD0;//借用纹理的语义实现世界空间下点的位置传递
float3 worldNormal:NORMAL;//世界空间下,法线的方向
};
v2f vert(c2v data)
{
//顶点着色器传递给片元着色器的数据结构体声明
v2f r;
//必须要做的,将点从模型空间下,转化到裁剪空间下
r.pos = UnityObjectToClipPos(data.vertex);
//被渲染点的位置,是在世界空间下的坐标
r.worldPos = mul(unity_ObjectToWorld, data.vertex);
//世界坐标下,法线的方向向量
r.worldNormal = mul((float3x3)unity_ObjectToWorld,data.normal);
return r;
}
fixed4 frag(v2f data):SV_Target
{
//将顶点着色器传过来的法线向量标准化
fixed3 worldNormal = normalize(data.worldNormal);
//使用相机位置减去顶点着色器计算的世界坐标系下的点的位置,并标准化
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - data.worldPos.xyz);
//光线反射的方向:reflect(入射光方向,当前点的法线向量)
fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz),worldNormal));
//高光反射计算
//高光光照 = 光源的颜色 * 材质的高光反射颜色 * MAX(0,标准化后的观察方向 * 标准化后的反射方向)^光晕系数
fixed3 Specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(viewDir,refDir)),_Gloss);
//获得直射光的光方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//兰伯特定律计算
//漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0,标准化后物体表面法线向量 * 标准化后光源方向向量)
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * max(0,dot(worldNormal,worldLightDir));
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz +diffuse + Specular;
return fixed4(color,1.0);
}
ENDCG
}
}
}