【光照】Unity[经验模型]和[物理模型]

发布时间:2026/7/26 19:49:09

【光照】Unity[经验模型]和[物理模型] 【光照】Unity[经验模型]和[物理模型]在游戏开发中光照是营造沉浸感的关键。Unity 作为主流引擎提供了从简单到复杂的多种光照模型。本文将带你从基础概念出发逐步深入理解经验模型如 Lambert、Blinn-Phong和物理模型如 PBR并通过完整代码示例掌握它们的实现。## 一、光照基础概念为什么需要模型现实世界中光照与物体表面的交互极其复杂。为了在计算机中模拟这一过程我们使用光照模型——它是一组数学公式用于计算表面某点接收到的光能如何转化为人眼看到的颜色。-经验模型基于视觉直觉忽略物理正确性追求计算效率。-物理模型基于真实物理原理能量守恒、微表面理论追求真实感。Unity 的 Shader 是编写光照模型的主要工具。本文所有代码基于 Unity 内置渲染管线Built-in RP的 Surface Shader 和顶点/片元 Shader。—## 二、经验模型Lambert 与 Blinn-Phong### 2.1 Lambert 漫反射模型原理表面亮度与光线入射角余弦成正比即朗伯余弦定律。它假设表面粗糙光线向所有方向均匀反射。公式颜色 材质颜色 × 光源颜色 × max(0, dot(法线, 光源方向))完整 Shader 代码Unity Surface Shader 风格csharpShader Example/LambertDiffuse { Properties { _MainTex (Texture, 2D) white {} _Color (Color, Color) (1,1,1,1) } SubShader { Tags { RenderTypeOpaque } LOD 200 CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; fixed4 _Color; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { fixed4 c tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo c.rgb; // 漫反射颜色 o.Alpha c.a; } ENDCG } FallBack Diffuse}注释-#pragma surface surf Lambert告诉 Unity 使用内置 Lambert 光照函数。-SurfaceOutput结构体中的Albedo就是材质颜色。### 2.2 Blinn-Phong 高光模型原理在 Lambert 基础上增加高光使用半向量视线方向与光源方向的一半代替反射向量计算更高效。公式高光 光源颜色 × 高光强度 × pow(max(0, dot(法线, 半向量)), 光泽度)完整 Shader 代码顶点/片元 Shader 风格csharpShader Example/BlinnPhong { Properties { _MainTex (Texture, 2D) white {} _SpecColor (Specular Color, Color) (1,1,1,1) _Shininess (Shininess, Range(0.01, 10)) 1 } SubShader { Tags { RenderTypeOpaque } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc #include Lighting.cginc struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _SpecColor; float _Shininess; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); o.worldNormal UnityObjectToWorldNormal(v.normal); o.worldPos mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag (v2f i) : SV_Target { // 采样纹理 fixed4 texColor tex2D(_MainTex, i.uv); // 法线归一化 float3 N normalize(i.worldNormal); // 光源方向假设只有一个主平行光 float3 L normalize(_WorldSpaceLightPos0.xyz); // 视线方向 float3 V normalize(_WorldSpaceCameraPos - i.worldPos); // 半向量 float3 H normalize(L V); // Lambert 漫反射 float NdotL max(0, dot(N, L)); float3 diffuse texColor.rgb * _LightColor0.rgb * NdotL; // Blinn-Phong 高光 float NdotH max(0, dot(N, H)); float spec pow(NdotH, _Shininess * 128); // 乘以128是为了匹配常见范围 float3 specular _SpecColor.rgb * _LightColor0.rgb * spec; // 环境光 float3 ambient UNITY_LIGHTMODEL_AMBIENT.rgb * texColor.rgb; // 最终颜色 return fixed4(ambient diffuse specular, 1); } ENDCG } } FallBack Diffuse}注释- 手动计算了法线、光源、视线和半向量。-_LightColor0是 Unity 内置的第一个平行光颜色。- 高光项乘以_Shininess * 128是为了让参数范围更直观。—## 三、物理模型PBR基于物理的渲染### 3.1 PBR 核心原理PBR 遵循两个物理原则1.能量守恒反射光 折射光次表面散射≤ 入射光。2.微表面理论表面由无数微小镜面组成粗糙度决定高光扩散程度。关键参数-Albedo基础颜色不含光照信息。-Metallic金属度1金属0非金属。-Smoothness光滑度1镜面0粗糙。-Normal Map法线贴图增加细节。### 3.2 Unity 标准着色器Standard ShaderUnity 内置了 PBR 实现只需在 Surface Shader 中指定#pragma surface surf StandardcsharpShader Example/PBR_Standard { Properties { _MainTex (Albedo (RGB), 2D) white {} _MetallicTex (Metallic (R), 2D) white {} _Smoothness (Smoothness, Range(0,1)) 0.5 _Metallic (Metallic, Range(0,1)) 0.0 } SubShader { Tags { RenderTypeOpaque } CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 sampler2D _MainTex; sampler2D _MetallicTex; half _Smoothness; half _Metallic; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutputStandard o) { fixed4 c tex2D (_MainTex, IN.uv_MainTex); o.Albedo c.rgb; // 从金属贴图读取金属度否则使用全局值 half metallic tex2D(_MetallicTex, IN.uv_MainTex).r * _Metallic; o.Metallic metallic; o.Smoothness _Smoothness; o.Alpha c.a; } ENDCG } FallBack Standard}注释-SurfaceOutputStandard是 PBR 专用的输出结构。-Metallic和Smoothness直接驱动能量守恒计算。- 设置fullforwardshadows支持阴影。### 3.3 手动实现简化 PBR高级用法为了深入理解我们实现一个简化版 Cook-Torrance BRDF包含漫反射Lambert和微表面高光GGX。csharpShader Example/SimplePBR { Properties { _MainTex (Albedo, 2D) white {} _Metallic (Metallic, Range(0,1)) 0 _Smoothness (Smoothness, Range(0,1)) 0.5 _NormalMap (Normal Map, 2D) bump {} } SubShader { Tags { RenderTypeOpaque } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc #include Lighting.cginc struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float3 worldPos : TEXCOORD1; float3 worldNormal : TEXCOORD2; float3 worldTangent : TEXCOORD3; float3 worldBinormal : TEXCOORD4; float4 vertex : SV_POSITION; }; sampler2D _MainTex; sampler2D _NormalMap; float _Metallic; float _Smoothness; // GGX 法线分布函数 float DistributionGGX(float3 N, float3 H, float roughness) { float a roughness * roughness; float a2 a * a; float NdotH max(dot(N, H), 0.0); float NdotH2 NdotH * NdotH; float denom (NdotH2 * (a2 - 1.0) 1.0); return a2 / (3.14159 * denom * denom); } v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv v.uv; o.worldPos mul(unity_ObjectToWorld, v.vertex).xyz; o.worldNormal UnityObjectToWorldNormal(v.normal); o.worldTangent UnityObjectToWorldDir(v.tangent.xyz); o.worldBinormal cross(o.worldNormal, o.worldTangent) * v.tangent.w; return o; } fixed4 frag (v2f i) : SV_Target { // 法线贴图采样 float3 normalTS UnpackNormal(tex2D(_NormalMap, i.uv)); float3x3 TBN float3x3(i.worldTangent, i.worldBinormal, i.worldNormal); float3 N normalize(mul(normalTS, TBN)); float3 V normalize(_WorldSpaceCameraPos - i.worldPos); float3 L normalize(_WorldSpaceLightPos0.xyz); float3 H normalize(L V); float roughness 1.0 - _Smoothness; // 粗糙度 // 漫反射能量守恒金属无漫反射 float3 albedo tex2D(_MainTex, i.uv).rgb; float3 F0 lerp(0.04, albedo, _Metallic); // 菲涅尔基础反射率 float3 kS F0 (1.0 - F0) * pow(1.0 - max(dot(V, H), 0.0), 5.0); // Schlick菲涅尔 float3 kD (1.0 - kS) * (1.0 - _Metallic); // 漫反射比例 // 高光项 float D DistributionGGX(N, H, roughness); float NdotL max(dot(N, L), 0.0); float NdotV max(dot(N, V), 0.0); float3 specular (D * kS) / (4.0 * NdotL * NdotV 0.0001); float3 diffuse kD * albedo / 3.14159; float3 final (diffuse specular) * _LightColor0.rgb * NdotL; final UNITY_LIGHTMODEL_AMBIENT.rgb * albedo; return fixed4(final, 1); } ENDCG } } FallBack Standard}注释- 手动实现 TBN 矩阵将法线贴图转换到世界空间。-DistributionGGX函数模拟微表面法线分布。- 菲涅尔效应使用 Schlick 近似金属反射率取自albedo非金属固定 0.04。—## 四、经验模型 vs 物理模型如何选择| 维度 | 经验模型 | 物理模型 ||------|----------|----------||计算开销| 低适合移动端 | 高需 PS4/PC/高端手机 ||真实感| 卡通风格、非写实 | 照片级写实 ||参数直观性| 直接调颜色和强度 | 需理解金属度、粗糙度 ||环境一致性| 依赖美术调参 | 自动适配环境光照 |实践建议- 卡通游戏 → 使用 Blinn-Phong 或自定义经验模型。- 写实游戏 → 必须使用 PBRStandard Shader 或自研 BRDF。- 混合项目 → 低配设备用经验模型高配设备用 PBR。—## 总结从 Lambert 漫反射到 Blinn-Phong 高光再到 PBR 的 GGX 模型我们走过了一条从视觉近似到物理模拟的进化之路。经验模型以其高效和易控性适合风格化渲染而物理模型通过能量守恒和微表面理论带来了前所未有的真实感。在 Unity 中你可以通过 Surface Shader 快速使用标准 PBR也可以编写顶点/片元 Shader 实现自定义 BRDF。理解这两类模型的原理将让你在不同项目需求面前做出最优选择。

相关新闻