【实践LearnOpenGL项目】高级篇

练习一、实例化渲染

如果要绘制多个类似物体时,可能最先想到是循环绘制,但是用实例化glDraw*Instanced,性能要好特别多!我们先直接看效果图,如下所示

这里加载了3W个陨石和1个行星,都是用的外部模型obj文件加载渲染的,实现原理可参考【实践LearnOpenGL项目】提升篇。加载好这俩模型数据后,使用随机数定义3W个移动缩放旋转的四维矩阵,让3W个共用一套模型数据的陨石看起来自然一些。

我们重点看下实例化流程,部分代码如下

    // 遍历陨石面去设置变换矩阵
    for (unsigned int i = 0; i < rock.m_meshes.size(); i++)
    {
        unsigned int VAO = rock.m_meshes[i].VAO;
        glBindVertexArray(VAO);
        glEnableVertexAttribArray(3);
        glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);
        glEnableVertexAttribArray(4);
        glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4)));
        glEnableVertexAttribArray(5);
        glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(2 * sizeof(glm::vec4)));
        glEnableVertexAttribArray(6);
        glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(3 * sizeof(glm::vec4)));
        glVertexAttribDivisor(3, 1);
        glVertexAttribDivisor(4, 1);
        glVertexAttribDivisor(5, 1);
        glVertexAttribDivisor(6, 1);
        glBindVertexArray(0);// 解绑
    }

由于OpenGL顶点属性通道最大支持的单次传递是vec4类型数据,这里将四维矩阵拆分成了四个vec4。别忘了在顶点着色器加上这里传入的矩阵,这里对应的“layout (location = 3)” 。由于要使用glDrawElementsInstanced,而不是封装接口内的glDrawElements,因此需要自己实现以下部分。

        asteroidShader.use();
        asteroidShader.setInt("texture_diffuse1", 0);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, rock.m_textures_loaded[0].id);
        for (unsigned int i = 0; i < rock.m_meshes.size(); i++)
        {
            glBindVertexArray(rock.m_meshes[i].VAO);
            glDrawElementsInstanced(GL_TRIANGLES, static_cast<unsigned int>(rock.m_meshes[i].indices.size()), GL_UNSIGNED_INT, 0, amount);
            glBindVertexArray(0);
        }

练习二、离屏抗锯齿

其实之前在文章【实践LearnOpenGL项目】光照基础篇中,就已经出现了视觉锯齿,也就是边缘不光滑的情况,如下所示。

为了解决这个问题,我们需要用到离屏抗锯齿技术。原理大概可以理解为,先创建一个多重采样帧缓冲区,用来多次采样,相当于打草稿。然后创建后处理帧缓冲区,用来存储解析后的抗锯齿图像,相当于将草稿整合并拓印出来,最后是以纹理的形式进行屏幕输出,部分代码如下

    // 多重采样帧缓冲区配置
    unsigned int framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    // 创建支持4倍采样的颜色附件纹理
    unsigned int textureColorBufferMultiSampled;
    glGenTextures(1, &textureColorBufferMultiSampled);
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, textureColorBufferMultiSampled);
    glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGB, SCR_WIDTH, SCR_HEIGHT, GL_TRUE);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, textureColorBufferMultiSampled, 0);
    // 创建多重采样的深度/模板渲染缓冲对象
    unsigned int rbo;
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

以上的4倍采样相当于画4次,然后取平均。然后我们再看后处理帧缓冲

    // 后处理帧缓冲区配置
    unsigned int intermediateFBO;
    glGenFramebuffers(1, &intermediateFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, intermediateFBO);
    // 创建普通2D纹理作为颜色附件,用来存储解析后的抗锯齿图像
    unsigned int screenTexture;
    glGenTextures(1, &screenTexture);
    glBindTexture(GL_TEXTURE_2D, screenTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0);

这里最终结果存储在了2D纹理screenTexture中,配置好这俩帧缓冲后,接下来我们看渲染的流程

        // 1. 初始化多重采样
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glEnable(GL_DEPTH_TEST);
    
        // 2. 多重采样缓冲区解析到普通帧缓冲
        glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO);
        glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);

        // 3. 将其作为纹理进行屏幕输出
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glDisable(GL_DEPTH_TEST);

加载纹理screenTexture后可得到如下效果

 练习三、统一缓冲对象(UBO)

