news 2026/6/12 11:50:53

Unity URP 法线贴图色彩空间、编码与解码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity URP 法线贴图色彩空间、编码与解码

从切线空间到纹理像素,再到 Shader 中的法线重建 —— 逐步拆解法线贴图的完整数据流

1. 法线贴图是什么

法线贴图(Normal Map)是一张存储了表面法线方向的纹理。它不存储颜色,而是将三维向量 (n⃗.x, n⃗.y, n⃗.z) 编码到 RGB 三个通道中,让低面数模型在光照计算时呈现出高面数的凹凸细节。

一张典型的法线贴图看起来是蓝紫色的 —— 因为大多数表面法线指向"正上方"(+Z 方向),编码后对应 R=0.5, G=0.5, B=1.0,正是那种淡蓝紫颜色。

图 1:法线贴图的作用 —— 让低面数模型获得高面数的光照效果

2. 切线空间与法线方向

法线贴图中的法线定义在切线空间(Tangent Space)中。切线空间由三个基向量构成:

图 2:切线空间的三个基向量 —— 沿 UV 方向展开

关键性质:

  • 法线是单位向量,满足 x² + y² + z² = 1
  • 三个分量范围都是 [-1, +1]
  • "平坦"表面的法线 = (0, 0, 1),指向正 Z 方向
  • 切线空间跟随模型表面弯曲,所以法线贴图可以在不同模型上复用

3. 编码:从法线到颜色

纹理的 RGB 通道只能存储 [0, 1] 的值,而法线分量在 [-1, +1] 之间。编码的核心就是做一次线性映射

图 3:编码映射 —— [-1,+1] → [0,1] 的线性变换

编码公式(法线 → 纹理颜色):

// 每个通道独立编码 color.r = normal.x * 0.5 + 0.5; color.g = normal.y * 0.5 + 0.5; color.b = normal.z * 0.5 + 0.5;

常见法线值的编码结果

法线方向n (x, y, z)c (R, G, B)视觉颜色
平坦表面(+Z)(0, 0, 1)(0.5, 0.5, 1.0)#8080FF
朝右倾斜(1, 0, 0)(1.0, 0.5, 0.5)#FF8080
朝上倾斜(0, 1, 0)(0.5, 1.0, 0.5)#80FF80
朝左倾斜(-1, 0, 0)(0.0, 0.5, 0.5)#008080

直觉:法线贴图中越接近蓝紫色的区域,表面越平坦;越偏红/绿的区域,表面朝 X/Y 方向的倾斜越大。

4. 两种编码格式:RGB vs DXT5nm

Unity 支持两种法线贴图的存储方式,这是理解编码/解码的关键分歧点:

图 4:两种编码格式的通道布局与解码路径

为什么 DXT5nm 把 X 放到 Alpha 通道?
DXT5/BC3 压缩格式中,Alpha 通道有独立的 4-bit 插值,精度比 R/G/B 的共享 5-bit 更高。将 X 分量放到 Alpha 中可以在压缩后保留更多法线细节。而 Z 通道被丢弃是因为它可以由 X、Y 推导出来(单位向量约束),不存储反而节省了压缩空间。

DirectX vs OpenGL 绿通道方向

另一个常见困惑是绿通道(Y 分量)的方向:

在 Unity 的导入设置中可以通过"Flip Green Channel"选项来切换。两种格式互为绿通道取反关系。

5. 解码:UnpackNormal 的完整流程

Unity 内部通过两个关键函数完成法线贴图的解码。整个流程如下:

下面是 Unity 源码中两个关键解码函数的简化版:

// RGB 格式解码 —— 三个通道完整存储 float3 UnpackNormalRGB(float4 packedNormal) { float3 normal; normal.xy = packedNormal.rg * 2.0 - 1.0; // [0,1] → [-1,+1] normal.z = packedNormal.b * 2.0 - 1.0; // 直接读 Z return normalize(normal); }
// DXT5nm 格式解码 —— X 在 Alpha,Y 在 Green float3 UnpackNormalmapRGorAG(float4 packedNormal) { // 检测 R 通道是否被用来存 X(某些平台上 R=A) float2 rg_or_ag = packedNormal.rg; if (packedNormal.a >= 0.0) // Unity 通过编译宏决定路径 rg_or_ag = packedNormal.ag; // DXT5nm: 读 A 和 G float3 normal; normal.xy = rg_or_ag * 2.0 - 1.0; // 解码 X 和 Y normal.z = sqrt(1.0 - saturate( // 由单位向量约束推导 Z dot(normal.xy, normal.xy))); return normal; }

