法線マッピング(Normal mapping)
オブジェクトの法線情報をビットマップに書き込んだものを法線マップ(Normal map)といい、それを使って凹凸感を出した描画をします。法線マップは、XYZをそれぞれRGBに対応させ、値を0〜255として、それぞれ128を引いた値にして向きを表現しています。
また、法線マップを扱うためには、頂点の位置XYZとテクスチャのUV座標のうち、UVZを使った接空間 (Tangent Space)が必要になります。座標系のもう1つの軸である従法線(Binormal)ベクトルは、通常はシェーダー内で、法線ベクトルと接ベクトルから外積を使って計算します。
- 頂点シェーダーのプログラムは次のようになります。
normal.vsh
// 頂点座標
attribute highp vec3 inVertex;
// 法線ベクトル
attribute mediump vec3 inNormal;
// テクスチャ座標
attribute mediump vec2 inTexCoord;
// 接ベクトル
attribute mediump vec3 inTangent;
// モデル−ビュー行列
uniform highp mat4 ModelViewMatrix;
// 射影行列
uniform highp mat4 ProjectionMatrix;
// ライトの向きベクトル
uniform mediump vec3 LightDirection;
// 法線ベクトル(フラグメントシェーダーに渡す値)
varying mediump vec3 Normal;
// テクスチャ座標(フラグメントシェーダーに渡す値)
varying mediump vec2 TexCoord;
// ライトのベクトル(フラグメントシェーダーに渡す値)
varying mediump vec3 LightVec;
// マテリアル
void main()
{
// 頂点座標を座標変換する。
gl_Position = ProjectionMatrix * ModelViewMatrix * vec4(inVertex, 1.0);
// フラグメントシェーダーにテクスチャ座標を渡す
TexCoord = inTexCoord;
// ライトの向きベクトルを逆にして、念の為に正規化する
highp vec3 lightDirection = normalize(-LightDirection);
// 従法線ベクトルを計算する
highp vec3 bitangent = cross(inNormal, inTangent);
// 接空間でのライトの向きを変換する行列を作成する
highp mat3 tangentSpaceXform = mat3(inTangent, bitangent, inNormal);
// ライトの向きベクトルを計算する
LightVec = lightDirection * tangentSpaceXform;
}
- フラグメントシェーダーのプログラムは次のようになります。
normal.fsh
// 拡散反射光用テクスチャ
uniform sampler2D DiffuseTexture;
// 法線マップ
uniform sampler2D NormalTexture;
// ライトの向き
uniform mediump vec3 LightDirection;
// 視点に向かうベクトル
uniform mediump vec3 Eye;
// 鏡面反射光の強度
uniform mediump float Power;
// 法線ベクトル(頂点シェーダーから渡された値)
varying mediump vec3 Normal;
// テクスチャ座標(頂点シェーダーから渡された値)
varying mediump vec2 TexCoord;
// 接空間でのライトの向き(フラグメントシェーダーから渡された値)
varying mediump vec3 LightVec;
// 拡散反射光に対する反射率
const mediump vec3 Diffuse = vec3(0.7, 0.7, 0.7);
// 鏡面反射光に対する反射率
const mediump vec4 Specular = vec4(1.0, 1.0, 1.0, 1.0);
// 環境光
const mediump vec3 Ambient = vec3(0.2, 0.2, 0.2);
void main()
{
// 法線マップから法線を取得する
mediump vec3 N = texture2D(NormalTexture, TexCoord).rgb * 2.0 - 1.0;
// ライトの向きベクトルを逆にして、念の為に正規化する
mediump vec3 L = normalize(-LightDirection);
// 視点に向かうベクトル
mediump vec3 E = Eye;
// ハーフベクトル
mediump vec3 H = normalize(L + E);
// 拡散反射光を計算する
mediump float df = max(0.0, dot(N, L));
// 鏡面反射光を計算する
mediump float sf = max(0.0, dot(N, H));
sf = pow(sf, Power);
// 最終的な色を光の明るさとテクスチャの色から決める。
gl_FragColor = Ambient + df * texture2D(DiffuseTexture, TexCoord) * Diffuse + sf * Specular;
}