Unity Shader 切线空间数据是如何计算出来的

从建模软件的计算、到Unity的导入,再到最终的Shader构建,切线空间的计算是一套贯穿整个美术-技术流程的完整逻辑。不过这里需要先澄清一个关键点:切线空间的核心数据(切线 Tangent、手性标志 w),是在导入Unity时就已经计算好的,而不是在Shader运行时动态生成的。Shader的任务只是使用这些数据。

下面,我们就按数据从生成到应用的完整生命周期来梳理。

✍️ 第一步:在建模软件中"种下种子"

在3D建模软件里,艺术家们虽然很少手动计算切线,但他们的工作------UV展开,是整个计算过程的基石。

  • 核心原理 :切线空间的定义(X轴为切线T,Y轴为副法线B)与模型的 UV 坐标系直接挂钩。切线T的方向被设计为与UV的U方向在模型表面上对齐。

  • 数学推导:给定一个三角形,其三个顶点的位置和UV坐标,存在一个3x3的线性变换,能将UV坐标的增量(ΔU, ΔV)映射到世界空间的位置增量上。解这个方程组,就能得到切线T和副法线B的初始方向。

  • 实际问题:镜像UV的陷阱:为了最大化利用贴图空间,模型会镜像另一半的UV,但这会破坏UV的连续性,导致镜像部分的T/B方向无法与法线贴图正确匹配,产生光影错误。

🤖 第二步:Unity导入时的"智能修复" (MikkTSpace)

为了解决镜像UV导致的切向量方向错乱问题,Unity在模型导入阶段使用业界标准算法------MikkTSpace来完成计算。它的核心目标,就是为每个顶点计算出能保证法线贴图在所有情况下(包括镜像)都正确显示的切线数据。

MikkTSpace 工作流程:

  1. 分析网格与UV:读取模型的顶点位置、法线(Normal)和UV信息。

  2. 计算和平均化:为每个三角形面计算切线T和副法线B,并对共享顶点的面进行平均化处理。

  3. 正交化处理:强制调整切线T,使其与顶点法线N垂直,确保TBN坐标系的精确性和稳定性。

  4. 计算关键信号------tangent.w :通过检查T/B/N向量构成的手性(右手/左手系)计算出 tangent.ww的值是+1-1,代表了UV是否发生了镜像。

  5. 存储结果 :将最终的正交化切线T(xyz分量)和手性标志w存储在 Vector4 类型的 tangent 变量中。

正如官方文档,此算法已成为业界标准,被广泛用于各大3D建模软件包、法线贴图工具和图形引擎中。

🎮 第三步:在Shader中"激活"数据

当模型进入游戏后,Shader扮演"执行者"的角色。标准写法如下:

复制代码
// 从模型数据中获取法线(normalOS)和切线(tangentOS)
v2f vert (appdata v) {
    // ... 将法线和切线从模型空间转换到世界空间
    float3 normalWS = TransformObjectToWorldNormal(v.normalOS);
    float4 tangentOS = v.tangent; // Unity提供的切线是float4类型
    float3 tangentWS = TransformObjectToWorldDir(tangentOS.xyz);
    
    // 根据tangent.w构建正确的副法线(Bitangent/Binormal)
    float3 bitangentWS = cross(normalWS, tangentWS) * tangentOS.w;

    // 构建并传递TBN矩阵
    o.tspace0 = half3(tangentWS.x, bitangentWS.x, normalWS.x);
    o.tspace1 = half3(tangentWS.y, bitangentWS.y, normalWS.y);
    o.tspace2 = half3(tangentWS.z, bitangentWS.z, normalWS.z);
    // ...
}

需要注意的是,Unity旧版内置管线和基于UnityCG.cginc的代码,可能使用TANGENT_SPACE_ROTATION宏来构建TBN矩阵,但其核心原理是完全一致的。

🏗️ 重建TBN矩阵与转换法线

在片元着色器中,需要重建TBN矩阵,用法线贴图的数据替换原本的法线。

复制代码
// 从法线贴图采样并解码,得到切线空间下的法线数据(tangentNormal)
float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));

// 构建TBN矩阵,将切线空间法线转换到世界空间
float3x3 TBN = float3x3(i.tspace0, i.tspace1, i.tspace2);
float3 worldNormal = normalize(mul(tangentNormal, TBN));

💡 核心提示:理解 tangent.w

理解 tangent.w 的作用对于正确实现法线贴图至关重要,它能确保在镜像区域的光照方向始终正确。这个流程在Unity中通常是全自动的。但当我们需要手动构建TBN矩阵或在运行时动态调整模型时,对这些细节的理解就变得至关重要了。

相关推荐
yi碗汤园5 小时前
MQTT消息队列遥测传输协议
网络·网络协议·unity
林川~019 小时前
Unity 常用 API 与核心类全解:从生命周期到协程,附性能避坑清单(Unity 6 适配)
unity·游戏引擎·常用api
sensen_kiss10 小时前
CPT306 Coursework 1 个人游戏作业——坦克大战
unity·游戏程序·游戏策划
蒸鱼Yuzheng11 小时前
Unity 与 Unreal 测试工具怎么回答:Profiler、日志、自动化与证据链
测试工具·unity·性能分析·游戏测试·unrealengine
野区捕龙为宠12 小时前
Unity WebGL平台 通过 URL 加载图片(UGUI RawImage / 模型材质)
unity·团结引擎
阿松爱学习1 天前
【Unity开发】FileStream 详解及用法指南
unity·c#·unity开发
想做后端的前端1 天前
Unity-UIGI优化Rebatch与Rebuild
unity
玖玥拾2 天前
Unity Shader 基础与图形学(一)
unity·图形渲染·图形学·shader
WiChP2 天前
【V0.1B16】从零开始的2D游戏引擎开发之路
开发语言·算法·游戏引擎
百里香酚兰2 天前
【Unity学习笔记】如何把粉色Prefab还原
笔记·学习·unity