标准光照模型
- 自发光: 描述给定一个方向时,模型表面会向这个方向发射多少辐射量
- 高光反射: 描述光线从光源照到物体表面时,会向完全镜面反射方向发射多少辐射量
- 漫反射: 描述光线从光源照到物体表面时,会向每个方向发射多少辐射量
- 环境光(ambient): 描述所有其他的间接光照
环境光与自发光
这两种光照不需要经过计算,用常量表示就可以了。
环境光: 可以在Window->Rendering->LightingSettings->EnvironmentLighting中设置。在Shader计算中通过Unity的内置变量UNITY_LIGHTMODEL_AMBIENT得到环境光的颜色和强度信息。
自发光: 不需要计算或者设置属性。一般的物体都没有自发光属性,如果需要的话,在片元着色器输出的时候加上自定义的自发光颜色就好了。
漫反射
兰伯特光照模型: 当光源向物体表面照射时,会向四面八方反射,这个现象称为漫反射。漫发射的光照强度符合Lambert定律,即反射光强与该点的光源方向和法线的夹角余弦值(两向量点乘)成正比。这个其实根据生活中的现象思考一下就知道了,一束光垂直照射下来(夹角为0度),反射的光强最强,水平面方向(夹角90度)照射过来,光强最弱。
而为了防止光源方向和法线方向的余弦值为负值,做了个取最大值的操作来将余弦值控制最小为0,防止物体被后面的光源照亮。
兰伯特公式(Lambert):
漫反射Diffuse颜色 = 直射光颜色 * max(0, cos(光源方向和法线方向夹角)) * 材质自身色彩
半兰伯特光照模型: 兰伯特光照模型存在一个问题:光照射不到的地方模型表面是黑色的。为了改善这个问题,特地提出了半兰伯特光照模型,在光源方向和法线点乘结果上进行偏移,将其结果[-1,1]映射到[0,1]范围内,这样即使在背光处,模型表面也有了明暗变化。值得注意的是,半兰伯特模型没有任何物理依据,只是一个视觉加强的技术。
半兰伯特公式(HalfLambert):
漫反射Diffuse颜色 = 直射光颜色 * (cos(光源方向和法线方向夹角)*0.5 + 0.5) * 材质自身色彩
普通Lambert光照模型和HalfLambert光照模型区别:
逐顶点计算和逐像素计算区别: UnityShader中可以在顶点着色器或者片元着色器中计算漫反射光照,逐顶点计算是通过顶点插值来计算多边形覆盖区域的像素颜色,所以逐顶点计算的光照在背光面和向光面交界处会有一些锯齿,视觉效果更粗糙(低模粗糙更明显),使用逐像素计算光照效果更平滑,视觉效果更好。
逐顶点计算:
Shader "cc/shader1" {
Properties {
_Diffuse ("Diffuse",Color) = (1,1,1,1)
}
SubShader {
Pass {
Tags {"LightMode" = "ForwardBase"} // 光照流水线,定义了才能得到一些内置光照变量
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" // 后面用到 _LightColor0
fixed4 _Diffuse; // 声明属性变量
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 获得环境光,Unity内置变量
// 等价 UnityObjectToWorldNormal(v.normal)
fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); // 将法线从模型空间转到世界空间
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); // 光源方向(假设场景只有一个光源且为平行光)
// _LightColor0:光源颜色和强度。saturate:把参数限制在[0,1],CG提供的函数
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
o.color = ambient + diffuse;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
逐像素计算:
Shader "cc/shader2" {
Properties {
_Diffuse ("Diffuse",Color) = (1,1,1,1)
}
SubShader {
Pass {
Tags {"LightMode" = "ForwardBase"} // 光照流水线,定义了才能得到一些内置光照变量
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" // 后面用到 _LightColor0
fixed4 _Diffuse; // 声明属性变量
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 worldNormal : TEXCOORD0; // 获取纹理坐标
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// 不需要计算光照模型,直接把世界空间法线传给着色器
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 获取环境光
fixed3 worldNormal = normalize(i.worldNormal); // 获取世界坐标的法线
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); // 光源方向
// _LightColor0:光源颜色和强度,saturate:把参数限制在[0,1]
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
fixed3 color = ambient + diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
半兰伯特光照模型:
Shader "cc/shader2" {
Properties {
_Diffuse ("Diffuse",Color) = (1,1,1,1)
}
SubShader {
Pass {
Tags {"LightMode" = "ForwardBase"} // 光照流水线,定义了才能得到一些内置光照变量
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" // 后面用到 _LightColor0
fixed4 _Diffuse; // 声明属性变量
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 worldNormal : TEXCOORD0; // 获取纹理坐标
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// 不需要计算光照模型,直接把世界空间法线传给着色器
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 获取环境光
fixed3 worldNormal = normalize(i.worldNormal); // 获取世界坐标的法线
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); // 光源方向
// _LightColor0:光源颜色和强度
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(worldNormal,worldLight)*0.5 + 0.5);
fixed3 color = ambient + diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
高光反射
高光反射指的是光源照到物体表面时,会向该点完全镜面进行反射。计算高光反射需要知道四个参数,光的颜色、光的强度、材质的高光反射系数(_Specular)、视角方向(v)和反射方向(r),其中反射方向r可以通过计算得到。
高光反射公式:
高光反射光照值 = (光的颜色和强度 * 高光反射系数) * pow(saturate(dot(视角方向 ,反射方向)), 高光范围系数);
逐顶点计算:
Shader "cc/shader3" {
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" = "ForwardBase"} // 光照流水线,定义了才能得到一些内置光照变量
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" // 后面用到 _LightColor0
fixed4 _Diffuse;
fixed4 _Specular;
fixed _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
v2f vert (a2v v){
v2f o;
o.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 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
// 计算获得出射方向r。reflect:发射方向,CG提供的函数。worldLightDir是光照方向,与L方向相反。
fixed reflectDic = normalize(reflect(-worldLightDir,worldNormal));
// 视觉方向v。即相机到物体的向量归一化。
// 等价UnityWorldSpaceViewDir(mul(unity_ObjectToWorld,v.vertex))
fixed viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld,v.vertex).xyz);
// 高光反射系数,Phong模型公式,假设只有一个平行光源_LightColor0。
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDic,viewDir)),_Gloss);
o.color = ambient + diffuse + specular;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color,1.0);
}
ENDCG
}
}
FallBack "Specular"
}
逐像素计算:
Shader "cc/shader4" {
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" = "ForwardBase"} // 光照流水线,定义了才能得到一些内置光照变量
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" // 后面用到 _LightColor0
fixed4 _Diffuse;
fixed4 _Specular;
fixed _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 o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
fixed reflectDic = normalize(reflect(-worldLightDir,worldNormal));
fixed viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDic,viewDir)),_Gloss);
return fixed4(ambient + diffuse + specular,1.0);
}
ENDCG
}
}
FallBack "Specular"
}
Phong和Blinn-Phong光照模型
以上介绍了环境光、漫反射、高光反射三种光照的计算方式,三种光照结合在一起称为Phone光照模型。如果在顶点着色器中实现Phong光照模型的,叫做Gouraud着色(Gouraud Shading);如果在片段着色器中实现Phong光照模型的,叫做Phong着色(Phong Shading)。
由于Phone光照模型需要计算光照的反射方向向量,这个是比较耗时的,因此砖家们又提出了一种Blinn-Phong光照模型。Blinn-Phong光照模型没有使用反射方向,而是引入了一个新的矢量h,它是通过视角方向v和光照方向I相加后再归一化得到的。即:
Blinn-Phong光照模型公式如下:
高光反射光照值 = (光的颜色和强度 * 高光反射系数) * pow(saturate(dot(法线向量 ,引入的矢量h)), 高光范围系数);
在相同条件下Blinn-Phong的高光范围要比Phong更大,写实效果Phong光照模型更好。但算法简单,运行速度快是Blinn-Phong光照模型的优点。
Blinn-Phong光照模型代码如下:
Shader "cc/shader4" {
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" = "ForwardBase"} // 光照流水线,定义了才能得到一些内置光照变量
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" // 后面用到 _LightColor0
fixed4 _Diffuse;
fixed4 _Specular;
fixed _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 o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
// 视觉方向v
//fixed viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
// 单位向量h
fixed3 halfDir = normalize(worldLightDir + viewDir);
// 套公式
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
return fixed4(ambient + diffuse + specular,1.0);
}
ENDCG
}
}
FallBack "Specular"
}