原文出处 https://www.cnblogs.com/lookof/p/3509970.html
- Normal Map中的值
-
有没有想过,Normal Map(法线贴图)为何看上去都是“偏蓝色”的?这是由于,在map中存储的值都是在Tangent Space(切空间)下的。好比,一根正好垂直于表面的法线向量在切空间下是(0,0,1),假如用一个char(注意不是unsigned char)来表达像素的话,该向量就会被转换为(0,0,127)。这样的值无疑是“蓝色”。因为大部分的法线都不会偏移这根“标准法线”太远(好比[0.1, 0.2, 0.8]...)因此大部分像素都是“偏蓝”的。用这种方式存储Normal,能够视为这种法线老是“贴着”模型表面“插”上去的,而不用考虑这根法线到底在世界空间/模型空间的什么地方,又会通过怎么样的转换。这样就能够与各类可能的空间变换操做解耦,并且直观友好,简单易懂。
- Tangent Space (切空间)
-
那么,什么是切空间?我我的理解,切空间就是针对表面上某个考察的点,以该点的uv二维坐标系表达该点的切线(tangent)和该点的次法线(binormal)所构成的切平面,再加上垂直于它们的法线,就组成了一个能够用来被描述的空间。该空间就是切空间,它的坐标系(三根轴,三个基“basis”)分别是tangent(对应x), binormal(对应y)和normal(对应z)。
这里,比较难理解的是切空间和uv坐标的对应关系。不妨假想如今有一个构造很是复杂的模型,但它的表面(surface)能够像剥鹿皮同样完整剥开,而它的uv也恰能够以这样的“比喻”展开。它的法线既到处都垂直于它的表面。如今想象全部的法线都“扎”在这个表面上,而后让咱们把这个表面以展uv的方式剥开并摊平(那么你如今面对的实际上是一张uv set)。当你聚焦(focus)到某一个点的法线上时,这根法线正对着你,它的方向就是切空间的z轴。而这张uv set就是“切平面”。链接该点与下一点的坐标u的方向就是该点的切平面的x轴,而链接该点与下一点的坐标v的方向,就是它的切平面的y轴。
出于省事的说法,有的人可能会这样描述,“切空间下的x轴和y轴就是顶点u,v的坐标” 。若是你听到某人这么说,你能够揣测他多是真的是行家,由于这个意思算是“点”对了。但不幸的是,严格来讲,这种说法是错误的。切空间下的x轴方向(切线方向),是该点u坐标指向下一点u坐标的方向;对应地,y轴方向(次法线方向),是该点v坐标指向下一点v坐标的方向。若是你在一张uv set上审视它,也就等因而在一个二维笛卡尔坐标系上审视它,那么获得的x轴方向就是“水平”的,而y轴方向就是“竖直”的。这里想要强调的意思是,不管是切线方向仍是次法线方向,它们做为一个方向,势必是由两点之间的走向关系决定的。单独的某点u,v坐标,仅仅是个值而已,单凭一个点的值,既不可能获得切线方向,也不可能获得次法线方向。换成这种说法 “切空间下的x轴和y轴就是顶点所在uv坐标系下的u轴和v轴”, 就对了。
- 求算 Tangent 与 Binormal -
- “切空间”下的切线
再来考虑一个问题。在空间中某点的position和normal是很容易知道的,那么相应也很容易获得该点的“切平面”。那么,可否随意在这个“切平面”上指定一条tangent和一条binormal,做为该点的切线和次法线?从数学理论上来说这没问题。随着随意构造的tangent,binormal和normal指定好后,一个空间坐标系就由此指定了。固然了,根据以前的假设,这个坐标系未必是正交的(由于tangent不必定垂直于binormal),但normal = tangent cross binormal。这样的空间坐标系能够构造出无数个来,但若是要构造出上一节讨论过的“切空间”坐标系,却只能有一种构造方法。而“切空间”在行业中是一种广泛认可和使用的空间,所以有必要弄清楚如何求算这个属于“切空间”下的tangent和binormal向量。(至于将切空间和uv坐标系联系起来究竟有什么好处,我还不是太理解。暂时做为事实接受下来)。注意这里说求算tangent和binormal向量,是指它们从切空间(tangent space)被“转换”,或者说“映射(mapping)”到物体坐标系下(object space)下的值。
- 从另外一个角度看“转换(映射)”
在开始正式推导前,为了可以使接下来的一个结论看起来“显而易见”,我想举一个直观的例子,从另一个角度谈谈我对“转换”,或者说“映射”的认识。想象有一段路面在某点开始分了叉,一条路是上坡,而另外一条路是下坡。一辆汽车行驶在这段路面上并开始上坡,太阳在它身后,将它的影子"映"在了那段下坡的路上。如今,能够这么理解,汽车和汽车影子分别处于不一样的两个空间内,一个是上坡的空间,另外一个是下坡的空间。但它们之间有某种联系,那就是当汽车在行驶时,它们都会发生相关联的变化。假如我知道,当汽车在上坡的空间中处于某点p1时,它的阴影处于下坡空间中的某点s1;当汽车前进到上坡空间中的某点p2时,它的阴影对应地前进到下坡空间中的s2。接下来我想知道,当汽车阴影在下坡的空间中前进了一个单位的长度时,对应地汽车在上坡的空间中前进了多少?
这个问题应该是“显而易见”的,答案是(p2-p1)/(s2-s1),这也是“除法”的基本含义。同时,当p2无限接近p1(因为关联性s2也无限接近s1)时,这也变成了“导数”的含义(dp/ds),由于这个问题实质上就是在问“变化率”的问题。在这个例子中,因为上坡和下坡都是一条直线,因此状况能够简化为dp = p2-p1, ds = s2-s1, 因而答案也等于dp/ds。
dp/ds这个值的单位,是处于dp所在的空间中的。回到上面这个例子中来,dp/ds这个表达式,是在说当汽车影子在本身的空间中变化ds个单位时,对应汽车在本身的空间中变化了dp个单位。同时也能够这么理解,汽车影子在本身空间中运动了ds个单位,“映射”到汽车所在的空间,汽车对应在本身的空间中运动了dp个单位。
这个问题能够归纳为,自变量(x)在本身的空间内变化一个单位,“映射"到因变量(y)所在空间中,因变量会变化dy/dx个单位。
有了这个基础的理解,能够将其推广到多维的状况。自变量和因变量都处于各自的空间中,它们的维数还不必定相等。例如,考察一架飞机在单位时间(t)内位移(s)的变化,就是ds/dt。其中ds是在三维空间中,而dt却处于一维空间(时间)中。这里它们各自是几维是不重要的,关键在于自变量与因变量之间的”关联“。正是因为这种”关联“,使得当自变量只变化微小的一丁点时,因变量也会变化那么微小的一丁点。
- *扩展阅读 : pbrt中的 pi = p0 + (dp/du) * u + (dp/dv) * v
(注:若是你没在看《pbrt》,或者你从没见过上面这个公式,那么此节能够略过不看。这节只是源于自问自答的一个想法。曾经为理解这个公式卡了好久,既然如今稍稍有些心得,那也该本着善始善终的态度所有记录下来为好。)
读过pbrt的同窗知道,第3.6.2节讲的是如何求得射线与三角形相交的那一点“微面(facet)"的所有信息。其中就包括tangent和binormal。只不过,它将tangent值表达成了dp/du, binormal值表达成了dp/dv。关于这个观点,在本文的上一节中已经进行了力所能及的理解。只不过,书中在进行推导时,是从下面这个假设起步的,即在由三个点pi(i=1,2,3)组成的三角形中,设p0是三角形所在平面的其中一点,那么则有:pi = p0 + (dp/du) * u + (dp/dv) * v。
这里,我就想补充解释一下为何会有这个结论。
提及来也简单,只是书中没有明说,这个p0点对应的uv坐标值就是(0,0)。而后咱们把这个公式变通一下,就成了:pi - p0 = (dp/du)*(u-0) + (dp/dv)*(v-0)。这个公式,就很像本文上节最后获得的结论。只不过是把点2换成了点i,点1换成了点0。书中的意思是说,在这个三角形所在的这个平面上,任意一点均可以经过这种公式计算获得,只要给定了uv(0,0)对应的p0点以及欲求点的uv坐标值(u,v)便可。
这里可能让人感到困惑的一个地方是,uv坐标与顶点之间的对应关系,通常是经过人为指定的。那凭什么认为uv坐标基点(0,0)所对应的p0,会刚好在pi(i=1,2,3)这个三角形所在的平面上?实际上这里说的uv坐标(0,0)点,并非咱们一般认为的人为指定的那个点,而是从数学角度上”推断“出的一个点。能够这样理解。既然咱们知道三角形的三个顶点的三维空间坐标值(xi, yi, zi)(i=1,2,3)与uv坐标值(ui, vi)(i=1,2,3),又知道三点可以肯定一个平面这个常识。那么就能够推断出在uv空间中的任意一点,在三角形所在的这个平面中一定存在对应的顶点。 那么也就能够肯定在这个平面上找到一点p0,使得它的uv坐标值是(0,0)。
- 开始求算!
有了以上知识理解上的准备,接下来的求算任务基本上就是直截了当的了,没什么太须要费脑力的地方。不过须要对线性代数的基础知识有点了解: 其实只要知道如何求逆矩阵就好了。
切线和次法线永远老是针对面(face)而言的,单独考察一个点的切线或是次法线没有意义。而构成一个面最简单的方式就是三角形,因此咱们就考察如何求算三角形的tangent和binormal。给定一个三角形,已知它的三个物理空间的顶点为pi(i=1,2,3),对应的uv空间坐标为uvi(i=1,2,3)。因为这是一个三角形平面,所以三个点的tangent值与binormal值都是该面上的值。设 tangent = dp/du, binormal = dp/dv。根据上几节的推导,咱们很容易就能够获得,
p2 - p1 = dp/du * (u2 - u1) + dp/dv * (v2 - v1)
p3 - p1 = dp/du * (u3 - u1) + dp/dv * (v3 - v1)
修改为矩阵形式,就成了下面这样,

