OpenGL.Shader:11-シャドウの実現-指向性ライトシャドウ
私は長い間研究記事を書いていないと感じており、主に仕事と生活で忙しいです。国民の日の後、2019年にはあまり時間が残っていません。あなた自身とあなたの愛する人のために戦ってください。
1.シャドウマッピングの理論
影はブロックされた光の結果です。他のオブジェクトの障害物のために光源の光がオブジェクトの表面に到達できない場合、オブジェクトは影の中にあります。影はシーンをよりリアルに見せ、観察者がオブジェクト間の空間的な位置関係を取得できるようにします。
今回は、コンテンツで前のセクションのDepth Textureを使用する必要があり、ポータルはここにあります。デプステクスチャは、デプス値が主なストレージコンテンツであるテクスチャオブジェクトです。その原理は、光源角度が観測ターゲット(プロジェクションマトリックス*ビューマトリックス)光源マトリックス空間を指すことです。デプステストは、この光源空間で開始され、すべての観測オブジェクトが保存されます。閉塞状況。閉塞状況に応じて、シャドウマッピングを実行します。
シャドウマッピングの背後にある考え方は非常に単純です。レンダリングするパースペクティブとしてライトの位置を使用し、見ることができるすべてのものが照らされ、見えないものはシャドウになければなりません。床があり、光源とその間に大きな箱があるとします。光源が光の方向を見ているので、箱は見えますが、床の一部は見えません。この部分は影になっているはずです。(ここにあるすべての青い線は、光源が見ることができるフラグメントを表しています。黒い線は、遮られたフラグメントを表しています)
退屈な理論的研究の部分は終わりました。上記の理論によれば、光源+オブジェクト=シャドウマッピングが存在する限り、シャドウの実現が一般的なアルゴリズムであることがわかります。以下では、シャドウの実現をさらに把握するための例として、シェーダーコードを取り上げます。
#version 320 es
in vec3 position;
vec3通常;
vec2uvで;out VS_OUT { vec3 FragPosWorldSpace; vec3通常; vec2 TexCoords; vec4 FragPosLightSpace; } vs_out;
均一なmat4プロジェクション。
均一なmat4ビュー。
均一なmat4モデル。
均一なmat4lightSpaceMatrix;void main()
{ gl_Position = Projection * view * model * vec4(position、1.0f); vs_out.TexCoords = uv; //テクスチャのuv座標は、補間のためにフラグメントシェーダーに渡されます vs_out.Normal = transpose(inverse(mat3 (モデル)))*通常; vs_out.FragPosWorldSpace = vec3(model * vec4(position、1.0)); vs_out.FragPosLightSpace = lightSpaceMatrix * vec4(vs_out.FragPosWorldSpace、1.0); }
頂点シェーダーの内容は比較的理解しやすく、構造VS_OUTを使用して、関連するすべての出力を統合します。
通常のベクトルは、通常のベクトルマトリックス(モデルマトリックスの逆マトリックスの転置)で変換する必要があります
。FragPosWorldSpace=ワールド座標系の絶対位置座標、使用
するフラグメントシェーダーを保存します; lightSpaceMatrixは上記の光源マトリックススペース、光源マトリックススペース*絶対位置座標は光源スペースの下の頂点位置です;
次に、重要なポイントであるフラグメントシェーダーを見てください。
#version 320es
高精度mediumpfloat;
均一vec3_lightColor;
均一なsampler2D_texture;
均一なsampler2D_shadowMap;
均一vec3_lightPos;
均一vec3_viewPos;VS_OUT { vec3 FragPosWorldSpace; vec3通常; vec2 TexCoords; vec4 FragPosLightSpace; } fs_in;
vec4fragColorを出力します。
float ShadowCalculation(vec4 fragPosLightSpace)
{ [...] } void main(){ vec3 color = texture(_texture、fs_in.TexCoords).rgb; vec3 normal = normalize(fs_in.Normal); vec3 lightColor = _lightColor; //周囲アンビエント vec3アンビエント= 0.5 *カラー; //拡散反射ディ フューズvec3lightDir = normalize(_lightPos-fs_in.FragPosWorldSpace); float diff = max(dot(lightDir、normal)、0.0); vec3ディフューズ= diff * lightColor; //シャドウディストーション //フロートバイアス= max(0.01 *(1.0-dot(normal、lightDir))、0.0005); //シャドウ フロートを計算しますshadow = ShadowCalculation(fs_in.FragPosLightSpace);
vec3ライティング=(アンビエント+(1.0-シャドウ)*ディフューズ)*カラー;
fragColor = vec4(lighting、1.0f);
}
フラグメントシェーダーは、Blinn-Phong照明モデルを使用してシーンをレンダリングします。次に、シャドウ値を計算します。これは、フラグメントがシャドウ内にある場合は1.0、シャドウ外にある場合は0.0です。次に、拡散色にこのシャドウ要素が乗算されます。影が完全に黒くなることはないので(散乱のため)、乗算から周囲成分を削除します。
フラグメントがシャドウ内にあるかどうかを確認するには、最初に、ライトスペース内のフラグメントの位置を、カッティングスペースの標準化されたデバイス座標に変換します(言い換えると、3Dスペースは画面上の2D座標に変換され、深度テクスチャは比較)。クリッピングスペースの頂点位置を頂点シェーダーのgl_Positionに出力すると、OpenGLは自動的に遠近法分割を実行します。これにより、クリッピングスペース座標-wからw、-1から1の範囲が変換され、x、y、zが必要になります。要素は、ベクトルのw要素で除算されます。クロップスペースのFragPosLightSpaceはgl_Positionを介してピクセルシェーダーに渡されないため、パースペクティブ分割を自分で行う必要があります。
//パースペクティブ分割を実行します
vec3projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
上記のprojCoordsのxyzコンポーネントはすべて[-1,1]であり(以下では、これは遠方平面などのポイントにのみ当てはまることがわかります)、深度マップの深度と比較するには、zコンポーネントを[0,1]に変換する必要があります。 ;深度マップからサンプリングされた座標であるためには、xyコンポーネントも[0,1]に変換する必要があります。したがって、projCoordsベクトル全体を[0,1]の範囲に変換する必要があります。
projCoords = projCoords * 0.5 + 0.5;
これらの投影座標を使用して、深度テクスチャから0から1までの結果をサンプリングでき、最初のレンダリング段階からのprojCoords座標は、変換されたNDC座標に直接対応します。ライト位置の視野の下で最も近い深さを取得します。
floatclosestDepth = texture(shadowMap、projCoords.xy).r;
フラグメントの現在の深さを取得するには、投影ベクトルのz座標を取得します。これは、光の観点からフラグメントの深さに等しくなります。
float currentDepth = projCoords.z;
実際の比較は、currentDepthがclosetDepthより大きいかどうかを確認するだけです。大きい場合、フラグメントは隠された背面ビューにあります。
float shadow = currentDepth> closestDepth?1.0:0.0;
完全なshadowCalculation関数は次のようになります。
float ShadowCalculation(vec4 fragPosLightSpace)
{
// 执行透视除法
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
// 变换到[0,1]的范围
projCoords = projCoords * 0.5 + 0.5;
// 取得最近点的深度(使用[0,1]范围下的fragPosLight当坐标)
float closestDepth = texture(shadowMap, projCoords.xy).r;
// 取得当前片元在光源视角下的深度
float currentDepth = projCoords.z;
// 检查当前片元是否在阴影中
float shadow = currentDepth > closestDepth ? 1.0 : 0.0;
return shadow;
}
第二に、影の実現
研究の重要な理論的部分は終わりました。次のステップは実際の操作です。固定点光源を作り、元の緑の草や立方体を照らし、影の効果を出す予定です。光源の位置は、小さな白い立方体によってシミュレートされます。
void ShadowFBORender::surfaceCreated(ANativeWindow *window)
{
if (mEglCore == NULL) {
mEglCore = new EglCore(NULL, FLAG_TRY_GLES3);
}
mWindowSurface = new WindowSurface(mEglCore, window, true);
mWindowSurface->makeCurrent();
char res_name[250]={0};
sprintf(res_name, "%s%s", res_path, "land.jpg");
GLuint land_texture_id = TextureHelper::createTextureFromImage(res_name);
sprintf(res_name, "%s%s", res_path, "test.jpg");
GLuint texture_cube_id = TextureHelper::createTextureFromImage(res_name);
// 带阴影效果的正方体
cubeShadow.init(CELL::float3(1,1,1));
cubeShadow.setSurfaceTexture(texture_cube_id);
// 带阴影效果的草地板
landShadow.init(10, -1);
landShadow.setSurfaceTexture(land_texture_id);
// 实际的光源位置
mLightPosition = CELL::real3(5, 5, 2);
// 模拟光源位置的小正方体
lightPositionCube.init(CELL::real3(0.15f,0.15f,0.15f), 0);
lightPositionCube.mModelMatrix.translate(mLightPosition);
}
次に、描画とレンダリングのプロセスを実装します。
void ShadowFBORender::renderOnDraw(double elpasedInMilliSec)
{
mWindowSurface->makeCurrent();
matrix4 cProj(mCamera3D.getProject());
matrix4 cView(mCamera3D.getView());
mLightProjectionMatrix = CELL::perspective(45.0f, (float)mViewWidth/(float)mViewHeight, 0.1f, 30.0f);
mLightViewMatrix = CELL::lookAt(mLightPosition, CELL::real3(0,0,0), CELL::real3(0,1.0,0));
// Note.绘制深度纹理
renderDepthFBO();
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
glViewport(0,0, mViewWidth, mViewHeight);
// 绘制模拟光源位置的小正方体
lightPositionCube.render(mCamera3D);
// 绘制带阴影效果的地板
landShadow.setShadowMap(depthFBO.getDepthTexId());
landShadow.render(cProj,cView, mLightPosition, mLightProjectionMatrix, mLightViewMatrix);
// 绘制带阴影效果的正方体
cubeShadow.setShadowMap(depthFBO.getDepthTexId());
cubeShadow.render(cProj,cView, mLightPosition, mLightProjectionMatrix, mLightViewMatrix);
mWindowSurface->swapBuffers();
}
void ShadowFBORender::renderDepthFBO()
{
depthFBO.begin();
{
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
//glEnable(GL_CULL_FACE);
//glCullFace(GL_FRONT);
landShadow.render(mLightProjectionMatrix,mLightViewMatrix,
mLightPosition,
mLightProjectionMatrix,mLightViewMatrix);
cubeShadow.render(mLightProjectionMatrix,mLightViewMatrix,
mLightPosition,
mLightProjectionMatrix,mLightViewMatrix);
//glCullFace(GL_BACK);
//glDisable(GL_CULL_FACE);
}
depthFBO.end();
}
前のものとの違いは、LandShadow / CubeShadowオブジェクトの描画メソッドです。以前は、Camera3Dオブジェクトが渡されていました。レンダリング深度テクスチャメソッドrenderDepthFBOでは、入力パラメーターも異なります。これは主に、深度テクスチャに必要なライトスペースの深度値です。実際のレンダリングでは、これはカメラのビュースペースの効果です。LandShadowを例として取り上げて、特定のコードを確認します。
class LandShadow {
public:
struct V3N3T2 {
float x, y, z; //位置坐标
float nx, ny, nz; //法向量
float u,v; //纹理坐标
};
public:
V3N3T2 _data[6];
CELL::matrix4 _modelMatrix;
GLuint _texId;
GLuint _ShadowMapId;
IlluminateWithShadow sprogram;
void setShadowMap(GLuint texId) {
_ShadowMapId = texId;
}
void setSurfaceTexture(GLuint texId) {
_texId = texId;
}
void init(const float size, const float y_pos)
{
float gSizeX = 10;
float gSizeZ = 10;
V3N3T2 verts[] =
{
{-gSizeX, y_pos, -gSizeZ, 0,1,0, 0.0f, 0.0f}, // left far
{ gSizeX, y_pos, -gSizeZ, 0,1,0, size, 0.0f}, // right far
{ gSizeX, y_pos, gSizeZ, 0,1,0, size, size}, // right near
{-gSizeX, y_pos, -gSizeZ, 0,1,0, 0.0f, 0.0f}, // left far
{ gSizeX, y_pos, gSizeZ, 0,1,0, size, size}, // right near
{-gSizeX, y_pos, gSizeZ, 0,1,0, 0.0f, size} // left near
};
memcpy(_data, verts, sizeof(verts));
_modelMatrix.identify();
sprogram.initialize();
}
void render(matrix4 currentProjectionMatrix, matrix4 currentViewMatrix,
real3& lightPos,
matrix4 lightProjectionMatrix, matrix4 lightViewMatrix)
{
sprogram.begin();
// 加载材质纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _texId);
glUniform1i(sprogram._texture, 0);
// 加载阴影深度测试的纹理
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, _ShadowMapId);
glUniform1i(sprogram._shadowMap, 1);
// 用于对象顶点坐标的空间转换
glUniformMatrix4fv(sprogram._projection, 1, GL_FALSE, currentProjectionMatrix.data());
glUniformMatrix4fv(sprogram._view, 1, GL_FALSE, currentViewMatrix.data());
glUniformMatrix4fv(sprogram._model, 1, GL_FALSE, _modelMatrix.data());
glUniform3f(sprogram._lightColor, 1.0f, 1.0f, 1.0f);
glUniform3f(sprogram._lightPos, lightPos.x, lightPos.y, lightPos.z);
// 光源空间矩阵
matrix4 lightSpaceMatrix = lightProjectionMatrix * lightViewMatrix;
glUniformMatrix4fv(sprogram._lightSpaceMatrix, 1, GL_FALSE, lightSpaceMatrix.data());
// 绘制
glVertexAttribPointer(static_cast<GLuint>(sprogram._position), 3, GL_FLOAT, GL_FALSE,
sizeof(LandShadow::V3N3T2), &_data[0].x);
glVertexAttribPointer(static_cast<GLuint>(sprogram._normal), 3, GL_FLOAT, GL_FALSE,
sizeof(LandShadow::V3N3T2), &_data[0].nx);
glVertexAttribPointer(static_cast<GLuint>(sprogram._uv), 2, GL_FLOAT, GL_FALSE,
sizeof(LandShadow::V3N3T2), &_data[0].u);
glDrawArrays(GL_TRIANGLES, 0, 6);
sprogram.end();
}
};
最初のパラメーターと2番目のパラメーターは、オブジェクトの特定の頂点位置をレンダリングするために使用されます。3番目のパラメーターは、光源の位置です。
4番目と5番目のパラメーターは、光源空間マトリックスのオブジェクト頂点の位置を計算するために使用され、頂点が配置されているフラグメントのシャドウ判定を容易にします。
上記のコードでは、大きな違いがない場合、実行効果はおそらく次のようになります。
これはなぜですか?この現象は、非常に深刻なシャドウ歪みと冗長なシャドウオクルージョンテストの存在によるものです。どのように対処しますか?次回は分解を聞いてください!