Unity URP 法线贴图如何生成 用什么工具创建
2026/6/13 1:07:01 网站建设 项目流程

从零理解法线贴图的原理,掌握从 Photoshop、Blender 到 Substance 的多种生成工具,并在 URP 管线中正确实现法线贴图的采样与光照计算。

一、什么是法线贴图

法线贴图(Normal Map)是一种特殊的纹理,它不存储颜色信息,而是逐像素地编码了表面的法线方向。在渲染时,GPU 读取法线贴图中的法线向量,替代模型原本的顶点插值法线,从而在不增加几何复杂度的情况下模拟出丰富的表面凹凸细节。

核心要点:法线贴图通过在像素着色器中逐片元替换表面法线,让光线在"假凹凸"上产生正确的反射方向。眼睛看到的是细节,而三角形数量并没有增加。这是现代实时渲染中最重要的"障眼法"之一。

二、法线贴图的工作原理

2.1 切线空间(Tangent Space)

绝大多数的法线贴图都使用切线空间。切线空间是一个以模型表面为参考的局部坐标系,由三个正交轴构成:

  • T(Tangent)— 切线方向,沿 UV 的 U 轴方向
  • B(Bitangent / Binormal)— 副切线方向,沿 UV 的 V 轴方向
  • N(Normal)— 顶点法线方向,垂直于表面

在切线空间中,一个完全平坦的表面法线是(0, 0, 1),在法线贴图中的颜色就是(128, 128, 255)即浅蓝色。这就是为什么法线贴图整体看起来偏蓝。

2.2 解码公式

在着色器中,法线贴图的采样值从[0, 1]范围映射到[-1, 1]

// 从法线贴图采样得到的颜色值 (0~1) float4 normalTex = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, IN.uv); // 解码:从 [0,1] 映射到 [-1,1] float3 tangentNormal = normalTex.xyz * 2.0 - 1.0; // 或者使用 Unity 内置函数(处理 DXT5nm 压缩格式) float3 tangentNormal = UnpackNormal(normalTex);

注意 DXT5nm 压缩:使用UnpackNormal()是最安全的做法。Unity 在构建时可能将法线贴图压缩为 DXT5nm 格式,此时 R 通道被丢弃,法线的 X 存于 A 通道,Y 存于 G 通道。直接* 2 - 1会得到错误结果。

三、工具矩阵:用什么生成法线贴图

创建法线贴图主要有两条路径:从 2D 纹理转换(高度图 → 法线贴图),和从高模烘焙到低模。下面逐一介绍主流工具。

四、工具推荐对比

工具类型价格上手难度推荐场景
Substance Designer2D 节点式$$$专业程序化纹理制作
Substance Painter高模烘焙$$$中高3D 资产纹理绘制
Blender Bake高模烘焙免费独立开发者、预算有限
Photoshop2D 滤镜$$快速转换、UI 纹理
NormalMap-Online在线 2D免费极低快速原型、学习测试
xNormal高模烘焙免费轻量专业烘焙
Materialize2D 转换免费从照片生成 PBR 材质

五、实战:用 Photoshop 从高度图生成法线贴图

这是最快捷的创建方式,适合有现成高度图或灰度纹理的场景。

  1. 准备高度图

    准备一张灰度图。白色 = 凸起(最高),黑色 = 凹陷(最低)。确保图片是 2 的幂尺寸(512、1024、2048 等),这是 GPU 纹理的基本要求。

  2. 应用法线贴图滤镜

    打开 Photoshop → 菜单栏 →滤镜 → 3D → 生成法线贴图(Filter → 3D → Generate Normal Map)。如果没有 3D 菜单,检查首选项中是否启用了图形处理器。

  3. 调节参数

    在弹出的对话框中调节以下关键参数:模糊(Blur)一般设为 0~1 避免细节丢失;细节缩放(Detail Scale)控制凹凸强度,默认 10,砖墙类可调至 15~20;反转 Y— Unity 使用 OpenGL 法线格式(Y+ 向上),确保不勾选 Invert Y。

  4. 保存并导入 Unity

    导出为 PNG 或 TGA。导入 Unity 后,在 Inspector 中将纹理类型设为Normal Map,勾选Create from Grayscale如果还没做法线转换。确保 Texture Shape 为 2D。