再变换一下,就获得

求解这个式子的细节就不列在这里了。关键了解一下如何求一个矩阵的逆矩阵(伴随矩阵数除其行列式),而后根据矩阵的乘法运算规则就能获得结果。随着结果的求得,咱们也就知道了tangent和binormal的值。
- TBN Matrix (TBN 矩阵)
上节求得了
tangent(简写为
T)与
binormal(简写为
B),再叉乘一下就获得了
normal(简写为
N):即
N =
T X
B。 由此三个向量就能够构成一个空间,[
T, B, N]。
这个矩阵表示的是,切空间(tangent space)中的三个基向量被转换到当前坐标系下所对应的三个基向量构成的空间。任何一个切空间下的向量,经过这个矩阵即可以变换到当前坐标系下(好比模型的物体坐标系亦或它所在的世界坐标系),究竟被转到什么坐标系,这要看当时求算T,B时利用的Pi点所在的空间:若是Pi是模型的物体坐标系,那对应该矩阵也就会把切空间的向量转到物体坐标系下;若是Pi是模型所在的世界坐标系,那对应矩阵就会把向量转到世界坐标系下。好比,
normal in object space =
[T,B,N] X normal in tangent space
这里咱们关心的问题是,这种转换的意义是什么?费这么大劲作转换,究竟是为了什么?答案是,经过这种转换,就能够把Normal Map中的法线转换到对应的物体/世界坐标系下,与这个空间下的光线进行计算,从而可以算出对应此点的正确光照。咱们知道Normal Map的法线为何存储在“切空间”下是有道理的(第一节有提到),但它不能直接被使用。通过这样的转换,就能够与各类可能的空间坐标联系起来了。
不过常常用的,还不是把normal值转换到物体/世界坐标系下,而是反过来,把光线“反”转换到到切空间下,与normal计算出光照值。为何?由于一个模型可能有很是多的点,对应normal map中的normal值数量必然也是巨大的,若是把每一根normal都作转换,这种计算量的成本是至关高的。但反过来,光线就那么几条,转换一次光线就能给全部切空间下的normal使用,相对来讲这种计算要“便宜”得多,因此反转光线值这种作法是更为常见的作法。
light in tangent space =
[T,B,N]-1 X light in object space
一样须要求其逆矩阵。不过若是TBN三向量彼此正交的话(通常来讲是这样),那么它的逆矩阵就简单地等于它的转置矩阵。
实际上TBN的做用还不止于此,它的应用很普遍并且也很是重要。好比作displacement mapping时,基于Vector置换的作法就一样用到了TBN矩阵。用法和原理与Normal Map都是同样的,只不过此时Map换成了再切空间下的用来置换的向量,而非法线。
-----------------------
reference :
3.book "pbrt"(2nd edition) section-3.6.2