以物理方式制作着色器
在你在上一章学到的许多东西中,有三条规则是你实现的BRDF需要遵循的,以便被认为是基于物理的。将这些知识付诸实践的一个方法是审查定制的Phong表面着色器,看看它是否破坏了这些规则。该着色器遵循Phong的原始配方,而Phong是相当古老的,所以很有可能是这样。
分析 Phong
清单9-1显示了自定义的Phong照明功能。让我们检查一下正向性、对等性和能量守恒。
清单 9-1. Phong的自定义照明函数
inline fixed4 LightingPhong (SurfaceOutput s, half3 viewDir, UnityGI gi)
{
UnityLight light = gi.light;
float nl = max(0.0f, dot(s.Normal, light.dir));
float3 diffuseTerm = nl * s.Albedo.rgb * light.color;
float3 reflectionDirection = reflect(-light.dir, s.Normal);
float3 specularDot = max(0.0, dot(viewDir, reflectionDirection));
float3 specular = pow(specularDot, _Shininess);
float3 specularTerm = specular * _SpecColor.rgb * light.color.rgb;
float3 finalColor = diffuseTerm.rgb + specularTerm;
fixed4 c;
c.rgb = finalColor;
c.a = s.Alpha;
#ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
c.rgb += s.Albedo * gi.indirect.diffuse;
#endif
return c;
}
检查正面性
你可以确保BRDF的输出总是正的,用一个小技巧,在最终颜色前面放一个从0开始的Max函数。就本章而言,这样做是可以的,但一般来说,如果BRDF在数学上能保证这一点,那会更好。
检查对等性
对等性不容易从着色器代码中检查。一般来说,任何公布的基于物理的BRDF都应该有这个属性。如果你想检查的话,可以查阅BRDF的研究论文,或者,如果你精通Mathematica等程序,甚至是手工计算,你可以自己做数学题并检查。Phong不是对等性的,所以这需要一些工作。
检查能量守恒
有了正确的归一化因素,甚至Phong也可以是能量守恒的。一个能量守恒的镜面,当它集中在一个较小的区域时,会更亮,而当它分散在更多的地方时,则更暗。一般来说,这将对应于材料的粗糙程度,在基于微面理论的BRDF中。
改良后的Phong
对我们来说,幸运的是,Lafortune和Willems在他们1994年的论文中完成了使Phong基于物理的工作。我们想要的归一化因子是:
你可以在清单9-2中看到修改后的、规范化的Phong函数的实现。
清单 9-2. 修改后的Phong作为一个自定义的照明函数
inline fixed4 LightingPhongModified (SurfaceOutput s, half3 viewDir, UnityGI gi)
{
const float PI = 3.14159265358979323846;
UnityLight light = gi.light;
float nl = max(0.0f, dot(s.Normal, light.dir));
float3 diffuseTerm = nl * s.Albedo.rgb * light.color;
float norm = (n + 2) / (2 * PI);
float3 reflectionDirection = reflect(-light.dir, s.Normal);
float3 specularDot = max(0.0, dot(viewDir, reflectionDirection));
float3 specular = norm * pow(specularDot, _Shininess);
float3 specularTerm = specular * _SpecColor.rgb * light.color.rgb;
float3 finalColor = diffuseTerm.rgb + specularTerm;
fixed4 c;
c.rgb = finalColor;
c.a = s.Alpha;
#ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
c.rgb += s.Albedo * gi.indirect.diffuse;
#endif
return c;
}
就这样,我们实现了一个基于物理的BRDF。这可能是一个反常的过程。归根结底,就是把镜面反射率乘以归一化系数。归一化系数可以防止BRDF返回的光比它一开始收到的多。
你以后会玩到有趣的BRDFs,但现在让我们担心的是,当你看它的时候,检查它是否有物理上的感觉,并学习如何制作一个方便的着色器。这个自定义的照明函数很适合在几章之前的旧的自定义Phong表面着色器中使用(见清单9-3)。
清单 9-3. 定制/修改的Phong表面着色器
Shader "Custom/ModifiedPhong" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
_Shininess ("Shininess (n)", Range(1,1000)) = 100
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf PhongModified fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
half _Shininess;
fixed4 _Color;
inline void LightingPhongModified_GI (
SurfaceOutput s,
UnityGIInput data,
inout UnityGI gi)
{
gi = UnityGlobalIllumination (data, 1.0, s.Normal);
}
inline fixed4 LightingPhongModified (SurfaceOutput s, half3 viewDir, UnityGI gi)
{
const float PI = 3.14159265358979323846;
UnityLight light = gi.light;
float nl = max(0.0f, dot(s.Normal, light.dir));
float3 diffuseTerm = nl * s.Albedo.rgb * light.color;
float norm = (_Shininess + 2) / (2 * PI);
float3 reflectionDirection = reflect(-light.dir, s.Normal);
float3 specularDot = max(0.0, dot(viewDir, reflectionDirection));
float3 specular = norm * pow(specularDot, _Shininess);
float3 specularTerm = specular * _SpecColor.rgb * light.color.rgb;
float3 finalColor = diffuseTerm.rgb + specularTerm;
fixed4 c;
c.rgb = finalColor;
c.a = s.Alpha;
#ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
c.rgb += s.Albedo * gi.indirect.diffuse;
#endif
return c;
}
UNITY_INSTANCING_CBUFFER_START(Props)
UNITY_INSTANCING_CBUFFER_END
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = _Shininess;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
你要找的是,当镜面散开时,镜面亮度减少,而当镜面集中在一个小表面时,镜面亮度增加。由于我们身边有一个规范化前的Phong版本,我们可以很容易地检查到这一点。正如你在图9-1中看到的,原始的Phong(在右边)保持相同的亮度,而归一化的Phong(在左边)在镜面占据较大面积时亮度较低,占据较小面积时亮度较高,这是能量守恒的结果
另一个方便的方法是比较绘制BRDF的行为图,以看出其中的差别。你会在第11章中看到更多关于这个问题的内容,在那里我们将介绍一个分析BRDF的程序,但是图9-2显示了对未来的一个小小的偷窥
图9-2. 原始 Phong 和修改后的 Phong 的极坐标图比较
图9-2显示了两个版本的Phong,在图上画出了相互之间的对比。在图像的左边,两者都有n = 10。在图片的右边,对应于n = 100,你看到修改后的Phong的亮度急剧增加,而原始Phong的亮度保持不变。
总结
正如你所看到的,即使是一个老式的BRDF,也有可能使其更具有物理基础。使用BlinnPhong也可以达到同样的效果,方法是查找归一化系数或自己推导。自己推导需要你对微积分很熟悉。
虽然这个修改后的Phong是能量守恒的,但它仍然没有考虑到菲涅尔,所以这个Phong和从微面理论得出的BRDF之间仍然会有相当大的差异。
现在你的武器库中已经有了一个基于物理的BRDF,你已经准备好学习后期处理效果了。它们将帮助着色器看起来更加真实。你需要使用HDR相机,确保你在线性空间中进行着色,并添加色调映射。