关于 BumpMap 和 NormalMap 原理细节中的 Tangent Space 和 TBN 矩阵
这篇文章假使你已经对 BumpMap 和 NormalMap 的流程有基本的了解。
这两天因为项目需要又复习了下 NormalMap 的原理细节,一年多以前细细看过一次,到现在忘了很多,可是当初又没有写笔记,所以在纠结细节的这两天因为数学差而特别痛苦。
BumpMap 和 NormalMap 说起来大家都非常熟悉,但就是就我个人而言,初次分析背后的原理有很多容易混淆的地方,对于爱钻牛角尖的人很是折磨。
BumpMap 出现早,主要使用灰度图(高度图),实时计算法线,过程就和生成法线图的过程一样,利用相邻像素点的高度差向量叉撑计算法线,这个过程比较简单没有异议,有了法线之后的计算和 NormalMap 一致,对于 BumpMap 的的理论,找到了一篇40多页的论文:A Practical and Robust Bump-mapping Technique for Today's GPU,我现在是实在没时间去琢磨看完它。
对于 NormalMap 的使用,其中最重要的莫过于 Object Sapce (物体坐标系) 和 Tangent Space (切线坐标系,也称切线空间) 之间的转换过程,以及转换矩阵 TBN 矩阵。第一次接触是《Introduction to 3D Game Programming with DirectX 9.0c—A Shader Approach》这本书:假设 T, B, N 分别为相对于 Object Space 的沿着切线空间的 x, y, z 坐标轴的向量,那么从 Tangent Space 到 Object Space 的转换矩阵为:
| Tx Ty Tz |
MΔ = [T, B, N]T = | Bx By Bz |
| Nx Ny Nz |
假设 MΔ 为正交矩阵,那么从 Object Space 到 Tangent Space 的转换矩阵为:
| Tx Bx Nx |
M = MΔ-1 = ([T, B, N]T)-1 = [T, B, N] = | Ty By Ny |
| Tz Bz Nz |
然后预生成每个顶点的 T, B, N。
可能大家到这里就会觉得ok了,但是如果你看到了这篇文章《Derivation of the Tangent Space Matrix》,数学不好的人也许就会凌乱,因为在这里从 Tangent Spane 到 Object Space 的变换矩阵 M 与上面那本书里讲到的是相反的。即 MΔ = [T, B, N], M = MΔ-1 。(这篇文章讲解的 TBN 部分,与《Mathematics for 3D Game Programming and Computer Graphics》的第7.8节讲解的完全一致)。更有意思的是,我发现有人和我有同样的疑问,发在了 Ogre 论坛:http://www.ogre3d.org/forums/viewtopic.php?f=10&t=54827,比较早了2009年的,遗憾的是下面的回复都没有戳中他的点。
那这两种说法到底谁正确呢?从 Tangent Spane 到 Object Space 的变换矩阵到底是 [T, B, N] 还是 [T, B, N]T 呢?答案是全都正确!
因为看到这里线代学得不好的人很容易忽略一个条件就是:整个运算过程中使用的向量是行向量还是列向量,行向量左乘矩阵,列向量右乘矩阵。假设 T, B, N 是相对于 Local Space 的 Tangent Space 自身的坐标轴, 那么相对 Tangent Space 自身三个坐标轴自然是 T,(1, 0, 0),B,(0, 1 , 0), N,(0, 0, 1) ,这时有以下公式:
对于使用行向量:[T,, B,, N,]T M = [T, B, N]T
=> [(1, 0, 0),(0, 1 , 0), (0, 0, 1)] M = [T, B, N]T
=> M = [T, B, N]T
对于使用列向量:M [T,, B,, N,]= [T, B, N]
=> M [(1, 0, 0),(0, 1 , 0), (0, 0, 1)] = [T, B, N]
=> M = [T, B, N]
很明显,《Introduction to 3D Game Programming with DirectX 9.0c—A Shader Approach》一书中使用的是行向量,从 Tangant Space 到 Object Space 的计算过程为:
Vobject = Vtangent M = [Xtangent, Ytangent, Ztangent] [T, B, N]T;
Vtangent = Vobject M-1 ,如果 M 正交,则有:
Vtangent = Vobject M-1= Vobject MT
= [Xobject, Yobject, Zobject] [T, B, N]
= [Vobject dot T, Vobject dot B, Vobject dot N]
而《Derivation of the Tangent Space Matrix》和 《Mathematics for 3D Game Programming and Computer Graphics》中使用的是列向量,尤其是前者的矩阵都是使用的左乘,那么计算过程为:
VobjectT = M VtangentT = [T, B, N] [Xtangent, Ytangent, Ztangent]T;
VtangentT = M-1 VobjectT ,如果 M 正交,则有:
VtangentT = M-1 VobjectT = MT VobjectT
= [T, B, N]T [Xobject, Yobject, Zobject]T
= [VobjectT dot TT, VobjectT dot BT, VobjectT dot NT]
通过以上对比,是不是一目了然,而且无论怎样转换,最终还有一个可以检验的就是,从 Object Space 到 Tangent Space 转换一个向量的最终结果一定是,该向量分别与 T, B, N 三个向量分别点乘,作为新的 Tangent Space 中向量的三个分量,从以上的Vtangent的最终结果就可以看到。
额外补充,容易搞混的还有一点就是一旦假设 TBN 矩阵正交,那凌乱就来了,本来由于行向量和列向量就已经使 [T, B, N] 和 [T, B, N]T 的使用对于新手来说完全乱了阵脚,这个正交完全就是来捣乱的,所以建议新手完全忽略 TBN 矩阵的正交,记住 M = MΔ-1 就够了。《Derivation of the Tangent Space Matrix》和 《Mathematics for 3D Game Programming and Computer Graphics》这两个都是到最后才说到正交化 TBN 概念,前面都是硬算 MΔ-1,所以它们介绍的理论更纯正,但细节也更复杂些。
好了,以上就是这两天纠结理解的结果,下一篇我将再分享一下根据顶点位置和 uv 坐标计算具体的 TBN 矩阵时一些细节的理解。