六、实战:用 Blender 从高模烘焙法线贴图

这是游戏资产制作的黄金标准流程,适合已有高模雕刻和低模拓扑的场景。

  1. 准备高低模

    低模:正确展开 UV、所有面朝外、面法线方向一致。高模:雕刻好细节,与低模对齐位置。两个模型应重叠在同一世界位置。

  2. 创建烘焙用材质

    选中低模,在 Shader Editor 中新建一个 Image Texture 节点,新建一张图片(如 2048×2048),保持该节点选中状态(橙色高亮边框)。这是关键一步,烘焙结果就输出到这个节点。

  3. 配置烘焙参数

    Render Properties → Bake → Bake Type 选择Normal。关键参数:Extrusion(挤出距离/ Cage)— 设为 0.01~0.05m 防止漏烘;Max Ray Distance— 通常 0.1m 足够;Space 保持Tangent

  4. 先选高模再选低模

    在 Object Mode 下先选中高模,然后 Shift 加选低模(低模为最后选中 = Active)。顺序不能错。

  5. 执行烘焙

    点击Bake按钮,等待完成。完成后在 UV Editor 中查看结果:蓝色为主色调,细节处有红绿变化,即表示成功。

  6. 导出

    Image → Save As → 导出为 PNG。导入 Unity,Texture Type 设为Normal Map

七、在 URP 中使用法线贴图

7.1 Unity URP Lit Shader

使用 URP 内置的 Lit Shader 是最简单的方式。将生成的法线贴图拖入材质的Normal Map槽位即可。URP Lit 内部已经完成了 TBN 矩阵构建、采样、解码和光照计算的全流程。

7.2 材质参数说明

参数作用建议值
Normal Map法线贴图纹理导入的 PNG/TGA
Normal Scale法线强度系数 (0~1)0.5~1.0,默认 1.0
Base Map基础颜色(Albedo)对应的漫反射贴图
Smoothness表面光滑度0.3~0.7(多数非金属)
Metallic金属度0(非金属)或 1(金属)

7.3 手动编写 URP 法线贴图 Shader

如果需要自定义光照或特殊效果,可以手写 Shader。以下是一个完整的 URP 法线贴图片段着色器:

