+BIT祝威+悄悄在此留下版了个权的信息说:

[译]为任意网格计算tangent空间的基向量

Computing Tangent Space Basis Vectors for an Arbitrary Mesh (Lengyel’s Method)

Modern bump mapping (also known as normal mapping) requires that tangent plane basis vectors be calculated for each vertex in a mesh. This article presents the theory behind the computation of per-vertex tangent spaces for an arbitrary triangle mesh and provides source code that implements the proper mathematics.

+BIT祝威+悄悄在此留下版了个权的信息说:

现代凹凸映射(也被称为法线映射)要求为网格中的每个顶点计算出其tangent平面基向量。本文描述了对任意三角形网格计算逐顶点tangent空间的数学理论,还提供了实现数学计算的源代码。

Mathematical Derivation 数学推导

[This derivation also appears in Mathematics for 3D Game Programming and Computer Graphics, 3rd ed., Section 7.8.]

[这一推导也出现在Mathematics for 3D Game Programming and Computer Graphics, 3rd ed.,章节7.8。]

+BIT祝威+悄悄在此留下版了个权的信息说:

We want our tangent space to be aligned such that the x axis corresponds to the u direction in the bump map and the y axis corresponds to the v direction in the bump map. That is, if Q represents a point inside the triangle, we would like to be able to write

我们想让我们的tangent空间这样布局:其x轴对应凹凸贴图的u方向,y轴对应凹凸贴图的v方向。(译者注:这话的意思是,想象一下,将正方形贴图拿起来,将其(0, 0)位置固定到顶点上,此时,其u方向就是我们要找的tangent空间的x轴,其v方向就是我们要找的tangent空间的y轴,三角形面的法线方向就是我们要找的tangent空间的z轴。显然,这样的x轴y轴对,有无数个,我们需要的,是其中唯一的一个。是哪个呢?继续看原文。。。)换句话说,设Q代表三角形内部的一个点,我们希望有

where P0 is the position of one of the vertices of the triangle, and (u0v0) are the texture coordinates at that vertex. The vectors T and B are the tangent and bitangent vectors aligned to the texture map, and these are what we’d like to calculate.

+BIT祝威+悄悄在此留下版了个权的信息说:

其中P0是三角形的某个顶点,(u0v0)是此顶点的纹理坐标。向量TB分别是tangent 和bitangent 向量,它们分别与纹理贴图的轴平行,是我们想求得的未知数。(译者注:这个公式的意思是,当Q位于P0P1的中点时,u就位于u0u1的中点,等等,当Q位于P0P1P2内部的某点时,通过缩放纹理,会让(u, v)位于同贴图上同样的位置上。顶点P0P1P2的位置构成的三角形A,与顶点P0P1P2的纹理坐标构成的三角形B,两者上的点,即Q和(u, v),建立了一个线性的对应关系。能够使这2个三角形A和B的这样线性关系成立的那个TB,就是我们需要的唯一的结果。)

Suppose that we have a triangle whose vertex positions are given by the points P0P1, and P2, and whose corresponding texture coordinates are given by (u0v0), (u1v1), and (u2v2). Our calculations can be made much simpler by working relative to the vertex P0, so we let

假设我们有一个三角形,其顶点位置由P0P1P2给定,其对应的纹理坐标由(u0v0)、(u1v1)和(u2v2)给定。通过相对于P0(进行归零),我们的计算可以简化很多,所以我们让

+BIT祝威+悄悄在此留下版了个权的信息说:

and

+BIT祝威+悄悄在此留下版了个权的信息说:

(译者注:这其实就是让P0和(u0, v0)归零,将三角形A和B都放到原点上。由于A和B是线性关系,所以这样的平移不会影响其线性关系。)

We need to solve the following equations for T and B.

我们需要求解下述等式中的TB

+BIT祝威+悄悄在此留下版了个权的信息说:

This is a linear system with six unknowns (three for each T and B) and six equations (the xy, and z components of the two vector equations). We can write this in matrix form as follows.

这是个线性系统,有6个未知数(TB各3个)和6个等式(2个向量等式的xyz分量)。我们可以将它改写为下述矩阵形式。

Multiplying both sides by the inverse of the (st) matrix, we have

+BIT祝威+悄悄在此留下版了个权的信息说:

两边同时乘以(st)矩阵的逆矩阵(译者注:然后将等号左右互换位置),我们有

This gives us the (unnormalized) T and B vectors for the triangle whose vertices are P0P1, and P2. To find the tangent vectors for a single vertex, we average the tangents for all triangles sharing that vertex in a manner similar to the way in which vertex normals are commonly calculated. In the case that neighboring triangles have discontinuous texture mapping, vertices along the border are generally already duplicated since they have different mapping coordinates anyway. We do not average tangents from such triangles because the result would not accurately represent the orientation of the bump map for either triangle.

