Math 如何计算切线和副法线?

Math 如何计算切线和副法线?,math,opengl,3d,glsl,shader,Math,Opengl,3d,Glsl,Shader,在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})(假设观察者位于世界中心) 现在,如何计算每个顶点的副法线和切线?我的意思是,计算副法线的公式是什么,基于这些信息我必须使用什么?关于切线呢 无

在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})(假设观察者位于世界中心)
现在,如何计算每个顶点的副法线和切线?我的意思是,计算副法线的公式是什么,基于这些信息我必须使用什么?关于切线呢

无论如何,我会构造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); vec3切线; vec3副法线; vec3 c1=交叉(a_法线,vec3(0.0,0.0,1.0)); vec3 c2=交叉(a_正常,vec3(0.0,1.0,0.0)); 如果(长度(c1)>长度(c2)) { 切线=c1; } 其他的 { 切线=c2; } 切线=规格化(切线); binormal=交叉(v_nglNormal,切线); binormal=标准化(binormal);
但是我不知道它是否100%正确。

与您的问题相关的输入数据是纹理坐标。切线和副法线是局部平行于对象曲面的向量。在法线贴图的情况下,它们描述了法线纹理的局部方向

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

D = B-A
E = C-A

F = K-H
G = L-H
现在我们想用切线空间T,U来表示D和E,即

D = F.s * T + F.t * U
E = G.s * T + G.t * U
这是一个由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 |
反转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 |
与顶点法线T和U一起形成一个局部空间基,称为切线空间,由矩阵描述

| T.x U.x N.x |
| T.y U.y N.y |
| T.z U.z N.z |
从切线空间转换到对象空间。要进行照明计算,需要与此相反。通过一点练习,你会发现:

T' = T - (N·T) N
U' = U - (N·U) N - (T'·U) T'
规范化向量T'和U',称之为切线和副法线,我们得到从对象到切线空间的矩阵变换,在这里我们进行照明:

| T'.x T'.y T'.z |
| U'.x U'.y U'.z |
| N.x  N.y  N.z  |
我们将T'和U'与顶点法线一起存储为模型几何体的一部分(作为顶点属性),以便我们可以在着色器中使用它们进行照明计算我重复:您不确定着色器中的切线和副法线,而是预计算它们并将它们存储为模型几何体的一部分(就像法线一样)。


(上面垂直条之间的符号都是矩阵,而不是行列式,它们通常在符号中使用垂直条而不是括号。)

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

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

    // 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);
    
  • 离线=准备切线作为顶点属性。这更难获得,因为它不仅会添加另一个顶点属性,还需要重新组合所有其他属性。此外,它不会100%为您提供更好的性能,因为您将获得存储/传递/设置(!)vector3顶点属性动画的额外成本

在很多地方(谷歌it)都有对数学的描述,包括@datenwolf帖子

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

获取每个顶点的唯一切线(和其他属性)的最佳方法是尽早在导出器中进行。在按属性对纯顶点排序的阶段,您只需要将切线向量添加到排序键


作为问题的根本解,考虑使用<强>四元数< /强>。一个四元数(vec4)可以成功地表示预定义便利度的切向空间。很容易保持正交(包括传递到片段着色器)、存储和提取法线(如果需要)。更多信息。

基于kvark的回答,我想补充更多想法

如果你需要一个正交规范化的切线空间矩阵,你必须以任何方式做一些工作。 即使添加切线和双法线属性,它们也将在着色器阶段进行插值 最后,它们既不是标准化的,也不是彼此正常的

假设我们有一个标准化法向量
n
,我们有切线
t
和副法线
b
,或者我们可以通过以下推导计算它们:

// derivations of the fragment position
vec3 pos_dx = dFdx( fragPos );
vec3 pos_dy = dFdy( fragPos );
// derivations of the texture coordinate
vec2 texC_dx = dFdx( texCoord );
vec2 texC_dy = dFdy( texCoord );
// tangent vector and binormal vector
vec3 t = texC_dy.y * pos_dx - texC_dx.y * pos_dy;
vec3 b = texC_dx.x * pos_dy - texC_dy.x * pos_dx;
当然,正交正切空间矩阵可以用叉积来计算, 但这只适用于右手系统。如果矩阵被镜像(左侧系统),它将变为右侧系统:

t = cross( cross( n, t ), t ); // orthonormalization of the tangent vector
b = cross( n, t );             // orthonormalization of the binormal vector 
                               //   may invert the binormal vector
mat3 tbn = mat3( normalize(t), normalize(b), n );
在上面的代码片段中,如果切线空间是左手系统,则会反转副法线向量。 要避免这种情况,必须走艰难的道路:

t = cross( cross( n, t ), t ); // orthonormalization of the tangent vector
b = cross( b, cross( b, n ) ); // orthonormalization of the binormal vectors to the normal vector 
b = cross( cross( t, b ), t ); // orthonormalization of the binormal vectors to the tangent vector
mat3 tbn = mat3( normalize(t), normalize(b), n );
正交化任何矩阵的常用方法是:

另一种可能性是使用2*2矩阵的行列式,该行列式由纹理坐标
texC_dx
的导数产生<
t = t - n * dot( t, n ); // orthonormalization ot the tangent vectors
b = b - n * dot( b, n ); // orthonormalization of the binormal vectors to the normal vector 
b = b - t * dot( b, t ); // orthonormalization of the binormal vectors to the tangent vector
mat3 tbn = mat3( normalize(t), normalize(b), n );
float texDet = texC_dx.x * texC_dy.y - texC_dy.x * texC_dx.y;
vec3 t = texC_dy.y * pos_dx - texC_dx.y * pos_dy;
t      = normalize( t - n * dot( t, n ) );
vec3 b = cross( n, t );                      // b is normlized because n and t are orthonormalized unit vectors
mat3 tbn = mat3( t, sign( texDet ) * b, n ); // take in account the direction of the binormal vector