阴影贴图和图像内核:ShadowMapping & Kernel - MartinRGB/GLES30_ProgrammingGuide_NDK GitHub Wiki
阴影贴图
通过使用深度纹理,以两遍算法渲染阴影。
1.第一遍渲染中,从光源角度出发渲染场景,将片段深度值记录在 纹理 FBO 中。
2.第二遍渲染中,从眼睛位置出发渲染场景,在 Frag 中通过 textureProj-采样深度纹理
执行 深度测试
,确定片段是否在阴影中
整体使用渐进过滤方式采样深度纹理,形成软件阴影。
首先解释正交投影和透视投影的区别
Ortho:
Perspective:
其次了解图片内核(kernel in image processing)的概念
Image Kernels Explained Visually
Kernel 图像合成的一些规则
3x3 PCF 采样的原理
一、光源渲染记录深度值
1.从光源建立 MVP 矩阵
// Generate an orthographic projection matrix for the shadow map rendering
esMatrixLoadIdentity ( &ortho );
// 这样投影就和物体平行了
esOrtho ( &ortho, -10, 10, -10, 10, -30, 30 );
// ### GROUND
// Generate a model view matrix to rotate/translate the ground
esMatrixLoadIdentity ( &model );
// Center the ground
esTranslate ( &model, -2.0f, -2.0f, 0.0f );
esScale ( &model, 10.0f, 10.0f, 10.0f );
esRotate ( &model, 90.f, 1.0f, 0.0f, 0.0f );
// create view matrix transformation from the eye position
esMatrixLookAt ( &view,
userData->eyePosition[0], userData->eyePosition[1], userData->eyePosition[2],
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f );
esMatrixMultiply ( &modelview, &model, &view );
// Compute the final ground MVP for the scene rendering by multiplying the
// modelview and perspective matrices together
esMatrixMultiply ( &userData->groundMvpMatrix, &modelview, &perspective );
// create view matrix transformation from the light position
esMatrixLookAt ( &view,
userData->lightPosition[0], userData->lightPosition[1], userData->lightPosition[2],
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f );
esMatrixMultiply ( &modelview, &model, &view );
// Compute the final ground MVP for the shadow map rendering by multiplying the
// modelview and ortho matrices together
esMatrixMultiply ( &userData->groundMvpLightMatrix, &modelview, &ortho );
//Cube Matrix Code Here
...
2.创建深度纹理,并连接到 FBO
当深度纹理和 sampler2Dshadow
一起使用,就得到了基于迎接的百分比渐进过滤,硬件将一次执行 4个深度比较
UserData *userData = esContext->userData;
GLenum none = GL_NONE;
GLint defaultFramebuffer = 0;
// use 1K by 1K texture for shadow map
userData->shadowMapTextureWidth = userData->shadowMapTextureHeight = 1024;
glGenTextures ( 1, &userData->shadowMapTextureId );
glBindTexture ( GL_TEXTURE_2D, userData->shadowMapTextureId );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
// Setup hardware comparison
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL );
glTexImage2D ( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24,
userData->shadowMapTextureWidth, userData->shadowMapTextureHeight,
0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL );
glBindTexture ( GL_TEXTURE_2D, 0 );
glGetIntegerv ( GL_FRAMEBUFFER_BINDING, &defaultFramebuffer );
// setup fbo
glGenFramebuffers ( 1, &userData->shadowMapBufferId );
glBindFramebuffer ( GL_FRAMEBUFFER, userData->shadowMapBufferId );
glDrawBuffers ( 1, &none );
glFramebufferTexture2D ( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, userData->shadowMapTextureId, 0 );
glActiveTexture ( GL_TEXTURE0 );
glBindTexture ( GL_TEXTURE_2D, userData->shadowMapTextureId );
if ( GL_FRAMEBUFFER_COMPLETE != glCheckFramebufferStatus ( GL_FRAMEBUFFER ) )
{
return FALSE;
}
glBindFramebuffer ( GL_FRAMEBUFFER, defaultFramebuffer );
return TRUE;
3.用 vert 和 frag渲染场景
只需要在阴影贴图纹理中记录片段深度值
const char vShadowMapShaderStr[] =
"#version 300 es \n"
"uniform mat4 u_mvpLightMatrix; \n"
"layout(location = 0) in vec4 a_position; \n"
"out vec4 v_color; \n"
"void main() \n"
"{ \n"
" gl_Position = u_mvpLightMatrix * a_position; \n"
"} \n";
const char fShadowMapShaderStr[] =
"#version 300 es \n"
"precision lowp float; \n"
"void main() \n"
"{ \n"
"} \n";
在渲染场景之前,要清除深度缓冲区,禁用颜色渲染,开启多边形偏移,加大纹理写入深度
在draw()中
// clear depth buffer
glClear( GL_DEPTH_BUFFER_BIT );
// disable color rendering, only write to depth buffer
glColorMask ( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
// reduce shadow rendering artifact
glEnable ( GL_POLYGON_OFFSET_FILL );
glPolygonOffset( 5.0f, 100.0f );
....
// 渲染场景
DrawScene ( esContext, userData->sceneMvpLoc, userData->sceneMvpLightLoc );
二、从眼睛处使用深度值渲染
1.建立眼睛 MVP 矩阵
使用 esMatrixLookAt 传递眼睛位置,创建视图转换矩阵
// create view matrix transformation from the eye position
esMatrixLookAt ( &view,
userData->eyePosition[0], userData->eyePosition[1], userData->eyePosition[2],
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f );
...
// create view matrix transformation from the light position
esMatrixLookAt ( &view,
userData->lightPosition[0], userData->lightPosition[1], userData->lightPosition[2],
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f );
2.渲染场景
Shadow Mapping in OpenGL and GLSL
在顶点着色器中,两次顶点变换:
1.使用从眼睛位置创建的 MVP 矩阵,记录在 gl_Position 中
2.使用从光源位置创建的 MVP 矩阵,记录在 v_shadowCoord 中
#version 300 es
uniform mat4 u_mvpMatrix;
uniform mat4 u_mvpLightMatrix;
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec4 a_color;
out vec4 v_color;
out vec4 v_shadowCoord;
void main()
{
v_color = a_color;
gl_Position = u_mvpMatrix * a_position;
v_shadowCoord = u_mvpLightMatrix * a_position;
// transform from [-1,1] to [0,1];
v_shadowCoord = v_shadowCoord * 0.5 + 0.5;
}
在片段着色器中,使用 v_shadowCoord 和 textureProj 调用采样阴影贴图来检测片段是否在阴影中。
#version 300 es
precision highp float;
uniform highp sampler2DShadow s_shadowMap;
uniform highp float u_time;
in vec4 v_color;
in vec4 v_shadowCoord;
layout(location = 0) out vec4 outColor;
float lookup ( float x, float y )
{
float pixelSize = 0.002; // 1/500
vec4 offset = vec4 ( x * pixelSize * v_shadowCoord.w,
y * pixelSize * v_shadowCoord.w,
-0.005 * v_shadowCoord.w, 0.0 );
return textureProj ( s_shadowMap, v_shadowCoord + offset );
}
void main()
{
// 3x3 kernel with 4 taps per sample, effectively 6x6 PCF
// 3x3 核心过滤,增加 PCF 效果,然后平均采样结果,如果为0,渲染为黑色
// 结合上面的 Kernel 只是,我们看到,利用深度检测,x、y 遍历,向 lookup 中添加点,最后平均
float sum = 0.0;
float x, y;
for ( x = -2.0; x <= 2.0; x += 2.0 )
for ( y = -2.0; y <= 2.0; y += 2.0 )
sum += lookup ( x, y );
// divide sum by 9.0
sum = sum * 0.11;
outColor = v_color * sum;
}