这给出了顶点为P0P1P2的三角形的(译者注:即face的)(未标准化的)TB向量。为了找到顶点的tangent向量,我们对所有共享此顶点的三角形的tangent取平均值,类似于计算顶点的法线那样的方式。当相邻的三角形的纹理映射不连续时,其共享边上的顶点一般都已经被复制了一份,因为它们的纹理坐标毕竟是不同的。(译者注:想象一个人头部模型的三维网格及其贴图,贴图上的后脑勺部分肯定是被分开的,鼻子部分肯定是挨着的,所以网格上描述后脑勺的顶点是被复制了一份,是可以“掰开”的。还是不能想象的话,上网找找模型及其贴图,或者看看电影《画皮》。)我们不把这样的三角形计入平均化过程,因为其结果无法准确地代表(两个三角形中任意一个三角形的)凹凸贴图的朝向。(译者注:由于后脑勺的顶点被复制了一份,就不再共享边了,所以也不会计入平均化过程。)

+BIT祝威+悄悄在此留下版了个权的信息说:

Once we have the normal vector N and the tangent vectors T and B for a vertex, we can transform from tangent space into object space using the matrix

一旦我们有了顶点的法线向量N和tangent向量TB,我们就可以实施从tangent空间到object空间的转换了(用矩阵的形式)

To transform in the opposite direction (from object space to tangent space—what we want to do to the light direction), we can simply use the inverse of this matrix. It is not necessarily true that the tangent vectors are perpendicular to each other or to the normal vector, so the inverse of this matrix is not generally equal to its transpose. It is safe to assume, however, that the three vectors will at least be close to orthogonal, so using the Gram-Schmidt algorithm to orthogonalize them should not cause any unacceptable distortions. Using this process, new (still unnormalized) tangent vectors T′ and B′ are given by

+BIT祝威+悄悄在此留下版了个权的信息说:

为了向反方向变换(从object空间到tangent空间——我们想对光照方向这样做),我们直接使用此矩阵的逆矩阵即可。2个tangent向量未必是互相垂直的,它们也未必与法线向量垂直,所以此矩阵的逆矩阵未必等于其转置矩阵。(译者注:这是个数学定理吧,由3个互相垂直的向量构成的矩阵,其逆矩阵等于其转置矩阵。书到用时方恨少,有空学时最贪玩。)但可以安全地假设,这3个向量是接近互相垂直的,所以使用Gram-Schmidt算法来正交化它们,应该不会引起任何不可接受的失真。使用这个方法,新的(仍旧是未标准化的)tangent向量T′B′由下述公式给出

Normalizing these vectors
and storing them as the tangent and bitangent for a vertex lets us use the
matrix

标准化这些向量,将其保存为顶点的tangent和bitangent,这样我们就可以使用矩阵

to transform the direction
to light from object space into tangent space. Taking the dot product of the
transformed light direction with a sample from the bump map then produces the
correct Lambertian diffuse lighting value.

来将光线方向从object空间变换到tangent空间。对(变换后的光照方向)和(来自凹凸贴图的采样值)使用点积,就会产生正确的Lambertian漫反射光照值。

It is not necessary to store
an extra array containing the per-vertex bitangent since the cross
product N × T′ can be used to obtain mB′,
where m = ±1 represents the handedness of the tangent
space. The handedness value must be stored per-vertex since the bitangent B′ obtained
from N × T′ may point in the wrong direction.
The value of m is equal to the determinant of the matrix in
Equation (*). You might find it convenient to store the per-vertex tangent
vector T′ as a four-dimensional entity whose w coordinate
holds the value of m. Then the bitangent B′ can be
computed using the formula

没有必要用一个数组来保存逐顶点的bitangent,因为叉积N × T′可以用来获取mB′,其中m = ±1,表示tangent空间的惯用手(译者注:左手系or右手系)。惯用手值必须逐顶点地保存,因为用N × T′得到的bitangent B′可能指向错误的(译者注:即相反的)方向。m的值等于等式(*)中的矩阵的行列式(译者注:行列式其实就是一个数值)。你可能发现了,用四维向量保存逐顶点的tangent向量T′,其w分量保存m,比较方便。这样,bitangent B′就可以用下述公式计算

where the cross product
ignores the w coordinate. This works nicely for vertex shaders
by avoiding the need to specify an additional array containing the
per-vertex m values.

