Math 如何计算切线和副法线?
在OpenGL着色语言(GLSL)中讨论凹凸贴图、镜面反射高光以及此类内容 我有: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})(假设观察者位于世界中心) 现在,如何计算每个顶点的副法线和切线?我的意思是,计算副法线的公式是什么,基于这些信息我必须使用什么?关于切线呢 无
- 顶点数组(例如,{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})(假设观察者位于世界中心)
但是我不知道它是否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顶点属性动画的额外成本
作为问题的根本解,考虑使用<强>四元数< /强>。一个四元数(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