如何计算Tangent和Binormal?

use*_*230 43 opengl math 3d glsl

在OpenGL着色语言(GLSL)中讨论凹凸贴图,镜面高光和这些东西

我有:

  • 顶点数组(例如{0.2,0.5,0.1,0.2,0.4,0.5,...})
  • 法线数组(例如{0.0,0.0,1.0,0.0,1.0,0.0,...})
  • 点光源在世界空间中的位置(例如{0.0,1.0,-5.0})
  • 观众在世界空间中的位置(例如{0.0,0.0,0.0})(假设观众位于世界的中心)

现在,我如何计算每个顶点的Binormal和Tangent?我的意思是,计算Binormals的公式是什么,我必须根据这些信息使用什么?和切线相关?

无论如何我都会构建TBN矩阵,所以如果你知道根据这些信息直接构造矩阵的公式会很好!

哦,是的,如果需要,我也有纹理坐标.正如我所说的GLSL,我认为,每个顶点解决方案都是不错的,它不需要一次访问多个顶点信息.

----更新-----

我找到了这个解决方案

vec3 tangent;
vec3 binormal;

vec3 c1 = cross(a_normal, vec3(0.0, 0.0, 1.0));
vec3 c2 = cross(a_normal, vec3(0.0, 1.0, 0.0));

if (length(c1)>length(c2))
{
    tangent = c1;
}
else
{
    tangent = c2;
}

tangent = normalize(tangent);

binormal = cross(v_nglNormal, tangent);
binormal = normalize(binormal);

但我不知道它是否100%正确.

dat*_*olf 38

问题的相关输入数据是纹理坐标.Tangent和Binormal是局部平行于对象表面的向量.在正常映射的情况下,它们描述了正常纹理的局部方向.

因此,您必须计算纹理向量指向的方向(在模型的空间中).假设您有一个三角形ABC,纹理坐标为HKL.这给了我们矢量:

D = B-A
E = C-A

F = K-H
G = L-H
Run Code Online (Sandbox Code Playgroud)

现在我们想用切线空间T,U表示D和E.

D = F.s * T + F.t * U
E = G.s * T + G.t * U
Run Code Online (Sandbox Code Playgroud)

这是一个具有6个未知数和6个方程的线性方程组,它可以写成

| D.x D.y D.z |   | F.s F.t | | T.x T.y T.z |
|             | = |         | |             |
| E.x E.y E.z |   | G.s G.t | | U.x U.y U.z |
Run Code Online (Sandbox Code Playgroud)

反转FG矩阵产生

| T.x T.y T.z |           1         |  G.t  -F.t | | D.x D.y D.z |
|             | = ----------------- |            | |             |
| U.x U.y U.z |   F.s G.t - F.t G.s | -G.s   F.s | | E.x E.y E.z |
Run Code Online (Sandbox Code Playgroud)

与顶点法线T和U一起形成局部空间基础,称为切线空间,由矩阵描述

| T.x U.x N.x |
| T.y U.y N.y |
| T.z U.z N.z |
Run Code Online (Sandbox Code Playgroud)

从切线空间转换为对象空间.要进行照明计算,需要与此相反.通过一点运动,我发现:

T' = T - (N·T) N
U' = U - (N·U) N - (T'·U) T'
Run Code Online (Sandbox Code Playgroud)

归一化矢量T'和U',将它们称为切线和副法线,我们得到矩阵从物体转换到切线空间,我们在那里进行照明:

| T'.x T'.y T'.z |
| U'.x U'.y U'.z |
| N.x  N.y  N.z  |
Run Code Online (Sandbox Code Playgroud)

我们将T'和U'与顶点法线一起存储为模型几何的一部分(作为顶点属性),以便我们可以在着色器中使用它们进行光照计算.我再说一遍:你没有在着色器中确定切线和副法线,你预先计算它们并将它们存储为模型几何的一部分(就像法线一样).

(上面的垂直条之间的符号都是矩阵,从不是决定因素,它们的符号通常使用竖线而不是括号.)


kva*_*ark 17

通常,您有两种生成TBN矩阵的方法:离线和在线.

  • 使用派生指令在片段着色器中在线 =右.这些推导为多边形的每个点提供了平坦的TBN基础.为了得到平滑的,我们必须基于给定的(平滑的)顶点法线重新正交化它.这个过程在GPU上比初始TBN提取更加繁重.

    // compute derivations of the world position
    vec3 p_dx = dFdx(pw_i);
    vec3 p_dy = dFdy(pw_i);
    // compute derivations of the texture coordinate
    vec2 tc_dx = dFdx(tc_i);
    vec2 tc_dy = dFdy(tc_i);
    // compute initial tangent and bi-tangent
    vec3 t = normalize( tc_dy.y * p_dx - tc_dx.y * p_dy );
    vec3 b = normalize( tc_dy.x * p_dx - tc_dx.x * p_dy ); // sign inversion
    // get new tangent from a given mesh normal
    vec3 n = normalize(n_obj_i);
    vec3 x = cross(n, t);
    t = cross(x, n);
    t = normalize(t);
    // get updated bi-tangent
    x = cross(b, n);
    b = cross(n, x);
    b = normalize(b);
    mat3 tbn = mat3(t, b, n);
    
    Run Code Online (Sandbox Code Playgroud)
  • 离线 =准备切线作为顶点属性.这更难以获得,因为它不仅会添加另一个顶点属性,还需要重新组合所有其他属性.此外,它不会100%为您提供更好的性能,因为您将获得存储/传递/动画(!)vector3顶点属性的额外成本.