其中的叉积忽略了w分量。这样就可以在顶点着色器中,避免指定一个额外的用于保存逐顶点的m值的数组,棒棒哒。

Bitangent versus Binormal 副切线 versus 副法线

+BIT祝威+悄悄在此留下版了个权的信息说:

The term binormal is
commonly used as the name of the second tangent direction (that is
perpendicular to the surface normal and u-aligned tangent
direction). This is a misnomer. The term binormal pops up in the study of curves and
completes what is known as a Frenet frame about a particular point on a curve.
Curves have a single tangent direction and two orthogonal normal directions,
hence the terms normal and binormal. When discussing a coordinate frame at a
point on a surface, there is one normal direction and two tangent
directions, which should be called the tangent and bitangent.

名词binormal 通常用作第二个切线方向的名字(即垂直于表面法线和u方向的切线)。这是个误称。名词binormal出现在对曲线(curves )的研究中,它是Frenet坐标系的一部分,用于描述曲线上的点。曲线有一个切线方向和2个垂直的法线方向,因此有了名词normal和binormal。当讨论一个表面(surface)上一点处的坐标系时,有1个法线和2个切线方向,所以应当被称为tangent和bitangent

Source Code 源代码

The code below generates a
four-component tangent T in which the handedness of the local
coordinate system is stored as ±1 in the w-coordinate. The bitangent
vector B is then given by B = (N × T) · Tw.

+BIT祝威+悄悄在此留下版了个权的信息说:

下述代码计算了四维tangent
T,其tangent坐标系的惯用手(其值为±1)保存在w分量。之后,bitangent向量B可以通过B = (N × T) · Tw给出。

 #include "Vector4D.h"

 struct Triangle
{
unsigned short index[];
}; void CalculateTangentArray(long vertexCount, const Point3D *vertex, const Vector3D *normal,
const Point2D *texcoord, long triangleCount, const Triangle *triangle, Vector4D *tangent)
{
Vector3D *tan1 = new Vector3D[vertexCount * ];
Vector3D *tan2 = tan1 + vertexCount;
ZeroMemory(tan1, vertexCount * sizeof(Vector3D) * ); for (long a = ; a < triangleCount; a++)
{
long i1 = triangle->index[];
long i2 = triangle->index[];
long i3 = triangle->index[]; const Point3D& v1 = vertex[i1];
const Point3D& v2 = vertex[i2];
const Point3D& v3 = vertex[i3]; const Point2D& w1 = texcoord[i1];
const Point2D& w2 = texcoord[i2];
const Point2D& w3 = texcoord[i3]; float x1 = v2.x - v1.x;
float x2 = v3.x - v1.x;
float y1 = v2.y - v1.y;
float y2 = v3.y - v1.y;
float z1 = v2.z - v1.z;
float z2 = v3.z - v1.z; float s1 = w2.x - w1.x;
float s2 = w3.x - w1.x;
float t1 = w2.y - w1.y;
float t2 = w3.y - w1.y; float r = 1.0F / (s1 * t2 - s2 * t1);
Vector3D sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,
(t2 * z1 - t1 * z2) * r);
Vector3D tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r,
(s1 * z2 - s2 * z1) * r); tan1[i1] += sdir;
tan1[i2] += sdir;
tan1[i3] += sdir; tan2[i1] += tdir;
tan2[i2] += tdir;
tan2[i3] += tdir; triangle++;
} for (long a = ; a < vertexCount; a++)
{
const Vector3D& n = normal[a];
const Vector3D& t = tan1[a]; // Gram-Schmidt orthogonalize
tangent[a] = (t - n * Dot(n, t)).Normalize(); // Calculate handedness
tangent[a].w = (Dot(Cross(n, t), tan2[a]) < 0.0F) ? -1.0F : 1.0F;
} delete[] tan1;
}

CalculateTangentArray

How to cite this article 如何引用本文

Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software, 2001. http://terathon.com/code/tangent.html

Copyright © 2001–2017, Terathon Software LLC

+BIT祝威+悄悄在此留下版了个权的信息说:

