這篇要幫你把三種常見光照模型的「長相、調法、優缺點」一次看懂,並附上 Unity Built-in Render Pipeline(舊版標準管線)的範例,你可以直接貼進專案跑起來。
讀完你會知道:什麼時候用傳統模型(Phong/Blinn-Phong)來快速做出好看的高光,什麼時候改用較寫實的 PBR(Cook-Torrance)。
圖像心智
max(N·L, 0)
spec = pow( max( dot(R, V), 0 ), shininess )
pow( max( dot(N, H), 0 ), shininess )
。把鏡面高光拆成三個因素:
Specular ≈ (DGF) / (4 (N·L)(N·V))
Diffuse 走 Lambert,但要遵守能量概念:金屬幾乎沒有漫反射;非金屬漫反射 ≈ albedo/π
。
當不計算體積光時,我們假設是無限小的入射光立體角,可看作入射方向向量 (縮成一個點)。
// 4) 像素著色器:輸出顏色
float4 frag (v2f i) : SV_Target
{
float3 albedo=tex2D(_MainTex ,i.uv).rgb ;
float3 worldNormal=normalize(i.worldNormal);
float3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
float3 viewDir=normalize(_WorldSpaceCameraPos-i.worldPos);
float3 Lo= float3(0,0,0);
for(int index = 0; index < 4; index++){
float4 lightPosition = float4(unity_4LightPosX0[index],
unity_4LightPosY0[index],
unity_4LightPosZ0[index], 1.0);
// Cook-Torrance BRDF
float3 L = normalize(lightPosition.xyz - i.worldPos);
float3 H = normalize(L + viewDir);
float distance = length(lightPosition.xyz - i.worldPos);
float attenuation = 1.0 / (distance * distance); // (inverse-square law)
float3 radiance = attenuation;
Lo += float3(attenuation, attenuation,attenuation)* unity_LightColor[index].rgb ; // 測試用
}
return float4(color,1);
}
Fresnel-Schlick shading 是一種用於計算光在表面反射強度的近似方法,常見於現代 PBR(Physically Based Rendering)渲染模型中,尤其是在 Cook-Torrance BRDF 模型裡扮演重要角色。它主要用來模擬「視角越斜,反光越強」的現象,也就是所謂的 Fresnel 效應。
F = F0 + (1 - F0) * pow(1 - dot(V, H), 5)
float3 F0 = float3(0.04, 0.04, 0.04);
F0 = lerp(F0,albedo,_Metallic); // 通過金屬度屬性在跟之間進行線性插值,才能得到一個不同的非金屬
float3 F = fresnelSchlick(max(dot(H,viewDir),0.0),F0);
Lo+=F;
更多資訊請參考這個網頁。
// 4) 像素著色器:輸出顏色
float4 frag (v2f i) : SV_Target
{
const float pi = 3.14159265;
float3 albedo=tex2D(_MainTex ,i.uv).rgb ;
float3 worldNormal=normalize(i.worldNormal);
float3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
float3 viewDir=normalize(_WorldSpaceCameraPos-i.worldPos);
float3 Lo= float3(0,0,0);
for(int index = 0; index < 4; index++){
float4 lightPosition = float4(unity_4LightPosX0[index],
unity_4LightPosY0[index],
unity_4LightPosZ0[index], 1.0);
// Cook-Torrance BRDF
float3 L = normalize(lightPosition.xyz - i.worldPos);
float3 H = normalize(L + viewDir);
float distance = length(lightPosition.xyz - i.worldPos);
float attenuation = 1.0 / (distance * distance); // 光源的衰減會更符合物理上的反平方律(inverse-square law)(不是物理學正確的)
float3 radiance = unity_LightColor[index].rgb * attenuation; // 變數表格: https://docs.unity3d.com/6000.2/Documentation/Manual/SL-UnityShaderVariables.html
// D (NDF(Normal Distribution Function))
float NDF = DistributionGGX(worldNormal, H, _Roughness);
// F
float3 F0 = float3(0.04, 0.04, 0.04);
F0 = lerp(F0,albedo,_Metallic); // 通過金屬度屬性在跟之間進行線性插值,才能得到一個不同的非金屬
float3 F = fresnelSchlick(max(dot(H,viewDir),0.0),F0);
// G
float G = GeometrySmith(worldNormal, viewDir, L, _Roughness);
// Cook-Torrance BRDF
float3 numerator = NDF * G * F; // G 效益不大
float denominator = 4.0 * max(dot(worldNormal, viewDir), 0.0) * max(dot(worldNormal, L), 0.0) + 0.001; // 防止除以零
float3 specular = numerator / denominator;
float3 kS = F; //表示光能有多少被反射了
float3 kD = float3(1.0,1.0,1.0) - kS; //反射後剩餘的光亮
kD *= 1.0 - _Metallic; // 由于金属表面不折射光,没有漫反射颜色,通过归零kD来实现这个规则
float NdotL = max(dot(worldNormal, L), 0.0);
Lo += (kD * albedo / pi + specular) * radiance * NdotL; // 半球積分結果
}
//AO
float3 ambient = float3(0.03, 0.03, 0.03) * albedo * _AO;
float3 color = ambient + Lo;
return float4(color,1);
}
float gamma = 1.0/2.2;
color = color / (color + float3(1.0,1.0,1.0)); // (HDR to LDR)
color = pow(color, float3(gamma,gamma,gamma)); // gamma校正
Phong / Blinn-Phong
Cook-Torrance(PBR)
Roughness=0
、F0
用錯、或 diffuse 沒有除以 π
。