数学在许多地方描述(google it),包括@datenwolf帖子.

这里的问题是2个顶点可能具有相同的法线和纹理坐标但是不同的切线.这意味着您不能只是将顶点属性添加到顶点,您需要将顶点拆分为2并为克隆指定不同的切线.

获得每个顶点的唯一切线(和其他属性)的最佳方法是尽可能早地在导出器中执行此操作.在按属性排序纯顶点的阶段,您只需将切线向量添加到排序键即可.

作为问题的根本解决方案,请考虑使用四元数.单个四元数(vec4)可以成功地表示预定义的手感的切向空间.保持正交(包括传递给片段着色器)很容易,如果需要,存储和提取正常.有关KRI维基的更多信息.

  • @user464230:那些四元组只是 TBN 矩阵的更紧凑的表示。切线空间可以理解为将表面局部切线空间与(全局)对象空间对齐(旋转)所需的局部变换。即 TBN 矩阵是一个旋转矩阵。现在,旋转也可以表示为四元数。所以得到这个切线四元数的方法(@kvark:顺便说一句好主意!)是确定 TBN 矩阵并导出它的等效四元数,这是一个特征值问题。维基百科有数学:http://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation (2认同)

Rab*_*d76 6

根据kvark的回答,我想补充更多的想法。

\n\n

如果您需要正交归一化的切线空间矩阵,则必须以任何方式做一些工作。\n即使您添加切线和副法线属性,它们也会在着色器阶段进行插值\n并且最后它们既不会标准化,也不会被标准化。彼此都很正常。

\n\n

假设我们有一个归一化的法线向量 n,并且我们有正切t和副法线b,或者我们可以根据推导来计算它们,如下所示:

\n\n
// derivations of the fragment position\nvec3 pos_dx = dFdx( fragPos );\nvec3 pos_dy = dFdy( fragPos );\n// derivations of the texture coordinate\nvec2 texC_dx = dFdx( texCoord );\nvec2 texC_dy = dFdy( texCoord );\n// tangent vector and binormal vector\nvec3 t = texC_dy.y * pos_dx - texC_dx.y * pos_dy;\nvec3 b = texC_dx.x * pos_dy - texC_dy.x * pos_dx;\n
Run Code Online (Sandbox Code Playgroud)\n\n

当然,可以使用叉积来计算正交化切空间矩阵,但这仅适用于右手系统。如果矩阵被镜像(左手系统),它将转向右手系统:

\n\n
t = cross( cross( n, t ), t ); // orthonormalization of the tangent vector\nb = cross( n, t );             // orthonormalization of the binormal vector \n                               //   may invert the binormal vector\nmat3 tbn = mat3( normalize(t), normalize(b), n );\n
Run Code Online (Sandbox Code Playgroud)\n\n

在上面的代码片段中,如果切线空间是左手系,则副法线向量会反转。\n为了避免这种情况,必须克服困难:

\n\n
t = cross( cross( n, t ), t ); // orthonormalization of the tangent vector\nb = cross( b, cross( b, n ) ); // orthonormalization of the binormal vectors to the normal vector \nb = cross( cross( t, b ), t ); // orthonormalization of the binormal vectors to the tangent vector\nmat3 tbn = mat3( normalize(t), normalize(b), n );\n
Run Code Online (Sandbox Code Playgroud)\n\n

正交化任何矩阵的常用方法是Gram\xe2\x80\x93Schmidt 过程

\n\n
t = t - n * dot( t, n ); // orthonormalization ot the tangent vectors\nb = b - n * dot( b, n ); // orthonormalization of the binormal vectors to the normal vector \nb = b - t * dot( b, t ); // orthonormalization of the binormal vectors to the tangent vector\nmat3 tbn = mat3( normalize(t), normalize(b), n );\n
Run Code Online (Sandbox Code Playgroud)\n\n

另一种可能性是使用 2*2 矩阵的行列式,该行列式由纹理坐标 , 的推导得出texC_dxtexC_dy以考虑副法向量的方向。这个想法是正交矩阵的行列式是1,正交镜像矩阵的确定行列式是-1。

\n\n

行列式既可以通过 GLSL 函数计算determinant( mat2( texC_dx, texC_dy ),也可以通过其公式计算texC_dx.x * texC_dy.y - texC_dy.x * texC_dx.y

\n\n

对于正交归一化切空间矩阵的计算,不再需要副法向量,并且normalize可以回避副法向量的单位向量\n(·)的计算。

\n\n
float texDet = texC_dx.x * texC_dy.y - texC_dy.x * texC_dx.y;\nvec3 t = texC_dy.y * pos_dx - texC_dx.y * pos_dy;\nt      = normalize( t - n * dot( t, n ) );\nvec3 b = cross( n, t );                      // b is normlized because n and t are orthonormalized unit vectors\nmat3 tbn = mat3( t, sign( texDet ) * b, n ); // take in account the direction of the binormal vector\n
Run Code Online (Sandbox Code Playgroud)\n