从理论到实践在Unity 2022 LTS中实现Blinn-Phong光照模型的完整指南当你在Unity中创建一个3D场景时物体的外观很大程度上取决于光照效果。Blinn-Phong模型作为计算机图形学中最经典的光照模型之一它完美地平衡了计算效率和视觉效果。本文将带你从零开始在Unity 2022 LTS中完整实现这个模型让你真正理解光照背后的数学原理而不仅仅是死记硬背公式。1. 准备工作与环境搭建在开始编写Shader之前我们需要确保Unity环境配置正确。打开Unity 2022 LTS创建一个新的3D项目。建议使用URPUniversal Render Pipeline模板因为它提供了更现代的渲染管线支持。项目设置检查清单确认Unity版本为2022 LTS确保项目使用URP可在Window Package Manager中安装创建一个测试场景包含一个简单的物体如Sphere和方向光提示在Unity中可以通过Window Rendering Lighting Environment面板调整环境光设置接下来我们需要创建一个自定义Shader。在Project视图中右键点击选择Create Shader Unlit Shader。将其重命名为BlinnPhongShader。这个初始的Unlit Shader将作为我们的基础模板。2. 理解Blinn-Phong模型的三大组件Blinn-Phong模型由三个主要部分组成每个部分都模拟了光线与表面交互的不同方式环境光Ambient模拟间接光照为物体提供基础亮度漫反射Diffuse模拟粗糙表面的均匀散射镜面反射Specular模拟光滑表面的高光反射2.1 环境光实现环境光是最简单的部分它不考虑光源方向或视角方向。在Shader中我们可以这样定义环境光float3 ambient _AmbientColor.rgb * _AmbientIntensity;在Properties块中添加以下参数以便在材质面板中调整_AmbientColor(Ambient Color, Color) (0.1, 0.1, 0.1, 1) _AmbientIntensity(Ambient Intensity, Range(0, 1)) 0.12.2 漫反射实现漫反射遵循Lambert余弦定律计算光线方向与表面法线的夹角。在Shader中我们需要获取表面法线normal计算光线方向lightDir计算点积dot productfloat3 diffuse _LightColor0.rgb * _DiffuseColor.rgb * max(0, dot(normal, lightDir));对应的Properties参数_DiffuseColor(Diffuse Color, Color) (1, 1, 1, 1)2.3 镜面反射实现Blinn-Phong模型改进了传统的Phong模型使用半角向量halfway vector代替反射向量计算效率更高float3 viewDir normalize(_WorldSpaceCameraPos - i.worldPos); float3 halfwayDir normalize(lightDir viewDir); float spec pow(max(0, dot(normal, halfwayDir)), _Glossiness); float3 specular _LightColor0.rgb * _SpecularColor.rgb * spec;对应的Properties参数_SpecularColor(Specular Color, Color) (1, 1, 1, 1) _Glossiness(Glossiness, Range(1, 256)) 323. 完整Shader代码实现现在我们将所有部分组合成一个完整的Shader。以下是完整的Shader代码Shader Custom/BlinnPhong { Properties { _AmbientColor(Ambient Color, Color) (0.1, 0.1, 0.1, 1) _AmbientIntensity(Ambient Intensity, Range(0, 1)) 0.1 _DiffuseColor(Diffuse Color, Color) (1, 1, 1, 1) _SpecularColor(Specular Color, Color) (1, 1, 1, 1) _Glossiness(Glossiness, Range(1, 256)) 32 } SubShader { Tags { RenderTypeOpaque } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc #include Lighting.cginc struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float3 normal : NORMAL; float3 worldPos : TEXCOORD0; }; float4 _AmbientColor; float _AmbientIntensity; float4 _DiffuseColor; float4 _SpecularColor; float _Glossiness; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.normal UnityObjectToWorldNormal(v.normal); o.worldPos mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag (v2f i) : SV_Target { // 环境光 float3 ambient _AmbientColor.rgb * _AmbientIntensity; // 漫反射 float3 normal normalize(i.normal); float3 lightDir normalize(_WorldSpaceLightPos0.xyz); float3 diffuse _LightColor0.rgb * _DiffuseColor.rgb * max(0, dot(normal, lightDir)); // 镜面反射 float3 viewDir normalize(_WorldSpaceCameraPos - i.worldPos); float3 halfwayDir normalize(lightDir viewDir); float spec pow(max(0, dot(normal, halfwayDir)), _Glossiness); float3 specular _LightColor0.rgb * _SpecularColor.rgb * spec; // 组合所有光照 float3 finalColor ambient diffuse specular; return float4(finalColor, 1); } ENDCG } } }4. 调试与优化技巧实现Shader后你可能会遇到一些常见问题。以下是几个调试技巧4.1 常见问题排查问题现象可能原因解决方案物体全黑法线计算错误检查normal计算确保已归一化高光异常半角向量计算错误检查viewDir和lightDir是否归一化光照不变化光源类型错误确保使用方向光而非点光源4.2 性能优化建议减少计算将不变的计算移到顶点着色器使用half精度对于移动平台使用half代替float简化公式在可接受范围内简化数学运算注意在移动设备上高光的幂运算pow可能比较昂贵可以考虑使用查找表优化4.3 可视化调试技巧在开发过程中可以临时输出中间计算结果来调试// 调试法线 return float4(normal * 0.5 0.5, 1); // 调试漫反射 return float4(diffuse, 1); // 调试高光 return float4(specular, 1);5. 进阶应用与扩展掌握了基础实现后我们可以进一步扩展这个Shader5.1 添加纹理支持// 在Properties中添加 _MainTex(Main Texture, 2D) white {} // 在Shader中采样纹理 fixed4 texColor tex2D(_MainTex, i.uv); float3 diffuse _LightColor0.rgb * texColor.rgb * max(0, dot(normal, lightDir));5.2 实现多光源支持Unity的多光源渲染需要额外的PassPass { Tags { LightMode ForwardAdd } Blend One One CGPROGRAM // 与主Pass相同的代码但不包含环境光 ENDCG }5.3 法线贴图增强使用法线贴图可以增加表面细节// 采样法线贴图 float3 tangentNormal UnpackNormal(tex2D(_BumpMap, i.uv)); float3 worldNormal normalize(mul(tangentNormal, i.TBN));在实际项目中我发现高光反射的_Glossiness参数对最终效果影响最大。通常金属材质需要更高的值128-256而粗糙表面则适合较低的值16-64。通过调整这些参数你可以模拟从塑料到金属的各种材质效果。
别再死记公式了!用Unity 2022 LTS手把手复现Blinn-Phong光照模型(附完整Shader代码)
发布时间:2026/5/28 5:07:11
从理论到实践在Unity 2022 LTS中实现Blinn-Phong光照模型的完整指南当你在Unity中创建一个3D场景时物体的外观很大程度上取决于光照效果。Blinn-Phong模型作为计算机图形学中最经典的光照模型之一它完美地平衡了计算效率和视觉效果。本文将带你从零开始在Unity 2022 LTS中完整实现这个模型让你真正理解光照背后的数学原理而不仅仅是死记硬背公式。1. 准备工作与环境搭建在开始编写Shader之前我们需要确保Unity环境配置正确。打开Unity 2022 LTS创建一个新的3D项目。建议使用URPUniversal Render Pipeline模板因为它提供了更现代的渲染管线支持。项目设置检查清单确认Unity版本为2022 LTS确保项目使用URP可在Window Package Manager中安装创建一个测试场景包含一个简单的物体如Sphere和方向光提示在Unity中可以通过Window Rendering Lighting Environment面板调整环境光设置接下来我们需要创建一个自定义Shader。在Project视图中右键点击选择Create Shader Unlit Shader。将其重命名为BlinnPhongShader。这个初始的Unlit Shader将作为我们的基础模板。2. 理解Blinn-Phong模型的三大组件Blinn-Phong模型由三个主要部分组成每个部分都模拟了光线与表面交互的不同方式环境光Ambient模拟间接光照为物体提供基础亮度漫反射Diffuse模拟粗糙表面的均匀散射镜面反射Specular模拟光滑表面的高光反射2.1 环境光实现环境光是最简单的部分它不考虑光源方向或视角方向。在Shader中我们可以这样定义环境光float3 ambient _AmbientColor.rgb * _AmbientIntensity;在Properties块中添加以下参数以便在材质面板中调整_AmbientColor(Ambient Color, Color) (0.1, 0.1, 0.1, 1) _AmbientIntensity(Ambient Intensity, Range(0, 1)) 0.12.2 漫反射实现漫反射遵循Lambert余弦定律计算光线方向与表面法线的夹角。在Shader中我们需要获取表面法线normal计算光线方向lightDir计算点积dot productfloat3 diffuse _LightColor0.rgb * _DiffuseColor.rgb * max(0, dot(normal, lightDir));对应的Properties参数_DiffuseColor(Diffuse Color, Color) (1, 1, 1, 1)2.3 镜面反射实现Blinn-Phong模型改进了传统的Phong模型使用半角向量halfway vector代替反射向量计算效率更高float3 viewDir normalize(_WorldSpaceCameraPos - i.worldPos); float3 halfwayDir normalize(lightDir viewDir); float spec pow(max(0, dot(normal, halfwayDir)), _Glossiness); float3 specular _LightColor0.rgb * _SpecularColor.rgb * spec;对应的Properties参数_SpecularColor(Specular Color, Color) (1, 1, 1, 1) _Glossiness(Glossiness, Range(1, 256)) 323. 完整Shader代码实现现在我们将所有部分组合成一个完整的Shader。以下是完整的Shader代码Shader Custom/BlinnPhong { Properties { _AmbientColor(Ambient Color, Color) (0.1, 0.1, 0.1, 1) _AmbientIntensity(Ambient Intensity, Range(0, 1)) 0.1 _DiffuseColor(Diffuse Color, Color) (1, 1, 1, 1) _SpecularColor(Specular Color, Color) (1, 1, 1, 1) _Glossiness(Glossiness, Range(1, 256)) 32 } SubShader { Tags { RenderTypeOpaque } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc #include Lighting.cginc struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float3 normal : NORMAL; float3 worldPos : TEXCOORD0; }; float4 _AmbientColor; float _AmbientIntensity; float4 _DiffuseColor; float4 _SpecularColor; float _Glossiness; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.normal UnityObjectToWorldNormal(v.normal); o.worldPos mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag (v2f i) : SV_Target { // 环境光 float3 ambient _AmbientColor.rgb * _AmbientIntensity; // 漫反射 float3 normal normalize(i.normal); float3 lightDir normalize(_WorldSpaceLightPos0.xyz); float3 diffuse _LightColor0.rgb * _DiffuseColor.rgb * max(0, dot(normal, lightDir)); // 镜面反射 float3 viewDir normalize(_WorldSpaceCameraPos - i.worldPos); float3 halfwayDir normalize(lightDir viewDir); float spec pow(max(0, dot(normal, halfwayDir)), _Glossiness); float3 specular _LightColor0.rgb * _SpecularColor.rgb * spec; // 组合所有光照 float3 finalColor ambient diffuse specular; return float4(finalColor, 1); } ENDCG } } }4. 调试与优化技巧实现Shader后你可能会遇到一些常见问题。以下是几个调试技巧4.1 常见问题排查问题现象可能原因解决方案物体全黑法线计算错误检查normal计算确保已归一化高光异常半角向量计算错误检查viewDir和lightDir是否归一化光照不变化光源类型错误确保使用方向光而非点光源4.2 性能优化建议减少计算将不变的计算移到顶点着色器使用half精度对于移动平台使用half代替float简化公式在可接受范围内简化数学运算注意在移动设备上高光的幂运算pow可能比较昂贵可以考虑使用查找表优化4.3 可视化调试技巧在开发过程中可以临时输出中间计算结果来调试// 调试法线 return float4(normal * 0.5 0.5, 1); // 调试漫反射 return float4(diffuse, 1); // 调试高光 return float4(specular, 1);5. 进阶应用与扩展掌握了基础实现后我们可以进一步扩展这个Shader5.1 添加纹理支持// 在Properties中添加 _MainTex(Main Texture, 2D) white {} // 在Shader中采样纹理 fixed4 texColor tex2D(_MainTex, i.uv); float3 diffuse _LightColor0.rgb * texColor.rgb * max(0, dot(normal, lightDir));5.2 实现多光源支持Unity的多光源渲染需要额外的PassPass { Tags { LightMode ForwardAdd } Blend One One CGPROGRAM // 与主Pass相同的代码但不包含环境光 ENDCG }5.3 法线贴图增强使用法线贴图可以增加表面细节// 采样法线贴图 float3 tangentNormal UnpackNormal(tex2D(_BumpMap, i.uv)); float3 worldNormal normalize(mul(tangentNormal, i.TBN));在实际项目中我发现高光反射的_Glossiness参数对最终效果影响最大。通常金属材质需要更高的值128-256而粗糙表面则适合较低的值16-64。通过调整这些参数你可以模拟从塑料到金属的各种材质效果。