web-dev-qa-db-ja.com

接線と従法線の計算方法

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を計算できますか?つまり、従法線を計算するための式は何ですか、それらの情報に基づいて何を使用する必要がありますか?そして接線について?

とにかくTBNマトリックスを作成します。そのため、これらの情報に基づいてマトリックスを直接作成するための式がわかっていれば、それは素晴らしいことです。

ああ、そうだね、必要ならテクスチャー座標もある。 GLSLについてお話ししているように、頂点ごとのソリューションとしてはニースでしょう。つまり、一度に複数の頂点情報にアクセスする必要がないということです。

----更新-----

私はこの解決策を見つけました:

 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)); 
 
 if(length(c1)> length(c2))
 {
タンジェント= c1; 
} 
 else 
 {
 tangent = c2; 
} 
 
 tangent = normalize(tangent ); 
 
 binormal = cross(v_nglNormal、Tangent); 
 binormal = normalize(binormal); 

しかし、100%正しいかどうかはわかりません。

43
user464230

問題に関連する入力データは、テクスチャ座標です。接線と従法線は、オブジェクトの表面に局所的に平行なベクトルです。また、法線マッピングの場合、法線テクスチャのローカル方向を記述しています。

したがって、テクスチャベクトルが指す方向(モデルの空間内)を計算する必要があります。テクスチャ座標HKLの三角形ABCがあるとします。これにより、ベクターが得られます。

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'をモデルのジオメトリの一部として頂点の法線と共に(頂点属性として)格納し、シェーダーでライティング計算に使用できるようにします。 繰り返します:シェーダーで接線と従法線を決定するのではなく、それらを事前に計算し、モデルのジオメトリの一部として(法線のように)保存します。

(上記の垂直バーの間の表記はすべて行列であり、行列式ではありません。通常、表記法ではブラケットの代わりに垂直バーを使用します。)

37
datenwolf

一般に、TBNマトリックスを生成するには、オフラインとオンラインの2つの方法があります。

  • Online=派生命令を使用したフラグメントシェーダーの右側。これらの派生は、ポリゴンの各ポイントに対してフラットな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);
    
  • Off-line=頂点属性として接線を準備します。これは、別の頂点属性を追加するだけでなく、他のすべての属性を再構成する必要があるため、取得がより困難です。さらに、vector3頂点属性の格納/受け渡し/アニメーションの追加コストが発生するため、パフォーマンスが100%向上することはありません。

数学は、@ datenwolf投稿を含む多くの場所(google it)で説明されています。

ここでの問題は、2つの頂点の法線座標とテクスチャ座標が同じであるが、接線が異なる場合があることです。つまり、頂点に頂点属性を追加するだけでなく、頂点を2つに分割し、クローンに異なる接線を指定する必要があります。

頂点ごとに一意の接線(およびその他の属性)を取得する最良の方法は、エクスポーターでできるだけ早く=を実行することです。純粋な頂点を属性で並べ替える段階で、並べ替えキーに接線ベクトルを追加するだけです。

問題の根本的な解決策として、quaternionsの使用を検討してください。単一のクォータニオン(vec4)は、事前定義された便利さの接線空間を正常に表すことができます。正規直交(フラグメントシェーダーへの受け渡しを含む)を維持し、必要に応じて通常を保存および抽出するのは簡単です。 KRI wiki の詳細。

17
kvark

Kvarkからの回答に基づいて、さらに考えを追加したいと思います。

正規直交タンジェント空間行列が必要な場合は、何らかの方法で何らかの作業を行う必要があります。接線と従法線の属性を追加しても、シェーダーステージ中に補間され、最後には正規化されず、互いに垂直にもなりません。

正規化されたnormalvectornがあり、タンジェントtとbinormal 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 );
_

行列を直交化する一般的な方法は、 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 * 2行列の行列式を使用して、従法線ベクトルの方向を考慮することです。概念は、直交行列の行列式が1であり、直交鏡行列の行列式が-1であるということです。

行列式はGLSL関数determinant( mat2( texC_dx, texC_dy )で計算するか、式_texC_dx.x * texC_dy.y - texC_dy.x * texC_dx.y_で計算できます。

正規直交タンジェント空間行列の計算では、従法線ベクトルは不要になり、従法線ベクトルの単位ベクトル(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
_
0
Rabbid76