当视图、投影矩阵、光源参数等多个数据需要多个着色器中共享时,我们可以使用UBO,可以提升渲染性能。原项目只共享了投影和视图矩阵,部分代码如下

    // 获取着色器中“Matrices”的索引
    unsigned int uniformBlockIndexRed = glGetUniformBlockIndex(shaderRed.ID, "Matrices");
    unsigned int uniformBlockIndexGreen = glGetUniformBlockIndex(shaderGreen.ID, "Matrices");
    unsigned int uniformBlockIndexBlue = glGetUniformBlockIndex(shaderBlue.ID, "Matrices");
    unsigned int uniformBlockIndexYellow = glGetUniformBlockIndex(shaderYellow.ID, "Matrices");
    // 将所有着色器的uniformBlock绑定到同一点(0)
    glUniformBlockBinding(shaderRed.ID, uniformBlockIndexRed, 0);
    glUniformBlockBinding(shaderGreen.ID, uniformBlockIndexGreen, 0);
    glUniformBlockBinding(shaderBlue.ID, uniformBlockIndexBlue, 0);
    glUniformBlockBinding(shaderBlue.ID, uniformBlockIndexYellow, 0);
    // 创建UBO对象,分配2个mat4大小的内存(对应后面的投影和视图矩阵)
    unsigned int uboMatrices;
    glGenBuffers(1, &uboMatrices);
    glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
    glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    // 第二个参数index,对应绑定点0,第四和第五个参数对应起始偏移量和数据大小
    glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4));

    // 填充投影矩阵
    glm::mat4 projection = glm::perspective(45.0f, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
    //第二个参数0代表着填充到第一个mat4位置
    glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(projection));
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

如果共享数据是光源类参数,可定义结构体LightData,将sizeof(glm::mat4)换成sizeof(LightData)就可。我们接着看其他核心代码

     {
        // 由于视图矩阵会随鼠标变化,因此放在渲染循环内
        glm::mat4 view = camera.GetViewMatrix();
        glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
        //填充到第二个mat4位置
        glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
        glBindBuffer(GL_UNIFORM_BUFFER, 0);
    }

    //顶点着色器代码
    layout (std140) uniform Matrices
    {
        mat4 projection;
        mat4 view;
    };

其中的“std140”表示内存对齐规则,最后修改了下原项目的纹理,效果如下

 练习四、几何着色器

我们之前常用的有顶点和片段着色器,为了引出几何着色器,下面来对比一下。

下面我们来看一个具体修改三角形图元的例子,几何着色器代码如下

layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in VS_OUT {
    vec2 texCoords;
    vec3 vNormal;
    vec3 vModelPos;
} gs_in[];
in vec3 vNormal[];
in vec3 vModelPos[];

第一行的triangles表示输入图元是三角形,第二行表示triangle_strip表示图元类型是三角形带,max_vertices表示单次最多生成的顶点数量。因此这里输入的数据都是容量为3的数组

vec4 explode(vec4 position, vec3 normal)
{
    float magnitude = 2;
    vec3 direction = normal * ((sin(time) + 1.0) / 2.0) * magnitude; 
    return position + vec4(direction, 0.0);
}
vec3 GetNormal()
{
    vec3 a = vec3(gl_in[0].gl_Position) - vec3(gl_in[1].gl_Position);
    vec3 b = vec3(gl_in[2].gl_Position) - vec3(gl_in[1].gl_Position);
    return normalize(cross(a, b));
}
void main() {    
    vec3 normal = GetNormal();
    Normal = gs_in[0].vNormal;
    modelPos = gs_in[0].vModelPos;
    gl_Position = explode(gl_in[0].gl_Position, normal);
    TexCoords = gs_in[0].texCoords;
    EmitVertex();
    ...
    EndPrimitive();
    ...

上面通过计算三角形两向量的叉积,然后再将每个顶点朝叉积方向偏移,从而使三角形图元朝外法线方向移动,模拟类似爆炸的效果。后面在main函数中发射顶点、法向量等数据,这里需要发射三次,最后EndPrimitive函数表示图元发射的结束。我在原项目的基础上加上了镜面反射,最终效果如下~

最后,水平有限,欢迎指正和交流~觉得有用的话点点赞或收藏吧~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值