如何计算切线和Binormal?
在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? 我的意思是,计算二元法的公式是什么,基于这些信息我必须使用什么? 而关于切线?
无论如何,我将构buildTBNmatrix,所以如果你知道一个基于这些信息直接构buildmatrix的公式将是很好的!
哦,是的,我也有纹理坐标,如果需要的话。 就我所说的GLSL而言,每个顶点解决scheme都是很好的,我的意思是,一次不需要访问多个顶点信息。
—-更新—–
我发现这个解决scheme:
vec3切线; 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)); 如果(长度(c1)>长度(c2)) { tangent = c1; } 其他 { tangent = c2; } tangent = normalize(tangent); binormal = cross(v_nglNormal,tangent); binormal = normalize(binormal);
但是我不知道这是否是100%正确的。
您的问题的相关input数据是纹理坐标。 切线和Binormal是局部平行于物体表面的向量。 而在法线贴图的情况下,他们正在描述正常纹理的局部方向。
所以你必须计算纹理向量指向的方向(在模型的空间)。 假设你有一个三angular形ABC,纹理坐标HKL。 这给了我们载体:
D = BA E = CA F = KH G = LH
现在我们想用切线空间T,U表示D和E,即
D = Fs * T + Ft * U E = Gs * T + Gt * U
这是一个具有6个未知数和6个方程的线性方程组,可以写成
| Dx Dy Dz | | Fs Ft | | Tx Ty Tz | | | = | | | | | Ex Ey Ez | | Gs Gt | | Ux Uy Uz |
反转FGmatrix产量
| Tx Ty Tz | 1 | Gt -Ft | | Dx Dy Dz | | | = ----------------- | | | | | Ux Uy Uz | Fs Gt - Ft Gs | -Gs Fs | | Ex Ey Ez |
与顶点法线T和U一起形成局部空间基础,称为matrix描述的切空间
| Tx Ux Nx | | Ty Uy Ny | | Tz Uz Nz |
从切空间转换到对象空间。 进行照明计算需要与此相反。 有一点运动可以发现:
T' = T - (N·T) N U' = U - (N·U) N - (T'·U) T'
对vectorT'和U'进行归一化,称它们为切线和副法线,我们得到从物体到切线空间的matrix变换,在这里我们进行光照:
| T'.x T'.y T'.z | | U'.x U'.y U'.z | | Nx Ny Nz |
我们将T'和U'与顶点法线一起存储为模型几何体的一部分(作为顶点属性),以便我们可以在着色器中将它们用于照明计算。 我再说一遍:在着色器中,您不会确定切线和副法线,而是预先计算它们并将其存储为模型几何体的一部分(就像法线一样)。
(上面竖线之间的符号是所有matrix,而不是决定因素,通常使用竖线而不是括号中的括号。)
一般来说,你有两种生成TBNmatrix的方式:离线和在线。
-
在线 =使用派生指令在片段着色器中。 这些推导给你一个多边形的每个点平坦的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);
-
离线 =准备切线作为顶点属性。 这是更难以得到,因为它不会只是添加另一个顶点属性,但也将需要重新组合所有其他属性。 此外,它不会100%给你一个更好的performance,因为你会得到存储/传递/animation(!)vector3顶点属性的额外成本。
math在很多地方都有描述(谷歌),包括@datenwolf的post。
这里的问题是2个顶点可能具有相同的法线和纹理坐标,但是具有不同的切线。 这意味着你不能只是添加一个顶点属性到一个顶点,你需要将顶点分割为2,并为克隆指定不同的切线。
获得每个顶点独特的切线(和其他属性)的最好方法是在出口商中尽早做到这一点。 在按属性sorting纯顶点的阶段,您只需要将切线向量添加到sorting关键字。
作为解决问题的根本办法,考虑使用四元数 。 单个四元数(vec4)可以成功地表示一个预定义的手势的切向空间。 保持正交(包括传递到片段着色器)很容易,如果需要,存储和提取正常。 更多关于KRI wiki的信息 。
基于kvark的回答,我想补充更多的想法。
如果你需要一个正交归一化的正切空间matrix,你必须以任何方式做一些工作。 即使添加了切线和副法线属性,它们也会在着色器阶段进行插值,最后它们既不会标准化,也不会相互正交。
假设我们有一个归一化的正常向量 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;
当然,正交归一化的正切空间matrix可以通过使用交叉乘积来计算,但是这只适用于右手系统。 如果一个matrix是镜像的(左手系统),它将转向右手系统:
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 );
正交化matrix的常用方法是Gram-Schmidt过程 :
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 );
另一种可能性是使用由纹理坐标texC_dx
, texC_dy
的派生产生的2 * 2matrix的行列式来考虑副法线向量的方向。 这个想法是正交matrix的行列式是1并且是正交镜像matrix-1的确定的一个。
行列式可以通过GLSL函数determinant( mat2( texC_dx, texC_dy )
进行计算,也可以通过公式texC_dx.x * texC_dy.y - texC_dy.x * texC_dx.y
。
为了计算正交normalize
正切空间matrix,不再需要副法向量,并且可以避免副法向量的单位向量( normalize
)的计算。
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