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矩阵或在运行时动态调整模型时,对这些细节的理解就变得至关重要了。

相关推荐
mxwin6 小时前
Unity Shader 法线贴图跟切线空间有什么关系
unity·游戏引擎·贴图·shader
mxwin6 小时前
Unity Shader 贴图和采样的关系 如何保证贴图清晰
unity·游戏引擎·贴图·shader
心前阳光8 小时前
Unity之使用火山引擎实现文字提问流式回复
unity·游戏引擎·火山引擎
mxwin11 小时前
Unity Shader 什么是球谐光照 原理是什么
unity·游戏引擎·shader
心前阳光11 小时前
Unity之使用火山引擎实现语音识别
unity·语音识别·火山引擎
心前阳光11 小时前
Unity之使用火山引擎实现流式语音合成
unity·游戏引擎·火山引擎
心前阳光12 小时前
Unity之使用火山引擎实现音频剪辑提问,流式语音回复
unity·音视频·火山引擎
心前阳光12 小时前
Unity之音频剪辑提问,流式语音回复使用示例
unity·游戏引擎·音视频