Shader "Custom/URP_NormalLit" { Properties { _BaseMap("Base Map", 2D) = "white" {} _BaseColor("Base Color", Color) = (1,1,1,1) _NormalMap("Normal Map", 2D) = "bump" {} _NormalScale("Normal Scale", Range(0,2)) = 1 _Smoothness("Smoothness", Range(0,1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForward" } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 positionWS : TEXCOORD1; float3 normalWS : TEXCOORD2; float4 tangentWS : TEXCOORD3; }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap); CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; float4 _BaseColor; float4 _NormalMap_ST; float _NormalScale; float _Smoothness; CBUFFER_END Varyings vert(Attributes IN) { Varyings OUT; OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.positionWS = TransformObjectToWorld(IN.positionOS.xyz); OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS); OUT.tangentWS = float4( TransformObjectToWorldDir(IN.tangentOS.xyz), IN.tangentOS.w); OUT.uv = IN.uv; return OUT; } float4 frag(Varyings IN) : SV_Target { // 采样基底色 float4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv); float3 albedo = baseMap.rgb * _BaseColor.rgb; // 采样并解码切线空间法线 float4 normalTex = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, IN.uv); float3 tangentNormal = UnpackNormalScale(normalTex, _NormalScale); // 构建 TBN 矩阵,将法线从切线空间转换到世界空间 float3 N = normalize(IN.normalWS); float3 T = normalize(IN.tangentWS.xyz); float3 B = normalize(cross(N, T) * IN.tangentWS.w); float3x3 TBN = float3x3(T, B, N); float3 worldNormal = normalize(mul(tangentNormal, TBN)); // URP 主光照计算 Light mainLight = GetMainLight(); float NdotL = saturate(dot(worldNormal, mainLight.direction)); float3 diffuse = albedo * mainLight.color * NdotL; // 环境光 float3 ambient = SampleSH(worldNormal) * albedo; // 简易高光(Blinn-Phong) float3 viewDir = GetWorldSpaceViewDir(IN.positionWS); float3 halfDir = SafeNormalize(mainLight.direction + viewDir); float spec = pow(saturate(dot(worldNormal, halfDir)), 32.0); float3 specular = mainLight.color * spec * _Smoothness; return float4(diffuse + ambient + specular, 1.0); } ENDHLSL } } }

代码关键步骤:
UnpackNormalScale()— 解码法线贴图并乘以缩放系数
TBN 矩阵— 由世界空间的 T、B、N 三向量构建,将切线空间法线转换到世界空间
GetMainLight()— URP 内置函数,获取场景主方向光

八、常见问题与解决方案

问题 1:法线贴图导入后显示为灰色而非蓝色
原因:Unity 没有识别为法线贴图。
解决:Inspector 中将Texture Type设为Normal Map

问题 2:凹凸方向反了(凸的变凹)
原因:法线贴图的 Y 通道方向与 Unity 不匹配。Unity 使用 OpenGL 标准(Y+ = 向上),如果你的贴图是 DirectX 标准(Y+ = 向下),需要反转。
解决:在法线贴图的 Inspector 中勾选Flip Y Channel,或在 Photoshop 生成时不勾选 Invert Y。

问题 3:法线贴图在某些面上看起来是黑的
原因:模型的某些面法线方向反了,或者 UV 镜像导致切线空间的 winding order 不一致。
解决:检查模型面法线方向(Blender:Shift+N重新计算)、确保 UV 无镜像重叠。

问题 4:使用 UnpackNormal 报错
原因:没有包含正确的头文件。
解决:确保 include 了Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl

九、法线贴图的纹理导入设置

在 Unity 中正确导入法线贴图至关重要。以下是在 Inspector 中的推荐设置:

设置项推荐值说明
Texture TypeNormal Map必须设置,Unity 才会正确解码
Texture Shape2D标准 2D 纹理
sRGB (Color Texture)❌ 取消勾选法线贴图是数据纹理,不是颜色纹理,必须线性空间
Non-Power of 2ToNearest非 2 的幂尺寸自动缩放
CompressionNormal Quality / High Quality选择 Normal 压缩以使用 DXT5nm
Max Size2048 或根据需求多数情况 1024 或 2048 足够
Generate Mip Maps✅ 勾选生成多级渐远纹理,避免远处摩尔纹

关键:关闭 sRGB。法线贴图存储的是方向数据而非颜色数据。勾选 sRGB 会导致 Gamma 校正被应用到法线值上,使法线方向偏移,产生错误的光照结果。这是最常见的导入错误。

十、总结与最佳实践

  • 选对工具:简单转换用 Photoshop 或 NormalMap-Online;专业资产用 Substance Painter 烘焙;预算有限用 Blender。
  • 关闭 sRGB:法线贴图导入 Unity 后务必取消 sRGB 勾选。
  • 使用 UnpackNormal:永远用UnpackNormal()UnpackNormalScale()解码,不要手动*2-1
  • 注意 Y 轴方向:Unity 使用 OpenGL 法线格式(Y+ 向上),确保生成工具的输出格式匹配。
  • 法线贴图尺寸:通常 1024 或 2048 足够,过高分辨率对移动端性能影响显著。
  • 配合其他贴图:法线贴图与粗糙度贴图(Roughness)、AO 贴图配合使用,能产生更真实的材质效果。
  • 移动端优化:如果目标平台是移动端,考虑使用 ASTC 压缩格式,并在低端设备上将法线贴图降到 512。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询