目次
シャドウマッピング
透視シャドウマッピング(Perspective Shadow Mapping)
正確なものか不明ですが、なんとなく形になったのでメモします。
正確なものか不明ですが、なんとなく形になったのでメモします。
- GLGL1.3以上(GLGL1.5推奨) : Deprecated記述なし(たぶん)
- 2パス
- FBO使用

ライトがぐるぐると位置を移動します。
シェーダ側の処理は、最低限表示できるだけの記述に止めています。
もっといい記述があれば、変更するかもしれません。
もっといい記述があれば、変更するかもしれません。

ソースコード説明
描画前の準備
シェーダ準備
struct ShaderObject
{
GLuint programObject;
// uniform
GLuint mModelLocation;
GLuint mCameraViewLocation;
GLuint mCameraProjLocation;
GLuint mLightViewLocation;
GLuint mLightProjLocation;
GLuint shadowMapLocation;
GLuint colorLocation;
// attribute
GLuint vertexLocation;
GLuint normalLocation;
};
static ShaderObject shObj;
GLuint vertexShader;
GLuint fragmentShader;
vertexShader = LoadShader(GL_VERTEX_SHADER, "simple.vert");
fragmentShader = LoadShader(GL_FRAGMENT_SHADER, "simple.frag");
shObj.programObject = glCreateProgram();
glAttachShader(shObj.programObject,vertexShader);
glAttachShader(shObj.programObject,fragmentShader);
glLinkProgram(shObj.programObject);
シェーダを準備。
シャドウマップ用のテクスチャとFBO準備
static GLuint depthTextureId; // デプス・テクスチャID
static GLuint fboId; // FBO用ID
テクスチャ準備
glGenTextures(1, &depthTextureId);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthTextureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
(int)shadowMapWidth, (int)shadowMapHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
- テクスチャIDを取得(glGenTGextures)
- テクスチャ0を有効にする(glActiveTexture)
- (GPU側と)テクスチャIDをバインド(glBindTexture)
- テクスチャのパラメータ設定(glTexParameteri)
- GPU側のメモリ領域にテクスチャ用サイズ分を確保(glTexImage2D)
- テクスチャIDをゼロバインド(glBindTexture)
シャドウマップ用のFBO準備
glGenFramebuffers(1, &fboId);
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTextureId, 0);
GLenum FBOstatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(FBOstatus != GL_FRAMEBUFFER_COMPLETE)
printf("GL_FRAMEBUFFER_COMPLETE failed, CANNOT use FBO\n");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
- FBO用IDを取得(glGenFramebuffers)
- (GPU側と)FBOをバインド(glBindFramebuffer)
- FBOとテクスチャを関連付け(glFramebufferTexture2D)
シェーダ側引数へのアクセス準備
///////////////////////////////////////////////////////////////////////////
// for Fragment
//
glBindFragDataLocation(shObj.programObject, 0, "fragColor");
///////////////////////////////////////////////////////////////////////////
// attribute
//
shObj.vertexLocation = glGetAttribLocation(shObj.programObject, "a_vertex");
shObj.normalLocation = glGetAttribLocation(shObj.programObject, "a_normal");
///////////////////////////////////////////////////////////////////////////
// Uniform
//
shObj.shadowMapLocation = glGetUniformLocation(shObj.programObject,"ShadowMap");
shObj.mModelLocation = glGetUniformLocation(shObj.programObject,"mModel");
shObj.mCameraViewLocation = glGetUniformLocation(shObj.programObject,"mCameraView");
shObj.mCameraProjLocation = glGetUniformLocation(shObj.programObject,"mCameraProj");
shObj.mLightViewLocation = glGetUniformLocation(shObj.programObject,"mLightView");
shObj.mLightProjLocation = glGetUniformLocation(shObj.programObject,"mLightProj");
shObj.colorLocation = glGetUniformLocation(shObj.programObject,"color");
シーン・レンダリング
glUseProgram(shObj.programObject);
- シェーダを実行
1stパス(シャドウマップ生成)
glViewport(0, 0, (int)(shadowMapWidth), (int)(shadowMapHeight));
sl_LoadIdentityf(mLightProj);
sl_Perspectivef(mLightProj, 30.0f, (GLfloat)shadowMapWidth/(GLfloat)shadowMapHeight, 1.0, 40000.0);
sl_LoadIdentityf(mLightView);
sl_LookAtf(mLightView, light_pos[0], light_pos[1], light_pos[2], 0.0f, 0.0f, 0.0f, 0.0, 1.0, 0.0);
glCullFace(GL_FRONT);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
{
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.1, 4.0);
glUniformMatrix4fv(shObj.mCameraViewLocation, 1, GL_FALSE, mLightView);
glUniformMatrix4fv(shObj.mCameraProjLocation, 1, GL_FALSE, mLightProj);
//glUniformMatrix4fv(shObj.mLightViewLocation, 1, GL_FALSE, mLightView);
//glUniformMatrix4fv(shObj.mLightProjLocation, 1, GL_FALSE, mLightProj);
drawObjects();
glDisable(GL_POLYGON_OFFSET_FILL);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glCullFace(GL_BACK);
- ビューポートをシャドウマップ用のサイズに設定(glViewport)
- ライトのプロジェクション行列取得(sl_Perspectivef->mLightProj)
- ライトのビュー行列取得(sl_LookAtf->mLightView)
- ポリゴンの裏面のみを描画指定(glCullFace(GL_FRONT))
- 色バッファをすべてマスク(glColorMask)
- FBOを有効(glBindFramebuffer)
- デプス・バッファをクリア(glClear(GL_DEPTH_BUFFER_BIT))
- ポリゴンのオフセット(glPolygonOffset)
- ライトから見たシーンに設定(glUniformMatrix4fv)
- シーン描画(drawObject)
2ndパス(シーン描画)
glViewport(0, 0, (int)renderWidth, (int)renderHeight);
sl_LoadIdentityf(mCameraProj);
sl_Perspectivef(mCameraProj, 45.0, (GLfloat)renderWidth/(GLfloat)renderHeight, 1.0, 1000.0);
sl_LoadIdentityf(mCameraView);
sl_LookAtf(mCameraView, 5.0f, 6.0f, 7.0f, 0.0f, 1.0f, 0.0f, 0.0, 1.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUniformMatrix4fv(shObj.mCameraViewLocation, 1, GL_FALSE, mCameraView);
glUniformMatrix4fv(shObj.mCameraProjLocation, 1, GL_FALSE, mCameraProj);
glUniformMatrix4fv(shObj.mLightViewLocation, 1, GL_FALSE, mLightView);
glUniformMatrix4fv(shObj.mLightProjLocation, 1, GL_FALSE, mLightProj);
glUniform1i(shObj.shadowMapLocation, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthTextureId);
drawObjects();
- 通常の描画
シェーダ側のコード
バーテックス・シェーダ
- simple.vert
#version 150
in vec3 a_vertex;
out vec4 v_shadowCoord;
uniform mat4 mModel;
uniform mat4 mLightView;
uniform mat4 mLightProj;
uniform mat4 mCameraView;
uniform mat4 mCameraProj;
const mat4 bias = mat4(
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0
);
void main()
{
v_shadowCoord = bias * mLightProj * mLightView * mModel * vec4(a_vertex.xyz,1.0);
gl_Position = mCameraProj * mCameraView * mModel * vec4(a_vertex.xyz, 1.0);
}
わかりやすくするために、モデル、ビュー、プロジェクションの行列を分けています。
biasは、バイアス行列(bias matrix)というらしいです。
これは透視投影行列をテクスチャ座標に変換するのに必要のようです。
これは透視投影行列をテクスチャ座標に変換するのに必要のようです。
シャドウマップ用テクスチャ座標(v_shadowCorrd)は、
bias × ライト・プロジェクション行列 × ライト・ビュー行列 × モデル行列 × 頂点座標
より求めています。
フラグメント・シェーダ
- simple.frag
#version 150
in vec4 v_shadowCoord;
out vec4 fragColor;
uniform sampler2D ShadowMap;
uniform vec4 color;
void main()
{
vec4 shadowCoordWdiv = v_shadowCoord / v_shadowCoord.w;
float shadow = 1.0;
if (v_shadowCoord.w > 0.0) {
float distanceFromLight = textureProj(ShadowMap, shadowCoordWdiv).z;
shadow = (distanceFromLight < shadowCoordWdiv.z) ? 0.5 : 1.0;
}
fragColor = shadow * color;
}
シャドウマッピングにおける問題と回避
エイリアシングの回避
シャドウマッピングでは、エイリアシングの問題がつきまといます。
エイリアシングは、デプステクスチャの解像度が低いことで発生する影のギザギザです。
エイリアシングは、デプステクスチャの解像度が低いことで発生する影のギザギザです。

const float renderWidth = 640.0f; const float renderHeight = 480.0f; const float shadowMapRatio = 2.0f; const float shadowMapWidth = (renderWidth * shadowMapRatio); const float shadowMapHeight = (renderHeight * shadowMapRatio);
今回、シャドウマップのテクスチャサイズは、通常ウィンドウサイズの2倍にしました。
shadowMapRatioを1.0fにすると影のギザギザが目立つようになります。
ギザギザを抑えるためには、基本的にはテクスチャサイズを大きくすればよいのですが、処理が重くなるので、その辺はトレードオフになります。
最適なサイズというのは存在せず、シーンに合わせて設定するのが普通のようです。
あとはソフトシャドウイングとか。
shadowMapRatioを1.0fにすると影のギザギザが目立つようになります。
ギザギザを抑えるためには、基本的にはテクスチャサイズを大きくすればよいのですが、処理が重くなるので、その辺はトレードオフになります。
最適なサイズというのは存在せず、シーンに合わせて設定するのが普通のようです。
あとはソフトシャドウイングとか。
モアレ(moire)パタンの回避
影が縞模様(モアレ)になることがあります。
マッハバンドというらしい。デプスバッファZ値の丸め誤差が影響しているみたいです。
もしくはfloatの浮動小数点の誤差の影響とか。
解像度をあげると少なくなるみたいです。
マッハバンドというらしい。デプスバッファZ値の丸め誤差が影響しているみたいです。
もしくはfloatの浮動小数点の誤差の影響とか。
解像度をあげると少なくなるみたいです。

影とするかしないかの境界ぎりぎりを見てしまうと現れるので、通常は、オフセット値を与えてうまく消します。
glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(1.1, 4.0); ・・・ drawObjects(); glDisable(GL_POLYGON_OFFSET_FILL);
今回オフセット値は上記のように設定しました。
おそらくフラグメント・シェーダ側でも設定できると思います。
おそらくフラグメント・シェーダ側でも設定できると思います。
shadowCoordWdiv.z += 0.00005;
どのような値をどのように設定するかは決まっておらず、調べてみても明確にはわかりませんでした。
参考
- http://fabiensanglard.net/shadowmapping/index.php
- http://marupeke296.com/DXG_No46_DepthBufShadowImp.html