【從UnityURP開始探索遊戲渲染】專欄-直達
光照衰減的基本原理
在物理正確的光照模型中,衰減需要遵循兩個基本定律:
- 平方反比定律:光強與距離平方成反比 (I ∝ 1/r²)
- 餘弦定律:表面接收的光強與入射角餘弦成正比 (I ∝ cosθ)
經典蘭伯特模型的衰減處理
標準蘭伯特公式
$漫反射 = 表面顏色 * 表面反照率 * max(0, N·L)$
衰減實現分析
- 角度衰減:
- ✅ 正確實現餘弦定律
- 通過 N·L 點積計算入射角衰減
- 符合物理規律:光線入射角越大,光照強度越小
- 距離衰減:
- ⚠️ 完全缺失距離衰減計算
- 公式中沒有包含光源距離(r)相關項
- 光強不會隨距離增加而減弱
- 導致物理不準確性
Unity URP中的實現方案
距離衰減補償機制
URP通過額外計算衰減因子來彌補蘭伯特的不足:
hlsl
// URP光源獲取函數 (Lighting.hlsl)
Light GetMainLight()
{
Light light;
light.direction = _MainLightPosition.xyz;
// 距離衰減計算
float distance = length(_WorldSpaceCameraPos - positionWS);
light.distanceAttenuation = 1.0 / max(distance * distance, 0.01);
light.color = _MainLightColor.rgb;
return light;
}
// 主光源 漫反射計算
lightingData.mainLightColor += CalculateBlinnPhong(mainLight, inputData, surfaceData);
half3 CalculateBlinnPhong(Light light, InputData inputData, SurfaceData surfaceData)
{
// 這裏通過顏色計算了光線衰減
half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
half3 lightDiffuseColor = LightingLambert(attenuatedLightColor, light.direction, inputData.normalWS);
half3 lightSpecularColor = half3(0,0,0);
#if defined(_SPECGLOSSMAP) || defined(_SPECULAR_COLOR)
half smoothness = exp2(10 * surfaceData.smoothness + 1);
lightSpecularColor += LightingSpecular(attenuatedLightColor, light.direction, inputData.normalWS, inputData.viewDirectionWS, half4(surfaceData.specular, 1), smoothness);
#endif
#if _ALPHAPREMULTIPLY_ON
return lightDiffuseColor * surfaceData.albedo * surfaceData.alpha + lightSpecularColor;
#else
return lightDiffuseColor * surfaceData.albedo + lightSpecularColor;
#endif
}
URP衰減系統組成
光源類型處理:
- 平行光:無距離衰減 (1.0)
- 點光源:平方反比衰減 (1/r²)
- 聚光燈:角度衰減 × 距離衰減
優化策略:
- 使用預計算的衰減紋理
- 最大距離截斷(light.range)
- 平滑過渡邊緣處理
graph TD A[光源類型] --> B{平行光?} B -->|是| C[衰減=1.0] B -->|否| D{點光源?} D -->|是| E[1/r²計算] D -->|否| F[聚光燈衰減曲線] E --> G[距離截斷] F --> G G --> H[平滑過渡]
為什麼經典蘭伯特缺乏距離衰減
- 歷史設計侷限:
- 早期計算機圖形學簡化模型
- 源自環境固定的CAD渲染需求
- 僅考慮局部表面光照
- 數學簡化考量:
- 減少每像素計算量
- 避免昂貴的距離計算
- 保持公式簡潔性
- 藝術導向設計:
- 允許美術師手動控制光照範圍
- 避免距離導致的過度變暗
- 更適合風格化渲染
URP的實用解決方案
衰減校正技術
-
物理混合方案:
hlsl half3 ApplyAttenuation(Light light, float3 positionWS) { // 基礎平方反比衰減 float dist = distance(light.position, positionWS); float atten = 1.0 / (dist * dist); // 範圍平滑過渡 float fade = saturate(1.0 - (dist / light.range)); atten *= fade * fade; // 聚光燈角度衰減 if(light.type == SPOT) { float3 toLight = normalize(light.position - positionWS); float spotFactor = dot(toLight, light.direction); atten *= smoothstep(light.outerAngle, light.innerAngle, spotFactor); } return saturate(atten * light.intensity); } -
移動端優化版:
hlsl half3 SimpleAttenuation(Light light, float3 positionWS) { // 使用預計算的衰減紋理 float dist = distance(light.position, positionWS); float t = saturate(dist / light.range); half atten = SAMPLE_TEXTURE2D(_LightAttenuationTex, sampler_LinearClamp, float2(t, 0.5)).r; return atten * light.intensity; }
Unity編輯器中配置
csharp
// 光源組件屬性設置
Light light = gameObject.AddComponent<Light>();
light.type = LightType.Point;
light.range = 10.0f;// 控制衰減範圍
light.intensity = 1.0f;// 控制最大強度
light.color = Color.white;
結論與建議
核心結論
- 經典蘭伯特模型自身不包含距離衰減,僅有角度衰減
- URP通過外部衰減系統提供完整衰減支持,使經驗模型實用化
- 現代實現已接近物理正確,但仍有可控的藝術化調整空間
開發實踐建議
-
性能敏感場景:
hls // 使用簡化距離衰減 float atten = saturate(1.0 - distance/range); -
高品質渲染:
hlsl // 物理精確衰減 float atten = 1.0 / (distance * distance + 1e-5); -
風格化渲染:
hlsl // 自定義衰減曲線 float atten = exp(-_Falloff * distance);
在URP中,雖然經典蘭伯特模型本身不具備完整的物理衰減特性,但通過引擎層的光照系統補償,開發者可以輕鬆實現物理正確的衰減效果,同時保留藝術控制自由度。這種分層設計正是現代渲染管線的實用智慧體現。
【從UnityURP開始探索遊戲渲染】專欄-直達
(歡迎點贊留言探討,更多人加入進來能更加完善這個探索的過程,🙏)