int InitShadowMap ( ESContext *esContext )
{
UserData *userData = esContext->userData;
GLenum none = GL_NONE;
GLint defaultFramebuffer = 0;
// 使用1024*1024的深度纹理
userData->shadowMapTextureWidth = userData->shadowMapTextureHeight = 1024;
//生成并绑定纹理对象到GL_TEXTURE_2D
glGenTextures ( 1, &userData->shadowMapTextureId );
glBindTexture ( GL_TEXTURE_2D, userData->shadowMapTextureId );
//放大过滤,使用GL_LINEAR,将从纹理坐标附近的纹理中取得一个双线性样本(4个样本的平均值)
//如果使用GL_NEAREST,则将最靠近从纹理坐标的纹理中取得单点样本
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
//缩小过滤,使用GL_LINEAR,从最靠近纹理坐标的纹理中获得一个双线性样本
//如果使用GL_NEAREST,则从最靠近纹理坐标的纹理中获得一个单点样本
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
//沿S,T方向上,GL_CLAMP_TO_EDGE限定读取纹理边缘
//GL_REPRAT重复纹理;GL_MIRRORED_REPEAT重复纹理和镜像,这部分网上很多资料可自行搜索
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
// 设置硬件比较
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL );
//注意pixels为NULL,我们将渲染到整个纹理区域,所以没有理由指定任何输入数据
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 );
//将2D纹理连接到帧缓冲区附着点
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;
}
上述的
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL );
该代码主要是为了实现深度纹理的阴影特效,为了实现这个特效,在片段着色器中需要比较片段和纹理的深度值,以此判断该片段位于阴影内还是外;在这段代码之前,我们使用了GL_LINEAR方式去过滤,原因就是双线性过滤可以使得阴影的边缘可以平滑过滤;那么这里加上硬件比较的原因就是确保将采样的深度和参考值深度比较之后,平均该结果,实现百分比渐进过滤(PCF)功能,如果不加硬件比较,则可能导致过滤发生在采样比较之前发生,这样的话就不能得到正确的结果。
GL_TEXTURE_COMPARE_MODE的默认值是GL_NONE,但是当它被设置为GL_COMPARE_REF_TO_TEXTURE时,纹理坐标(s, t, r)的r分量用来和深度纹理的值做比较,比较结果会成为阴影纹理读取的结果(可能为0或者1,如果开启了纹理过滤则是这些值的平均值)。比较函数用GL_TEXTURE_COMPARE_FUNC设置,GL_LEQUAL:深度小于或等于的时候渲染;GL_LESS:深度小于时渲染。
以上部分参考于https://www.jianshu.com/p/b54f77569855
深度纹理格式:
内部格式后面的数字代表深度
内部格式 格式 类型
GL_DEPTH_COMPONENT16 GL_DEPTH_COMPONENT GL_UNSIGNED_SHORT
GL_DEPTH_COMPONENT16 GL_DEPTH_COMPONENT GL_UNSIGNED_INT
GL_DEPTH_COMPONENT24 GL_DEPTH_COMPONENT GL_UNSIGNED_INT
GL_DEPTH_COMPONENT32F GL_DEPTH_COMPONENT GL_FLOAT
void glDrawBuffers(GLsizei n, const GLenum *bufs);
定义一个缓冲区数组,将片段着色器数据的输出写入其中。如果片段着色器将一个值写入一个或多个用户定义的输出变量,那么每个变量的值都将写入bufs中指定的缓冲区中,该缓冲区对应于分配给该用户定义的输出的位置。为分配给大于或等于n的位置的用户定义的输出使用的绘制缓冲区被隐式地设置为GL_NONE,任何写到这种输出的数据都将被丢弃。
GL_NONE :片段着色器的输出值不写入任何颜色缓冲区。
int InitMVP ( ESContext *esContext )
{
ESMatrix perspective;
ESMatrix ortho;
ESMatrix modelview;
ESMatrix model;
ESMatrix view;
float aspect;
UserData *userData = esContext->userData;
// 屏幕宽高比
aspect = (GLfloat) esContext->width / (GLfloat) esContext->height;
// 生产一个45°的投影矩阵
esMatrixLoadIdentity ( &perspective );
esPerspective ( &perspective, 45.0f, aspect, 0.1f, 100.0f );
// 生成阴影映射渲染的正投影矩阵
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.0f, 1.0f, 0.0f, 0.0f );
// 创建视图矩阵
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
// position the cube
esMatrixLoadIdentity ( &model );
esTranslate ( &model, 5.0f, -0.4f, -3.0f );
esScale ( &model, 1.0f, 2.5f, 1.0f );
esRotate ( &model, -15.0f, 0.0f, 1.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 cube MVP for scene rendering by multiplying the
// modelview and perspective matrices together
esMatrixMultiply ( &userData->cubeMvpMatrix, &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 cube MVP for shadow map rendering by multiplying the
// modelview and ortho matrices together
esMatrixMultiply ( &userData->cubeMvpLightMatrix, &modelview, &ortho );
return TRUE;
}
这里通过
esMatrixLookAt ( &view,
userData->eyePosition[0], userData->eyePosition[1], userData->eyePosition[2],
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f );
生成了视图矩阵。eyePosition指定的是“眼睛/相机”所在的位置;(0,0,0)指定的是“眼睛”看向的中心位置,这里是坐标中心;(0,1,0)指的是“眼睛”向上的方向坐标;通俗点说,第一组参数是“头的位置”,第二组数据是“头看向的物体的中心位置”,第三组数据是“头顶朝向的位置”。
上述的投影矩阵代码可以直接写成(这里是根据推导得到的公式直接写成代码,而不需要调用API函数完成)
void TestPerspective(ESMatrix *result, float fovy, float aspect, float nearZ, float farZ)
{
ESMatrix frust;
GLfloat a = 1 / tanf(fovy / 360.0f * PI);
frust.m[0][0] = a / aspect;
frust.m[0][1] = frust.m[0][2] = frust.m[0][3] = 0.0f;
frust.m[1][1] = a;
frust.m[1][0] = frust.m[1][2] = frust.m[1][3] = 0.0f;
frust.m[2][0] = 0;
frust.m[2][1] = 0;
frust.m[2][2] = -(nearZ + farZ) / (nearZ - farZ);
frust.m[2][3] = -1;
frust.m[3][2] = -2 * (nearZ * farZ) / (nearZ - farZ);
frust.m[3][0] = frust.m[3][1] = frust.m[3][3] = 0.0f;
memcpy(result, &frust, sizeof(ESMatrix));
}
三、顶点和片段着色器
从光源角度将场景渲染到深度纹理中,即渲染阴影的着色器代码较为简单,直接上代码
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";
从眼睛位置渲染场景的着色器代码
const char vSceneShaderStr[] =
"#version 300 es \n"
"uniform mat4 u_mvpMatrix; \n"
"uniform mat4 u_mvpLightMatrix; \n"
"layout(location = 0) in vec4 a_position; \n"
"layout(location = 1) in vec4 a_color; \n"
"out vec4 v_color; \n"
"out vec4 v_shadowCoord; \n"
"void main() \n"
"{ \n"
" v_color = a_color; \n"
" gl_Position = u_mvpMatrix * a_position; \n"
" v_shadowCoord = u_mvpLightMatrix * a_position; \n"
" \n"
" // transform from [-1,1] to [0,1]; \n"
" v_shadowCoord = v_shadowCoord * 0.5 + 0.5; \n"
"} \n";
const char fSceneShaderStr[] =
"#version 300 es \n"
"precision lowp float; \n"
"uniform lowp sampler2DShadow s_shadowMap; \n"
"in vec4 v_color; \n"
"in vec4 v_shadowCoord; \n"
"layout(location = 0) out vec4 outColor; \n"
" \n"
"float lookup ( float x, float y ) \n"
"{ \n"
" float pixelSize = 0.002; // 1/500 \n"
" vec4 offset = vec4 ( x * pixelSize * v_shadowCoord.w, \n"
" y * pixelSize * v_shadowCoord.w, \n"
" -0.005 * v_shadowCoord.w, 0.0 ); \n"
//textureProj:纹理坐标各分量会除以最后一个分量
" return textureProj ( s_shadowMap, v_shadowCoord + offset ); \n"
"} \n"
" \n"
"void main() \n"
"{ \n"
" // 3x3 kernel with 4 taps per sample, effectively 6x6 PCF \n"
" float sum = 0.0; \n"
" float x, y; \n"
" for ( x = -2.0; x <= 2.0; x += 2.0 ) \n"
" for ( y = -2.0; y <= 2.0; y += 2.0 ) \n"
" sum += lookup ( x, y ); \n"
" \n"
" // divide sum by 9.0 \n"
" sum = sum * 0.11; \n"
" outColor = v_color * sum; \n"
"} \n";
这里将v_shadowCoord作为纹理坐标使用,但是规格化的投影空间是[-1,1],而纹理坐标是[0,1],因此我们需要把 这个视景体转化到[0,1]中,所以利用“v_shadowCoord = v_shadowCoord * 0.5 + 0.5”将其转换到纹理坐标中;这里是在顶点着色器中完成的计算,也可以在片段着色器中计算,节省资源,在片段着色器中,使用MVP矩阵乘以下面的偏置矩阵即可;
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
v_shadowCoord(x,y,z,w):当w分量大于零时是世界坐标系下a在视点之前,w分量小于零时是在视点之后,即这里只需要根据w的值和textureproj嗲用采样阴影贴图来判断该片段是否位于阴影范围内;textureProj函数,它专门用于投影纹理访问的。它的纹理坐标的各分量会除以最后一个分量,然后才访问纹理。
for ( x = -2.0; x <= 2.0; x += 2.0 )
for ( y = -2.0; y <= 2.0; y += 2.0 )
sum += lookup ( x, y );
这段代码实现了3*3的核心过滤操作,配合之前的4*4双线性过滤,等于实际上执行的是6*6(3+4-1)过滤操作,进一步加强了PCF。
void Draw ( ESContext *esContext )
{
UserData *userData = esContext->userData;
GLint defaultFramebuffer = 0;
// Initialize matrices
InitMVP ( esContext );
glGetIntegerv ( GL_FRAMEBUFFER_BINDING, &defaultFramebuffer );
// FIRST PASS: Render the scene from light position to generate the shadow map texture
glBindFramebuffer ( GL_FRAMEBUFFER, userData->shadowMapBufferId );
// Set the viewport
glViewport ( 0, 0, userData->shadowMapTextureWidth, userData->shadowMapTextureHeight );
// 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 );
glUseProgram ( userData->shadowMapProgramObject );
DrawScene ( esContext, userData->shadowMapMvpLoc, userData->shadowMapMvpLightLoc );
glDisable( GL_POLYGON_OFFSET_FILL );
// SECOND PASS: Render the scene from eye location using the shadow map texture created in the first pass
glBindFramebuffer ( GL_FRAMEBUFFER, defaultFramebuffer );
glColorMask ( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
// Set the viewport
glViewport ( 0, 0, esContext->width, esContext->height );
// Clear the color and depth buffers
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
// Use the scene program object
glUseProgram ( userData->sceneProgramObject );
// Bind the shadow map texture
glActiveTexture ( GL_TEXTURE0 );
glBindTexture ( GL_TEXTURE_2D, userData->shadowMapTextureId );
// Set the sampler texture unit to 0
glUniform1i ( userData->shadowMapSamplerLoc, 0 );
DrawScene ( esContext, userData->sceneMvpLoc, userData->sceneMvpLightLoc );
}
glColorMask ( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );禁用帧缓冲区颜色分量的写入。
glEnable ( GL_POLYGON_OFFSET_FILL );
glPolygonOffset( 5.0f, 100.0f );
对于一个三维多边形(即并不是所有顶点都在xy平面内)来说,这种显示填充多边形边的方法可能在边之间的生
成缝隙。这种称为缝线(stitching)的效果由扫描线填充算法和边的画线算法的计算差别造成。在对一个三维
多边形进行填充时,深度值(离xy平面的距离)按每一(x,y)位置计算。但是在多边形一条边上的这个深度值通
常与在同一(x, y)力位置用画线算法计算所得的深度值不完全相同。因此,在进行可见性测试时,内部填充色
可用来代替边的颜色以显示沿多边形边界的点。
消除三维多边形显示边的缝隙的一个办法是移动由填充子程序计算的深度值,使它们与多边形的边深度值不重
叠。
在OpenGL中,如果想绘制一个多边形同时绘制其边界,可是先使用多边形模式GL_FILL绘制物体,然后使用多边形模式GL_LINE和不同的颜色再次绘制这个多边形。但是由于直线和多边形的光栅化方式不同,导致位于同一位置的多边形和直线的深度值并不相同,进而导致直线有时在多边形的里面,有时在多边形的外面,这种现象就是"Stiching"。
启用Polygon Offset有三个可选参数(GL_POLYGON_OFFSET_POINT, GL_POLYGON_OFFSET_LINE 和GL_POLYGON_OFFSET_FILL),分别对应3种光栅化模式(GL_POINT, GL_LINE 和GL_FILL)。
offset = (m * factor) + (r * units)
m是多边形的深度的斜率(在光栅化阶段计算得出)中的最大值。这句话难以理解,你只需知道,一个多边形越是与近裁剪面(near clipping plan)平行,m就越接近0。
r是能产生在窗口坐标系的深度值中可分辨的差异的最小值,r是由具体实现OpenGL的平台指定的一个常量。
一个大于0的offset 会把模型推到离你(摄像机)更远一点的位置,相应地,一个小于0的offset 会把模型拉近。
以上参考于https://www.cnblogs.com/bitzhuwei/archive/2015/06/12/4571539.html 和 https://www.2cto.com/kf/201609/551264.html
void DrawScene ( ESContext *esContext,
GLint mvpLoc,
GLint mvpLightLoc )
{
UserData *userData = esContext->userData;
// Draw the ground
// Load the vertex position
glBindBuffer ( GL_ARRAY_BUFFER, userData->groundPositionVBO );
glVertexAttribPointer ( POSITION_LOC, 3, GL_FLOAT,
GL_FALSE, 3 * sizeof(GLfloat), (const void*)NULL );
glEnableVertexAttribArray ( POSITION_LOC );
// Bind the index buffer
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, userData->groundIndicesIBO );
// Load the MVP matrix for the ground model
glUniformMatrix4fv ( mvpLoc, 1, GL_FALSE, (GLfloat*) &userData->groundMvpMatrix.m[0][0] );
glUniformMatrix4fv ( mvpLightLoc, 1, GL_FALSE, (GLfloat*) &userData->groundMvpLightMatrix.m[0][0] );
// Set the ground color to light gray
glVertexAttrib4f ( COLOR_LOC, 0.9f, 0.9f, 0.9f, 1.0f );
glDrawElements ( GL_TRIANGLES, userData->groundNumIndices, GL_UNSIGNED_INT, (const void*)NULL );
// Draw the cube
// Load the vertex position
glBindBuffer( GL_ARRAY_BUFFER, userData->cubePositionVBO );
glVertexAttribPointer ( POSITION_LOC, 3, GL_FLOAT,
GL_FALSE, 3 * sizeof(GLfloat), (const void*)NULL );
glEnableVertexAttribArray ( POSITION_LOC );
// Bind the index buffer
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, userData->cubeIndicesIBO );
// Load the MVP matrix for the cube model
glUniformMatrix4fv ( mvpLoc, 1, GL_FALSE, (GLfloat*) &userData->cubeMvpMatrix.m[0][0] );
glUniformMatrix4fv ( mvpLightLoc, 1, GL_FALSE, (GLfloat*) &userData->cubeMvpLightMatrix.m[0][0] );
// Set the cube color to red
glVertexAttrib4f ( COLOR_LOC, 1.0f, 0.0f, 0.0f, 1.0f );
glDrawElements ( GL_TRIANGLES, userData->cubeNumIndices, GL_UNSIGNED_INT, (const void*)NULL );
}
这段代码只是简单的读取数据,设置数据,并完成绘图操作。