ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

OPENGL ES 3.0 shadow示例代码详解(阴影纹理、MVP矩阵等)

2019-08-13 16:05:23  阅读:432  来源: 互联网

标签:MVP userData OPENGL 0.0 示例 TEXTURE 纹理 frust GL


一、创建深度纹理并连接到帧缓冲区

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 :片段着色器的输出值不写入任何颜色缓冲区。

 

二、通过正交投影、模型和视图矩阵生成MVP矩阵

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 );
}

这段代码只是简单的读取数据,设置数据,并完成绘图操作。

标签:MVP,userData,OPENGL,0.0,示例,TEXTURE,纹理,frust,GL
来源: https://blog.csdn.net/qq_26749045/article/details/99410500

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有