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

相关推荐
叶帆9 天前
【YFIOs】用C#开发硬件之设备上云
开发语言·unity·c#
久数君9 天前
AI三维建模工具“造形家”:地理场景三维化的高效解决方案
unity·glb·ai算法·ai三维建模工具·地图框选·造形家·城市建筑模型
会思考的猴子10 天前
Unity VFX 属性 Postion 和 TargetPostion
unity
hai31524754310 天前
九章编程法 · 猜数字游戏 (GW-BASIC 重构版) *
人工智能·microsoft·游戏引擎·游戏程序
心前阳光10 天前
Unity资源导入之自动化资源导入
unity·自动化·游戏引擎
心前阳光10 天前
Unity之2021.3.45f2c1发布安卓程序遇到的问题
android·unity·游戏引擎
纪纯10 天前
PicoVR Unity Integration SDK 3.4 常用交互API
unity·游戏引擎·vr·pico
龙智DevSecOps解决方案10 天前
3A 游戏优化技术栈:如何打通引擎级分析工具与 DevOps 持续集成管线?
unity·性能优化·游戏开发·技术美术·perforce·unrealengine
葛兰岱尔10 天前
从 SolidWorks 到 Three.js,从 Inventor 到 Unity——制造业CAD模型“几何-语义一体化“转换,不再是天方夜谭!
开发语言·javascript·unity
鼎艺创新科技10 天前
三维电子沙盘中OSGB倾斜摄影数据的加载与渲染
游戏引擎·cocos2d