标准光照模型
虽然光照模型有很多种类,但在早期的游戏引擎中往往只使用一个光照模型,这个模型被称为标准光照模型。实际上,在BRDF理论被提出之前,标准光照模型就已经被广泛使用了。
在1975年,著名学者裴祥风(Bui Tuong Phong) 提出了标准光照模型背后的基本理念。标准光照模型只关心直接光照(direct light),也就是那些直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线。它的基本方法是,把进入到摄像机内的光线分为4个部分,每个部分使用一种方法来计算它的贡献度。
这4个部分是:
- 自发光(emissive)部分。这个部分用于描述当给定一个方向时,一个表面本身会向该方向发射多少辐射量。需要注意的是,如果没有使用全局光照(globalillumination)技术,这些自发光的表面并不会真的照亮周围的物体,而是它本身看起来更亮了而已。
- 高光反射(specular) 部分。这个部分用于描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
- 漫反射(diffuse)部分。这个部分用于描述,当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
- 环境光(ambient)部分。它用于描述其他所有的间接光照。
环境光
虽然标准光照模型的重点在于描述直接光照,但在真实的世界中,物体也可以被间接光照(indirect light) 所照亮。间接光照指的是,光线通常会在多个物体之间反射,最后进入摄像机,也就是说,在光线进入摄像机之前,经过了不止一次的物体反射。例如,在红地毯上放置一个浅灰色的沙发,那么沙发底部也会有红色,这些红色是由红地毯反射了一部分光线,再反弹到沙发上的。
在标准光照模型中,我们使用了一种被称为环境光的部分来近似模拟间接光照。环境光的计算非常简单,它通常是一个全局变量,即场景中的所有物体都使用这个环境光。下面的等式给出了计算环境光的部分:
自发光
光线也可以直接由光源发射进入摄像机,而不需要经过任何物体的反射。标准光照模型使用自发光来计算这个部分的贡献度。它的计算也很简单,就是直接使用了该材质的自发光颜色:
漫反射
漫反射光照是用于对那些被物体表面随机散射到各个方向的辐射度进行建模的。在漫反射中,视角的位置是不重要的,因为反射是完全随机的,因此可以认为在任何反射方向上的分布都是一样的。但是,入射光线的角度很重要。
漫反射光照符合兰伯特定律(Lambert’s law): 反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比。因此,漫反射部分的计算如下:
其中,n是表面法线,I是指向光源的单位矢量,Mdiffuse 是材质的漫反射颜色,Clight 是光源颜色。需要注意的是,我们需要防止法线和光源方向点乘的结果为负值,为此,我们使用取最大值的函数来将其截取到0,这可以防止物体被从后面来的光源照亮。
顶点着色器计算漫反射(逐顶点计算)
Properties
{ //漫反射光颜色
_Diffuse("Diffuse",color)=(1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
//定义
fixed4 _Diffuse;
//输出结构体
struct v2f
{ //输出裁剪空间中的位置信息
float4 vertex:SV_POSITION;
//输出裁剪空间对应的颜色信息
fixed3 color :Color;
};
//顶点着色器
v2f vert (appdata_base v)
{ //定义的输出结构体的类型
v2f o;
//模型空间的点 转换到裁剪空间的点
o.vertex = UnityObjectToClipPos(v.vertex);
//得到环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//模型空间下的法线向量转换到世界空间
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
//世界坐标中光源的位置
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射 环境中各种光照 * 材质漫反射的颜色 *(表面法线·光源的单位矢量)
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLight));
//光照叠加
o.color = diffuse + ambient;
return o;
}
//片元着色器
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
片元着色器计算漫反射(逐像素计算)
struct v2f
{
float4 vertex:SV_POSITION;
fixed3 worldNormal :TEXCOORD0;
};
//顶点着色器
v2f vert (appdata_base v)
{
v2f o;
//顶点
o.vertex = UnityObjectToClipPos(v.vertex);
//法线
o.worldNormal = UnityObjectToWorldNormal(v.normal);
return o;
}
//片元着色器
fixed4 frag (v2f i) : SV_Target
{
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//光源
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldLightDir,i.worldNormal));
//片元半兰伯特 漫反射计算
//fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(worldLightDir,i.worldNormal) * 0.5 + 0.5);
fixed3 color = ambient + diffuse;
return fixed4(color,1);
}
从左到右,依次是片元半兰伯特模型,片元着色器计算,顶点着色器计算。
半兰伯特模型:为了解决背光面一样的缺点。
片元着色器计算:可以得到更平滑的光照效果。
顶点着色器计算:可以明显看出光源交接有明显的锯齿状。
高光反射
这里的高光反射是一种经验模型,也就是说,它并不完全符合真实世界中的高光反射现象。
它可用于计算那些沿着完全镜面反射方向被反射的光线,这可以让物体看起来是有光泽的,例如
金属材质。
计算高光反射需要知道的信息比较多,如表面法线、视角方向、光源方向、反射方向等。在这四个矢量中,我们实际上只需要知道其中3个矢量即可,而第四个矢量一反射方向可以通过其他信息计算得到:
要计算高光反射需要知道4个参数:入射光线的颜色和强度Cight, 材质的高光反射系数Mpeular, 视角方向v令以及反射方向r。其中,反射方向r可以由表面法线n和光源方向I计算而得
其中,Mglass 是材质的光泽度(gloss), 也被称为反光度(shininess)。 它用于控制高光区域的“亮点”有多宽,Mgloss 越大,亮点就越小。Mspseular 是材质的高光反射颜色,它用于控制该材质对于高光反射的强度和颜色。Clight 则是光源的颜色和强度。
顶点着色器(逐顶点计算)
Properties
{ //环境光颜色
_Diffuse("Diffuse",color)=(1,1,1,1)
//材质的高光反射颜色
_Specular("Specular",color)=(1,1,1,1)
//光泽度
_Gloss("Gloss",Range(1,256)) = 5
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
//定义
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
//输出结构体
struct v2f
{
float4 vertex:SV_POSITION;
fixed3 color :Color;
};
v2f vert (appdata_base v)
{
v2f o;
//模型空间的点 转换到裁剪空间的点
o.vertex = UnityObjectToClipPos(v.vertex);
//求顶点 世界坐标
fixed3 worldPos = mul(unity_ObjectToWorld,v.vertex);
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
//方向光
//fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldLight = normalize(UnityWorldSpaceLightDir(worldPos));
//计算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLight));
//求反射方向 (光的入射反向,法线方向)
fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
//视点方向
//fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - UnityObjectToWorldDir(v.vertex));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//高光计算:光源的强度和颜色 * 材质高光反射颜色 * (反射方向·视点方向) ,_Gloss控制高光的亮点区域
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(reflectDir,viewDir)),_Gloss);
o.color = diffuse + ambient + specular;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
FallBack "Diffuse"
片元着色器(逐片元计算)
struct v2f
{
float4 vertex:SV_POSITION;
//法线信息
fixed3 worldNormal :TEXCOORD0;
//世界坐标
float3 worldPos:TEXCOORD1;
};
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//漫反射
fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
//fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldLightDir,i.worldNormal));
//高光反射
//反射方向
fixed3 reflectDir = normalize(reflect(-worldLightDir,i.worldNormal));
//视口方向
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
//fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
//高光计算公式
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color,1);
}
Blinn-Phong光照模型
struct v2f
{
float4 vertex:SV_POSITION;
fixed3 worldNormal :TEXCOORD0;
float3 worldPos:TEXCOORD1;
};
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldNormal = worldNormal;
o.worldPos = mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//漫反射
fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
//fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldLightDir,i.worldNormal));
//高光反射
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
//fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
//新的矢量 视角反向与方向向量相加 归一化
fixed3 halfDir = normalize(worldLightDir + viewDir);
//高光计算:光源的强度和颜色 * 材质高光反射颜色 * (法线方向·新的矢量) ,_Gloss控制高光的亮点区域
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(i.worldNormal,halfDir)),_Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color,1);
}
从左到右,依次是Blinn-Phong光照模型,片元着色器计算,顶点着色器计算。
Blinn-Phong光照模型:Blinn-Phong光照模型的高光反射部分看起来更大、更亮一些。
片元着色器计算:可以得到更平滑的高光效果。
顶点着色器计算:可以明显看出高光有明显的锯齿状。