注意:Unity 的UnpackNormal()函数会根据纹理的导入设置自动选择正确的解码路径。你通常不需要手动判断格式。只要法线贴图在 Inspector 中被设置为"Normal Map"类型,Unity 就会正确处理编码和色彩空间。

6. 色彩空间:sRGB vs Linear

这是法线贴图最容易出错的地方。法线贴图的数据是数学向量,不是颜色,因此它不应该经过 sRGB → Linear 的伽马校正。

图 6:sRGB 解码对法线贴图的影响 —— 伽马校正确保暗部细节的设计初衷对法线数据是灾难性的

核心规则:

属性漫反射贴图法线贴图
数据性质颜色(视觉感知)数学向量
sRGB 标记true(需要伽马校正)false(原始数据)
采样后处理硬件自动 Linear 转换直接使用,不做转换
Unity 标记方式默认 sRGB = true设为 "Normal Map" 类型

Unity 如何处理?
当你在 Inspector 中将纹理类型设为"Normal Map",Unity 会:
1. 自动将 sRGB 标记设为 false
2. 如果源纹理是 DXT5nm 格式,在导入时重排通道(R→A 或保持)
3. 在SAMPLE_TEXTURE2D采样时,因为 sRGB=false,GPU 不会做伽马转换,直接返回原始 [0,1] 数据

7. 常见陷阱与检查清单

1纹理未标记为 Normal Map结果:sRGB 伽马校正被应用,法线方向偏移,表面光照异常修复:Inspector → Texture Type → Normal Map2绿通道方向错误结果:凹凸方向反转,凸起变凹陷(或反之)修复:Inspector → Flip Green Channel / 在 Shader 中 Y 取反3手动乘以 2 减 1 但忘了 Z 重建结果:DXT5nm 格式下 Z 分量来自 Blue 通道(值为 1),解码后 Z=1 而非正确值修复:使用 UnpackNormal 或手动从 xy 推导 z4切线空间未正确传递到 Fragment结果:法线贴图在模型不同部位方向不一致,出现光照条纹修复:确保 Vertex 中计算 TBN 并使用 correctTangentSpace 变体5BC5/BC7 平台差异不同平台压缩格式不同,用 #pragma multi_compile_local 处理

图 7:五大常见陷阱与修复方案

检查清单:每次使用法线贴图时,确认以下事项:
• 纹理类型 = Normal Map(自动设 sRGB=false)
• 绿通道方向与项目约定一致(DirectX / OpenGL)
• 使用UnpackNormal()而非手动解码
• 切线向量在 Vertex Shader 中正确计算并传递
• TBN 矩阵的三个向量都已归一化

总结:完整数据流

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/12 11:47:05

3分钟极速配置:PotPlayer百度字幕翻译插件完整实战指南

3分钟极速配置:PotPlayer百度字幕翻译插件完整实战指南 【免费下载链接】PotPlayer_Subtitle_Translate_Baidu PotPlayer 字幕在线翻译插件 - 百度平台 项目地址: https://gitcode.com/gh_mirrors/po/PotPlayer_Subtitle_Translate_Baidu 还在为外语视频的字…

作者头像 李华
网站建设 2026/6/12 11:37:53

LLMs-in-Finance高级教程:构建多智能体协作的金融分析系统

LLMs-in-Finance高级教程:构建多智能体协作的金融分析系统 【免费下载链接】LLMs-in-Finance LLMs in Finance - Generative AI - AI Agents 项目地址: https://gitcode.com/gh_mirrors/ll/LLMs-in-Finance LLMs-in-Finance是一个专注于将生成式AI与AI智能体…

作者头像 李华
网站建设 2026/6/12 11:37:20

为什么字符串反转在JavaScript中如此困难?Esrever的诞生背景

为什么字符串反转在JavaScript中如此困难?Esrever的诞生背景 【免费下载链接】esrever A Unicode-aware string reverser written in JavaScript. 项目地址: https://gitcode.com/gh_mirrors/es/esrever 在JavaScript开发中,字符串反转似乎是一个…

作者头像 李华