内容概述
- 逐顶点光照和逐像素光照
- 漫反射光照模型
- 高光反射光照模型
- Phone标准光照模型
标准光照的构成结构
- 自发光:材质本身发出的光,模拟环境使用的光
- 漫反射光:光照到粗糙材质后,光的反射方向随机,还有一些光发生了折射,造成材质表面没有明显的光斑
- 高光反射光:照到材质表面后,无(低)损失直接反射给观察者眼睛,材质表面能观察到光斑
- 环境光:模拟场景光照(简单理解为太阳光)
- 裴祥凤提出的光照理论:标准光照 = 自发光 + 漫反射光 + 高光反射光 + 环境光
逐顶点光照和逐像素光照
顶点着色器:会在模型渲染点上运行,其他的点会线性插值
我的理解:
片元着色器:会在模型的所有像素点上运行
我的理解:上面的图形就会是全红。
逐顶点光照:会在顶点着色器上进行光照运算
逐像素光照:会在片元着色器上运行光照运算
逐像素比逐顶点效果好,逐顶点比逐像素性能好
兰伯特定律和半兰伯特定律
漫反射光照(兰伯特定律)
漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * MAX(0, 标准化后物体表面法线向量· 标准化后光源方向向量)
光源颜色:场景中光GameObject取得
材质的漫反射颜色:材质球配置
Max是数学函数,标准化也有函数
表面法线向量:CPU加载模型后,传递到GPU中的
光源方向向量:场景中光GameObject取得
实现:所有数据拿到,拿公式计算
半兰伯特定律
漫反射光照 = 光源的颜色 * 材质的漫反射颜色 * ((标准化后物体表面法线向量· 标准化后光源方向向量) * 0.5 + 0.5))
将整体颜色降低一半,再加一半
兰伯特定律 和 半兰伯特定律 的区别
这个的目的是让物体的背面不在那么黑
常用矩阵
矩阵名 | 说明 |
---|---|
UNITY_MATRIX_MVP | 将点从模型空间下,转换到裁剪空间下 |
UNITY_MATRIX_M | 点从模型空间下,转换到世界空间下 |
UNITY_MATRIX_V | 将点从世界空间下,转换到观察空间下 |
UNITY_MATRIX_P | 将点从观察空间下,转换到裁剪空间下 |
_Object2World | 将点从模型到世界空间转换(互为逆矩阵) |
_World2Object | 将点从世界空间到模型转换(互为逆矩阵) |
常用参数
column1 | column2 |
---|---|
_WorldSpaceLightPos0.xyz | 获取直射光的方向(物体到光的方向) |
_LightColor0.rgb | 获取光源的颜色 |
UNITY_LIGHTMODEL_AMBIENT.xyz | 环境光的颜色 |
_WorldSpaceCameraPos | 世界模式下。相机的位置 |
常用api
|几何函数|说明|
|normalize(v)|归一化向量|
|reflect(I,N)|根据入射光方向向量和顶点法向量N,计算发射光方向向量。其中I和N必须被归一化。
注意:这里的I是指向顶点的;函数只对三元向量有效|
编写简单的逐顶点漫反射
效果演示:
为什么会产生锯齿,因为采用了差值计算来实现的。
// 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
}
}
}
编写简单的逐顶点漫反射
效果演示:
很明显比逐像素更加的平滑
// 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(入射光的方向,当前点的法线方向)
光晕系数:材质球配置
效果演示:
代码示例:
// 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
}
}
}
逐像素高光反射
效果演示:
代码示例:
// 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
}
}
}
逐顶底高光 与 逐像素高光 对比
Phong光照模型(逐顶点、逐像素)
效果演示:
逐像素代码示例:
// 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
}
}
}
评论区