2018-7-25

ノーマルマップに関して覚書

雑な技術メモ

ノーマルマップを自前で実装する際に学んだことをメモ。

前提として、右手系で考える。

OpenGL と DirectX で法線マップ画像が異なる理由

OpenGL 用では緑が強いほど上方向へ傾いているが DirectX 用では下方向に傾いており、Y 軸に関して逆になっている。これは、OpenGL が右手系、DirectX が左手系と座標系が異なる事に起因している。

テクスチャに対して、手前を Z 軸正方向、右を X 軸正方向とすると、Y 軸正方向は右手系と左手系とでどうしても逆になってしまう。そのため、OpenGL と DirectX で法線マップ画像が異なっている。

接空間

法線マップは接空間上のベクトルを表現している。そのため右手系において、頂点に対して頂点法線を Z 軸、UV 座標の V 軸正方向を Y 軸、U 軸正方向を X 軸とする接空間を考える。さらに接空間の Z 軸を Normal、Y 軸を Binormal、X 軸を Tangent と定義する。

外積による Tangent、Binormal の計算

2 ベクトルの外積は、その 2 ベクトルが成す面に対して垂直なベクトルである。

そのため、説明記事によっては以下の方法で Tangent/Binormal を算出出来ると書いているものがある。

var tangent = cross(normal, vec3(0.0, 1.0, 0.0));
var binormal = cross(n, t);

しかし、外積は同じ向き or 逆方向の場合に結果が 0 ベクトルとなってしまう。そのためnormal = vec3(0.0, 1,0, 0.0)の時等で結果がおかしくなってしまう。

さらに、この方法だと Tangent や Binormal が UV 座標の U 軸/V 軸と一致している保証がない。頂点(0, 1, 0)において UV(0, 0)で頂点(0, -1, 0)において UV(0, 1)となるような UV マップのように、ローカル座標 Y 軸負方向に行くほど V が大きくなるような UV マップもありえる。UV 座標の U 軸が Tangent、V 軸が Binormal と対応していると定義しているのに、その Tangent/Binormal の算出に UV 情報を用いないためこの方法では正確な法線情報が算出出来ない。

deltaPosition, deltaUV による Tangent, Binormal の計算

参考:チュートリアル 13:法線マッピング

各三角面毎に頂点の Tangent/Binormal を、頂点座標と UV 座標の変化を元に算出する方法。

三角ポリゴンの頂点 v0,v1,v2 において、v0 の Tangent/Binormal は以下の式で算出出来る。(以下は擬似コード)

var deltaUV1 = v1.uv - v0.uv;
var deltaUV2 = v2.uv - v0.uv;
var deltaPos1 = v1.position - v0.position;
var deltaPos2 = v2.position - v0.position;

var r = 1.0 / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
var tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
var binormal = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;

この計算式を利用して、モデルの頂点座標/UV データを元に算出しシェーダーへ uniform として送る。

ローカル空間における法線情報の算出

ローカル空間における Normal/Tangent/Binormal があれば、以下の式でローカル空間における法線情報を導く事が出来る。

var sampleNormal = texture2D(normalTex, uv);
sampleNormal = (sampleNormal * 2.0) - 1.0; // -1.0~1.0の範囲に収める
var localNormal = tangent * sampleNormal.x + binormal * (-1.0 * sampleNormal.y) + normal * sampleNormal.z;

(-1.0 * sampleNormal.y)というように、Y に関して反転させているのは、テクスチャは UV 座標系において上下反転するから。

参考サイト

その5 0 から学ぶ法線マップ

チュートリアル 13:法線マッピング