标准光照明模型
它的基本方法是,把进入到摄像机的光分为四部分:
- 自发光(emissive)
- 高光反射(specular)
- 漫反射(diffuse)
- 环境光(ambient)
本文使用Unity Shader实现了兰伯特模型(Lambert)、半-兰伯特模型(Half-Lambert)、Phong模型、Blinn-Phong模型。
效果图
从左至右依次是:兰伯特模型、半兰伯特模型、Phong模型、Blinn-Phong模型。
代码分享(附详细注解)
Lambert模型:
v2f vert(a2v v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vert);//将顶点方向矢量从模型空间转换到裁剪空间
o.worldNormal = UnityObjectToWorldNormal(v.normal);//世界空间下的法向量
o.worldPos = mul(unity_WorldToObject, v.vert).xyz;
return o;
}
fixed4 frag(v2f f) :SV_Target{
//Lambert 光照模型公式:
// 最终颜色 = 直射光颜色 * 漫反射颜色 * max(0, dot(光源方向, 法线方向))
//归一化:颜色通道范围在0-1之间,归一化之后的坐标计算才不会超出范围
fixed3 worldNormal = normalize(f.worldNormal);
//模型空间中的顶点坐标 --> 世界空间中从这个点到光源的方向
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(f.worldPos));
//把光照计算放在片元函数中,计算次数更多,图像会更平滑
//计算环境光
fixed ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//计算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldLightDir, worldNormal));
//fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(worldLightDir, worldNormal) *0.5+0.5);
return fixed4(diffuse+ambient, 1.0);
}
Half-Lambert模型(与Lambert模型不同之处):
// 最终颜色 = 直射光颜色 * 漫反射颜色 * (dot(光源方向, 法线方向)*0.5+0.5)
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(worldLightDir, f.worldnormal) * 0.5 + 0.5);
Phong模型:
fixed4 frag(v2f f) :SV_Target{
//ambient
fixed ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//light direction
fixed3 WorlLightDir= normalize(UnityWorldSpaceLightDir(f.worldpos));
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(WorlLightDir, f.WorldNormal)*0.5+0.5);
//Eye direction
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(f.worldpos)); // 计算视角方向
fixed3 reflectDir = normalize(reflect(-WorlLightDir, f.WorldNormal)); // 计算反射方向
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, worldViewDir)), _Glossiness);
return fixed4(diffuse + specular+ambient, 1.0);
}
Blinn-Phong模型:
float4 frag(v2f f) :SV_TARGET{
//diffuse:Half-Lambert
float3 WorlLightDir = normalize(UnityWorldSpaceLightDir(f.worldpos));
float3 diffuse = _LightColor0.rgb * _diffuse * (dot(WorlLightDir,f.worldnormal) * 0.5 + 0.5);
//specular:Blinn
float3 worldViewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldpos.xyz); // 计算视角方向
float3 halfDir = normalize(worldViewDir + WorlLightDir); // 计算Blinn模型中引入的新矢量
float3 specular = _LightColor0.rgb*_Specular* pow(saturate(dot(f.worldnormal, halfDir)), _Gloss);
return float4(diffuse + specular, 1.0);
}
一些问题
关于光照明模型
可以从上面的效果图中看出,光照明模型并不符合真实世界的光照现象。但是由于其易用性、计算速度、较可以的效果,现在仍在广泛使用。
逐像素还是逐顶点
- 计算速度:逐顶点光照的计算量远小于逐像素光照的计算量。
- 效果:逐像素光照能得到更高质量的光照效果,无论是漫反射还是高光反射都更加平滑。当光照明模型中有非线性的计算时,逐顶点光照就会出问题。逐顶点得到的高光效果有比较大的问题,从高光中心点向外扩散显得很不平滑。另外,逐顶点光照需要模型有较多较密的顶点才行。如果模型的顶点很少,光照结果可能就完全不对了。
希望这篇文章对大家有所帮助。