什么是光照模型
光照模型就是一个公式,使用这个公式来计算在某个点的光照效果
标准光照模型
在标准光照模型里面,我们把进入摄像机的光分为下面四个部分
自发光
高光反射
Blinn光照模型
Specular=直射光 * pow( max(cosθ,0),10) θ:是反射光方向和视野方向的夹角
Blinn-Phong光照模型
Specular=直射光 * pow( max(cosθ,0),10) θ:是法线和x的夹角 x 是平行光和视野方向的平分线
漫反射 Diffuse = 直射光颜色 * max(0,cos夹角(光和法线的夹角) ) cosθ = 光方向· 法线方向
环境光
Tags{ "LightMode"="ForwardBase" }
只有定义了正确的LightMode才能得到一些Unity的内置光照变量
include "Lighting.cginc"
包含unity的内置的文件,才可以使用unity内置的一些变量
normalize() 用来把一个向量,单位化(原来方向保持不变,长度变为1)
max() 用来取得函数中最大的一个
dot 用来取得两个向量的点积
_WorldSpaceLightPos0 取得平行光的位置
_LightColor0取得平行光的颜色
UNITY_MATRIX_MVP 这个矩阵用来把一个坐标从模型空间转换到剪裁空间
_World2Object 这个矩阵用来把一个方向从世界空间转换到模型空间
UNITY_LIGHTMODEL_AMBIENT用来获取环境光
(1)兰伯特光照模型计算公式(漫反射计算方式)
(漫反射)Diffuse = 直射光颜色 * max(0,cos夹角(光和法线的夹角) ) cosθ = 光方向· 法线方向
下面我们使用逐顶点光照和逐像素光照来实现兰伯特光照模型计算公式。
逐顶点光照
两种方式可以实现漫反射 第一种是逐顶点实现漫反射,第二种方式是逐像素实现漫反射。
逐顶点就是将光照的计算方式放在顶点函数里面,因为顶点个数是有限的,而像素个数是无限的,逐顶点计算是顶点与顶点之间的计算时是使用了插值的计算方式计算,这样比较平和,但是顶点是直接计算出来的所以顶点上不会太平和。下面我们来实现第一种逐顶点。
Shader"Diffuse"{
properties{
_Diffuse("Color",Color) = (1,1,1,1)
}
SubShader{
Pass {
//定义正确的LightMode得到内置的光照变量
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
//包含unity的内置的文件,才可以使用unity内置的一些变量 相当于引入内置的光照模型
#include "Lighting.cginc"//取得第一个直射光的颜色 _LightColor0第一个值是光的位置_WorldSpaceLightPos0
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
struct a2v {
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f {
float4 position:SV_POSITION;
fixed3 color : COLOR;
};
v2f vert(a2v v) {
v2f f;
//UNITY_MATRIX_MVP 这个矩阵用来把一个坐标从模型空间转换到剪裁空间
f.position = mul(UNITY_MATRIX_MVP, v.vertex);
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
//_World2Object 这个矩阵用来把一个方向从世界空间转换到模型空间
fixed3 normalDir = normalize(mul(v.normal, (float3x3)unity_WorldToObject));//相当于把模型空间转换到世界空间
//normalize() 用来把一个向量,单位化(原来方向保持不变,长度变为1)
//_WorldSpaceLightPos0 取得平行光的位置
fixed3 lightDir = normalize( _WorldSpaceLightPos0.xyz);//对于每一个顶点来说 光的位置就是光的方向 因为光是平行光
//_LightColor0取得平行光的颜色
//max() 用来取得函数中最大的一个
//dot 用来取得两个向量的点积
fixed3 diffuse = _LightColor0.rgb*max(0, dot(normalDir, lightDir))*_Diffuse.rgb;//取得漫反射的颜色
f.color = diffuse+ ambient;
return f;
}
fixed4 frag(v2f f) :SV_Target{
return fixed4(f.color,1);
}
ENDCG
}
}
Fallback"VertexLit"
}
因为在程序里面设置了环境光所以在暗部会受到环境光的影响
这里设置受SkyBox环境光的影响,也可以自己将其他物体或者颜色设置成环境光颜色
逐像素光照
逐顶点光照没有逐像素光照渲染的平和 ,但是逐像素光照渲染更加耗费性能
Shader"Fragment"{
properties{
_Diffuse("Color",Color) = (1,1,1,1)
}
SubShader{
Pass {
//定义正确的LightMode得到内置的光照变量
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
//包含unity的内置的文件,才可以使用unity内置的一些变量 相当于引入内置的光照模型
#include "Lighting.cginc"//取得第一个直射光的颜色 _LightColor0第一个值是光的位置_WorldSpaceLightPos0
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
struct a2v {
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f {
float4 position:SV_POSITION;
fixed3 worldNormalDir : COLOR0;
};
v2f vert(a2v v) {
v2f f;
//UNITY_MATRIX_MVP 这个矩阵用来把一个坐标从模型空间转换到剪裁空间
f.position = mul(UNITY_MATRIX_MVP, v.vertex);
f.worldNormalDir = mul(v.normal, (float3x3)unity_WorldToObject);
return f;
}
fixed4 frag(v2f f) :SV_Target{
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
//_World2Object 这个矩阵用来把一个方向从世界空间转换到模型空间
fixed3 normalDir = normalize(f.worldNormalDir);//相当于把模型空间转换到世界空间
//normalize() 用来把一个向量,单位化(原来方向保持不变,长度变为1)
//_WorldSpaceLightPos0 取得平行光的位置
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//对于每一个顶点来说 光的位置就是光的方向 因为光是平行光
//_LightColor0取得平行光的颜色
//max() 用来取得函数中最大的一个
//dot 用来取得两个向量的点积
fixed3 diffuse = _LightColor0.rgb*max(0, dot(normalDir, lightDir))*_Diffuse.rgb;//取得漫反射的颜色
fixed3 tempColor = diffuse + ambient;
return fixed4(tempColor,1);
}
ENDCG
}
}
Fallback"Diffuse"
}
(2)半兰伯特光照模型计算公式(漫反射计算方式)
Diffuse = 直射光颜色 *( cosθ *0.5 +0.5 )
因为兰伯特光照模型在背光面会完全看不清,所以半兰伯特光照模型应时而生。
Shader"Lambert"{
properties{
_Diffuse("Color",Color) = (1,1,1,1)
}
SubShader{
Pass {
//定义正确的LightMode得到内置的光照变量
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
//包含unity的内置的文件,才可以使用unity内置的一些变量 相当于引入内置的光照模型
#include "Lighting.cginc"//取得第一个直射光的颜色 _LightColor0第一个值是光的位置_WorldSpaceLightPos0
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
struct a2v {
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f {
float4 position:SV_POSITION;
fixed3 worldNormalDir : COLOR0;
};
v2f vert(a2v v) {
v2f f;
//UNITY_MATRIX_MVP 这个矩阵用来把一个坐标从模型空间转换到剪裁空间
f.position = mul(UNITY_MATRIX_MVP, v.vertex);
f.worldNormalDir = mul(v.normal, (float3x3)unity_WorldToObject);
return f;
}
fixed4 frag(v2f f) :SV_Target{
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
//_World2Object 这个矩阵用来把一个方向从世界空间转换到模型空间
fixed3 normalDir = normalize(f.worldNormalDir);//相当于把模型空间转换到世界空间
//normalize() 用来把一个向量,单位化(原来方向保持不变,长度变为1)
//_WorldSpaceLightPos0 取得平行光的位置
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//对于每一个顶点来说 光的位置就是光的方向 因为光是平行光
float halfLambert = dot(normalDir, lightDir)*0.5 + 0.5;
//_LightColor0取得平行光的颜色
//max() 用来取得函数中最大的一个
//dot 用来取得两个向量的点积
fixed3 diffuse = _LightColor0.rgb*halfLambert*_Diffuse.rgb;//取得漫反射的颜色
fixed3 tempColor = diffuse + ambient;
return fixed4(tempColor,1);
}
ENDCG
}
}
Fallback"Diffuse"
}
这样对比一下背光面兰伯特光照模型和半兰伯特光照模型。可以看到很明显的差别,半兰伯特光照模型即使暗部就不会是全黑。
(3)高光反射计算方式
Blinn光照模型 计算公式
Specular=直射光 * pow( max(cosθ,0),10) θ:是反射光方向和视野方向的夹角
这里使用逐顶点光照模型写出高光反射。
Shader"Specular"{
properties{
_Diffuse("Color",Color) = (1,1,1,1)
_Specular("Specular Color",Color)=(1,1,1,1)//控制高光颜色
_Gloss("Gloss",Range(8,200))=10//控制高光强度
}
SubShader{
Pass {
//定义正确的LightMode得到内置的光照变量
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
//包含unity的内置的文件,才可以使用unity内置的一些变量 相当于引入内置的光照模型
#include "Lighting.cginc"//取得第一个直射光的颜色 _LightColor0第一个值是光的位置_WorldSpaceLightPos0
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
half _Gloss;
fixed4 _Specular;
struct a2v {
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f {
float4 position:SV_POSITION;
fixed3 color : COLOR;
};
v2f vert(a2v v) {
v2f f;
//UNITY_MATRIX_MVP 这个矩阵用来把一个坐标从模型空间转换到剪裁空间
f.position = mul(UNITY_MATRIX_MVP, v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//环境光
//_World2Object 这个矩阵用来把一个方向从世界空间转换到模型空间
fixed3 normalDir = normalize(mul(v.normal, (float3x3)unity_WorldToObject));//相当于把模型空间转换到世界空间
fixed3 lightDir = normalize( _WorldSpaceLightPos0.xyz);//对于每一个顶点来说 光的位置就是光的方向 因为光是平行光
fixed3 diffuse = _LightColor0.rgb*max(0, dot(normalDir, lightDir))*_Diffuse.rgb;//取得漫反射的颜色
fixed3 reflectDir = normalize(reflect(-lightDir, normalDir));
fixed3 vlewDir = normalize(_WorldSpaceCameraPos.xyz - mul(v.vertex, unity_WorldToObject).xyz);
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(dot( reflectDir, vlewDir), 0), _Gloss);//计算高光
f.color = diffuse+ ambient+ specular;
return f;
}
fixed4 frag(v2f f) :SV_Target{
return fixed4(f.color,1);
}
ENDCG
}
}
Fallback"Diffuse"
}
下面我们用 逐像素来实现高光反射
Shader"Specular Fragment"{
properties{
_Diffuse("Color",Color) = (1,1,1,1)
_Specular("Specular Color",Color)=(1,1,1,1)//控制高光颜色
_Gloss("Gloss",Range(8,200))=10//控制高光强度
}
SubShader{
Pass {
//定义正确的LightMode得到内置的光照变量
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
//包含unity的内置的文件,才可以使用unity内置的一些变量 相当于引入内置的光照模型
#include "Lighting.cginc"//取得第一个直射光的颜色 _LightColor0第一个值是光的位置_WorldSpaceLightPos0
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
half _Gloss;
fixed4 _Specular;
struct a2v {
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f {
float4 position:SV_POSITION;
float3 WorldNomormal : TEXCOORD0;
float3 WorldVertex:TEXCOORD1;
};
v2f vert(a2v v) {
v2f f;
//UNITY_MATRIX_MVP 这个矩阵用来把一个坐标从模型空间转换到剪裁空间
f.position = mul(UNITY_MATRIX_MVP, v.vertex);
f.WorldNomormal = mul(v.normal, (float3x3)unity_WorldToObject);
f.WorldVertex = mul(v.vertex,_World2Object).xyz;
return f;
};
fixed4 frag(v2f f) :SV_Target{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//环境光
//_World2Object 这个矩阵用来把一个方向从世界空间转换到模型空间
fixed3 normalDir = normalize(f.WorldNomormal);//相当于把模型空间转换到世界空间
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//对于每一个顶点来说 光的位置就是光的方向 因为光是平行光
fixed3 diffuse = _LightColor0.rgb*max(0, dot(normalDir, lightDir))*_Diffuse.rgb;//取得漫反射的颜色
fixed3 reflectDir = normalize(reflect(-lightDir, normalDir));
fixed3 vlewDir = normalize(_WorldSpaceCameraPos.xyz - f.WorldVertex);
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(dot(reflectDir, vlewDir), 0), _Gloss);//计算高光
fixed3 tempColor = diffuse + ambient + specular;
return fixed4(tempColor,1);
}
ENDCG
}
}
Fallback"Diffuse"
}
可以看到逐像素更加柔和平滑
Blinn-Phong光照模型
下面对我们对Blinn光照模型又作了一种改进,其计算方式为:
Specular=直射光 * pow( max(cosθ,0),10) θ:是法线和x的夹角 x 是平行光和视野方向的平分线。
Shader"Learning/Specular Fragment BlinnPhone"{
properties{
_Diffuse("Color",Color) = (1,1,1,1)
_Specular("Specular Color",Color)=(1,1,1,1)//控制高光颜色
_Gloss("Gloss",Range(8,200))=10//控制高光强度
}
SubShader{
Pass {
//定义正确的LightMode得到内置的光照变量
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
//包含unity的内置的文件,才可以使用unity内置的一些变量 相当于引入内置的光照模型
#include "Lighting.cginc"//取得第一个直射光的颜色 _LightColor0第一个值是光的位置_WorldSpaceLightPos0
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
half _Gloss;
fixed4 _Specular;
struct a2v {
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f {
float4 position:SV_POSITION;
float3 WorldNomormal : TEXCOORD0;
float4 WorldVertex:TEXCOORD1;
};
v2f vert(a2v v) {
v2f f;
//UNITY_MATRIX_MVP 这个矩阵用来把一个坐标从模型空间转换到剪裁空间
f.position = mul(UNITY_MATRIX_MVP, v.vertex);
/*f.WorldNomormal = mul(v.normal, (float3x3)unity_WorldToObject);*/
//UnityObjectToWorldNormal(float3 norm) 把法线方向 模型空间 == 》世界空间
f.WorldNomormal = UnityObjectToWorldNormal(v.normal);
f.WorldVertex = mul(v.vertex,unity_WorldToObject);
return f;
};
fixed4 frag(v2f f) :SV_Target{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//环境光
//_World2Object 这个矩阵用来把一个方向从世界空间转换到模型空间
fixed3 normalDir = normalize(f.WorldNomormal);//相当于把模型空间转换到世界空间
//fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//对于每一个顶点来说 光的位置就是光的方向 因为光是平行光
//WorldSpaceLightDir(float4 v) 模型空间中的顶点坐标 == 》世界空间中从这个点到光源的方向
fixed3 lightDir = normalize(WorldSpaceLightDir(f.WorldVertex).xyz);
fixed3 diffuse = _LightColor0.rgb*max(0, dot(normalDir, lightDir))*_Diffuse.rgb;//取得漫反射的颜色
//fixed3 vlewDir = normalize(_WorldSpaceCameraPos.xyz - f.WorldVertex);
//UnityWorldSpaceViewDir(float4 v) 世界空间中的顶点坐标==》世界空间从这个点到摄像机的观察方向
fixed3 vlewDir = normalize(UnityWorldSpaceViewDir(f.WorldVertex));
fixed3 halfDir = normalize(vlewDir + lightDir);
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(dot(normalDir, halfDir), 0), _Gloss);//计算高光
fixed3 tempColor = diffuse + ambient + specular;
return fixed4(tempColor,1);
}
ENDCG
}
}
Fallback"Diffuse"
}
在这里我们可以看到Blinn-Phong光照模型的高光更加大更加亮,并且在背光面B模型因为一些计算所以背光面也会有一些高光,但是B—P模型就不会出现这种现象,一般我们更倾向于Blinn-Phong的计算方式.