[译]为任意网格计算tangent空间的基向量的更多相关文章

  1. 我发起了一个 网格计算 协议 开源项目 GridP

    GridP  是   Grid Protocol   的 全称  . 我在 <关于软件产业的两个契机>  https://www.cnblogs.com/KSongKing/p/95319 ...

  2. Grid Virtual Server 和 网格计算

    Grid Virtual Server 的 Virtual Server 源于 LVS (Linux Virtual Server) , LVS 的意思就是把 多个 Linux 服务器 联合起来构成一 ...

  3. [.NET网格计算框架] Alchemi

      Alchemi [.NET网格计算框架] 是 一个以使用简易为目的的Windows下的网格计算框架.它提供了:a)开发网格软件的编程环境 和 b)建造网格和运行网格软件的运行机制.       A ...

  4. opencv —— moments 矩的计算(空间矩/几何矩、中心距、归一化中心距、Hu矩)

    计算矩的目的 从一幅图像计算出来的矩集,不仅可以描述图像形状的全局特征,而且可以提供大量关于该图像不同的几何特征信息,如大小,位置.方向和形状等.这种描述能力广泛应用于各种图像处理.计算机视觉和机器人 ...

  5. Unity Shader-法线贴图(Normal)及其原理

    简介 以前经常听说“模型不好看啊,怎么办啊?”答曰“加法线”,”做了个高模,准备烘一下法线贴图”,“有的美术特别屌,直接画法线贴图”.....法线贴图到底是个什么鬼,当年天真的我真的被这个图形学的奇淫 ...

  6. android计算每个目录剩余空间丶总空间以及SD卡剩余空间

    ublic class MemorySpaceCheck { /** * 计算剩余空间 * @param path * @return */ public static String getAvail ...

  7. C#实现任意大数的计算和简单逻辑命题的证明——前言

    介绍 这是本人毕业设计的项目,一直想将其整理成文,可一不小心4年就过去了(这个时间又可以读个大学了).现在给自己定一个目标,一个月时间里将项目的所有关键点都整理出来.不然真怕一眨眼又一个4年过去了,而 ...

  8. Android为TV端助力 计算每个目录剩余空间丶总空间以及SD卡剩余空间

    ublic class MemorySpaceCheck { /** * 计算剩余空间 * @param path * @return */ public static String getAvail ...

  9. Spatial Transformer Networks(空间变换神经网络)

    Reference:Spatial Transformer Networks [Google.DeepMind]Reference:[Theano源码,基于Lasagne] 闲扯:大数据不如小数据 这 ...

随机推荐

  1. 为什么你有10年经验,但成不了专家?(重复性刻意训练+反馈修正,练习的精髓是要持续地做自己做不好的,太精彩了)真正的高手都有很强的自学能力,老师和教练的最重要作用是提供即时的反馈(莫非我从小到大学习不好的原因在这里?没有单独刻意训练?) good

    也许简单看书就是没有刻意训练.更没有反馈,所以没有效果 我倒是想起自己,研究VCL源码的时候,都是自己给自己提问,然后苦思冥想.自己解决问题,然后Windows编程水平果然上了一个台阶.对什么叫做“框 ...

  2. Z Order(Copy From WIN32.HLP)

    The Z order of a window indicates the window's position in a stack of overlapping windows. This wind ...

  3. Qt单元测试浅析

    Qt单元测试框架,使用于基于Qt的应用程序和库,先从一个简单的demo工程说起吧. 我们可以通过QtCreator来创建一个简单的Qt单元测试工程,夏天到了,这个demo工程的名字就叫Summer好了 ...

  4. Linux编辑器Vim和Emacs入门

    sudo 命令 debian系统没有自带,需要安装: apt-get install sudo 安装位置为 /usr/bin/sudo,对应配置文件为 /etc/sudoers sudoers授权格式 ...

  5. Python自学day-11

    一.RabbitMQ概述 RabbitMQ是一种消息队列,是一个公共的消息中间件,用于不同进程之间的通讯. 除了RabbitMQ以外,还有ZeroMQ.ActiveMQ等等. 前面学习了两种队列: 线 ...

  6. Azkaban学习之路(一)—— Azkaban 简介

    一.Azkaban 介绍 1.1 背景 一个完整的大数据分析系统,必然由很多任务单元(如数据收集.数据清洗.数据存储.数据分析等)组成,所有的任务单元及其之间的依赖关系组成了复杂的工作流.复杂的工作流 ...

  7. C#版剑指Offer-001二维数组中的查找

    题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数 ...

  8. Mac下安装redis5.0 与命令

    参考链接:https://blog.csdn.net/zyp1376308302/article/details/84257606 参开链接2:https://www.cnblogs.com/guan ...

  9. valet环境PHPstorm+xdebug调试

    1.安装xdebug 2.配置xdebug zend_extension="/usr/local/Cellar/php@7.2/7.2.18/pecl/20170718/xdebug.so& ...

  10. spring源码深度解析— IOC 之 自定义标签解析

    概述 之前我们已经介绍了spring中默认标签的解析,解析来我们将分析自定义标签的解析,我们先回顾下自定义标签解析所使用的方法,如下图所示: 我们看到自定义标签的解析是通过BeanDefinition ...