opengl抗锯齿之多重采样抗锯齿MSAA


在这里插入图片描述

OpenGL 多重采样抗锯齿(MSAA)简介

抗锯齿技术的目标是减少或消除图形渲染中常见的锯齿效应(Alias)。锯齿通常出现在渲染斜线或曲线时,因为显示设备的像素网格限制了细节的精度。多重采样抗锯齿(MSAA,Multi-Sample Anti-Aliasing)是一种通过增加每个像素的采样数来减少锯齿的技术。

在多重采样中,OpenGL 并不是在单一的像素位置进行采样,而是对每个像素的多个子位置进行采样,计算它们的平均值。这种方法通常可以大大减少锯齿效果,并且在性能开销上比其他更复杂的抗锯齿方法(如超级采样抗锯齿,SSAA)要高效得多。

为什么需要抗锯齿?

渲染时,图形的边缘经常显示出“阶梯状”的锯齿效果。这种现象特别明显在显示斜线或曲线时,像素排列的离散性导致边缘无法平滑过渡。因此,抗锯齿方法通过对像素进行更多的采样,尝试重建更平滑的图像边缘,消除这些不自然的阶梯状效果。

多重采样抗锯齿的工作原理

与传统的单次采样不同,MSAA 通过对每个像素进行多个采样来减少锯齿现象。具体而言,MSAA 会在每个像素内进行多个子像素采样,这些采样点的位置不会完全重合,而是分布在像素的不同位置。MSAA 通过对这些子样本的颜色值进行平均,得出最终的像素颜色,从而减少锯齿现象。

image
对于每个像素来说,越少的子采样点被三角形所覆盖,那么它受到三角形的影响就越小。三角形的不平滑边缘被稍浅的颜色所包围后,从远处观察时就会显得更加平滑了。

MSAA 的工作流程

MSAA 的工作流程可以分为几个关键步骤:

1. 创建多重采样缓冲区

在 OpenGL 中,首先要创建一个支持多重采样的帧缓冲对象(Framebuffer Object,FBO),并为每个像素配置多个样本。这要求显卡在进行像素处理时能够处理多个子像素级别的颜色、深度和模板数据。

2. 渲染场景到多重采样缓冲区

渲染过程会在这个多重采样的缓冲区中执行,每个像素会有多个采样点,OpenGL 将计算这些采样点的颜色和深度。

3. 将多重采样结果合并到单一帧缓冲

渲染完成后,OpenGL 会将每个像素的多个采样结果进行合并,通常是通过计算这些采样点的平均值来实现。最终的合成结果是一个视觉上更加平滑、没有明显锯齿的图像。

离屏渲染示例代码

在 OpenGL 中使用多重采样时,通常会先进行离屏渲染(即将场景渲染到一个不可见的帧缓冲区),然后再将渲染结果输出到屏幕。以下是一个离屏渲染的伪代码示例,展示了如何在 OpenGL 中设置和使用多重采样。

伪代码(离屏渲染模式)

// 1. 初始化 OpenGL 和创建窗口
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_SAMPLES, 4);  // 启用 4 倍多重采样
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL MSAA", NULL, NULL);
glfwMakeContextCurrent(window);
glewExperimental = GL_TRUE;
glewInit();

// 2. 创建多重采样帧缓冲对象(FBO)和渲染缓冲
GLuint msaaFBO;
glGenFramebuffers(1, &msaaFBO);
glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO);

// 创建一个多重采样的渲染缓冲对象用于颜色附件
GLuint msaaColorBuffer;
glGenRenderbuffers(1, &msaaColorBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, msaaColorBuffer);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGB8, 800, 600);  // 4 个样本
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaColorBuffer);

// 创建多重采样的渲染缓冲对象用于深度和模板附件
GLuint msaaDepthBuffer;
glGenRenderbuffers(1, &msaaDepthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, msaaDepthBuffer);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, 800, 600);  // 4 个样本
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msaaDepthBuffer);

// 检查帧缓冲是否完整
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    // 处理错误
}

// 3. 创建用于解析的中间帧缓冲对象(普通帧缓冲)
GLuint intermediateFBO;
glGenFramebuffers(1, &intermediateFBO);
glBindFramebuffer(GL_FRAMEBUFFER, intermediateFBO);

// 创建一个纹理附件用于存储解析后的图像
GLuint screenTexture;
glGenTextures(1, &screenTexture);
glBindTexture(GL_TEXTURE_2D, screenTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 800, 600, 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);

// 检查帧缓冲是否完整
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    // 处理错误
}

// 4. 渲染场景到多重采样帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // 清除颜色和深度缓冲

// 渲染场景:例如,绘制模型、应用光照等
renderScene();

// 5. 解析多重采样帧缓冲的内容到中间帧缓冲
glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO);
glBlitFramebuffer(0, 0, 800, 600, 0, 0, 800, 600,
                  GL_COLOR_BUFFER_BIT, GL_NEAREST);  // 使用邻近过滤

// 6. 将解析后的结果渲染到屏幕
glBindFramebuffer(GL_FRAMEBUFFER, 0);  // 绑定默认帧缓冲
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 渲染屏幕四边形,使用 screenTexture 作为纹理
renderQuad(screenTexture);

// 7. 交换缓冲区,展示最终结果
glfwSwapBuffers(window);
glfwPollEvents();

// 8. 清理资源并终止
glDeleteFramebuffers(1, &msaaFBO);
glDeleteFramebuffers(1, &intermediateFBO);
glDeleteRenderbuffers(1, &msaaColorBuffer);
glDeleteRenderbuffers(1, &msaaDepthBuffer);
glDeleteTextures(1, &screenTexture);

glfwTerminate();

MSAA 的优缺点

优点

  • 抗锯齿效果明显:通过对每个像素进行多个采样,MSAA 能显著减少图像中的锯齿现象,特别是在渲染斜线或曲线时。
  • 性能开销较低:相比于超级采样(SSAA),MSAA 提供了一个较为高效的解决方案,适用于大部分需要抗锯齿的应用。

缺点

  • 性能损耗:即使 MSAA 比 SSAA 高效,但仍然比传统的单次采样渲染有一定的性能开销,尤其是在高采样率时(例如 8x 或 16x 多重采样)。
  • 透明物体表现差:MSAA 在处理透明物体时效果较差,因为透明像素需要额外的处理来避免锯齿。
  • 不能消除所有锯齿问题:对于着色器内的细节(如程序生成的高频纹理),MSAA 无法抗锯齿,因为这些细节并不受几何覆盖影响。

使用 MSAA 的场景

MSAA 常用于以下几种场景:

  • 实时渲染:例如在游戏或虚拟现实应用中,需要平滑的边缘效果而不牺牲太多性能。
  • 3D 建模和视觉效果:在需要渲染逼真图形时,MSAA 是一个重要的抗锯齿技术,特别是在光照和阴影效果复杂的场景中。

完整源码示例

下面的代码示例只需要opengl的库和opencv,采用离屏渲染的方式展示多重采样抗锯齿的效果


#include <iostream>
#include <fstream>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>

#include "opencv2/opencv.hpp"

#if !(defined(__arm__) || defined(__aarch64__))

namespace
{
    const char *vertex_shader_source = R"(
        #version 310 es
        precision mediump float;
        layout(location = 0) in vec3 a_position;
        layout(location = 1) in vec2 a_texCoord;
        
        out vec2 v_texCoord;
        void main()
        {
            gl_Position = vec4(a_position, 1.0);
            v_texCoord = a_texCoord;
        }
    )";

    const char *fragment_shader_source = R"(
        #version 310 es
        precision mediump float;
        in vec2 v_texCoord;
        out vec4 fragColor;
        uniform sampler2D s_texture;
        void main()
        {
            fragColor = texture(s_texture, v_texCoord);
            // fragColor = vec4(fragColor.r, fragColor.g, fragColor.b, 1.0);
            // fragColor = vec4(1.0, 0.20, 0.30, 1.0);
        }
    )";

    class GraphicsContext
    {
    public:
        static GraphicsContext &get_mutable_instance()
        {
            static GraphicsContext instance;
            return instance;
        }

        bool setup(int width, int height, bool off_screen)
        {
            if (off_screen)
            {
                m_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
                if (m_display == EGL_NO_DISPLAY)
                {
                    std::cout << "Failed to get EGL display" << std::endl;
                    return false;
                }

                EGLint major, minor;
                if (!eglInitialize(m_display, &major, &minor))
                {
                    std::cout << "Failed to initialize EGL" << std::endl;
                    EGLint error = eglGetError();
                    std::cout << "EGL Error: " << std::hex << error << std::endl;
                    return false;
                }
                std::cout << "EGL version: " << major << "." << minor << std::endl;

                // 绑定 OpenGL API
                if (!eglBindAPI(EGL_OPENGL_API))
                {
                    std::cout << "Failed to bind OpenGL API" << std::endl;
                    EGLint error = eglGetError();
                    std::cout << "EGL Error: " << std::hex << error << std::endl;
                    return false;
                }

                const EGLint config_attribs[] = {
                    EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
                    EGL_BLUE_SIZE, 8,
                    EGL_GREEN_SIZE, 8,
                    EGL_RED_SIZE, 8,
                    EGL_ALPHA_SIZE, 8,
                    EGL_DEPTH_SIZE, 24,
                    EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, 
                    EGL_NONE};

                EGLint num_configs;
                if (!eglChooseConfig(m_display, config_attribs, &m_config, 1, &num_configs) || num_configs == 0)
                {
                    std::cout << "Failed to choose EGL config" << std::endl;
                    EGLint error = eglGetError();
                    std::cout << "EGL Error: " << std::hex << error << std::endl;
                    return false;
                }

                const EGLint pbuffer_attribs[] = {
                    EGL_WIDTH, width,
                    EGL_HEIGHT, height,
                    EGL_NONE};

                m_surface = eglCreatePbufferSurface(m_display, m_config, pbuffer_attribs);
                if (m_surface == EGL_NO_SURFACE)
                {
                    std::cout << "Failed to create EGL surface" << std::endl;
                    EGLint error = eglGetError();
                    std::cout << "EGL Error: " << std::hex << error << std::endl;
                    return false;
                }

                const EGLint context_attribs[] = {
                    EGL_NONE};

                m_context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, context_attribs);
                if (m_context == EGL_NO_CONTEXT)
                {
                    std::cout << "Failed to create EGL context" << std::endl;
                    EGLint error = eglGetError();
                    std::cout << "EGL Error: " << std::hex << error << std::endl;
                    return false;
                }

                if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context))
                {
                    std::cout << "Failed to make EGL context current" << std::endl;
                    EGLint error = eglGetError();
                    std::cout << "EGL Error: " << std::hex << error << std::endl;
                    return false;
                }

                std::cout << "EGL context successfully created." << std::endl;
                return true;
            }
            else
            {
                // 非 off_screen 模式的处理
                // ...
                return true;
            }
        }

        void swapBuffer()
        {
            eglSwapBuffers(m_display, m_surface);
        }

    private:
        GraphicsContext() = default;
        ~GraphicsContext() = default;

        EGLDisplay m_display = EGL_NO_DISPLAY;
        EGLConfig m_config;
        EGLSurface m_surface;
        EGLContext m_context;
    };

    class Shader
    {
    public:
        unsigned int ID;
        
        Shader() {};

        void initWithCode(const char *vsCode, const char *fsCode)
        {
            unsigned int vertex, fragment;
            // vertex shader
            vertex = glCreateShader(GL_VERTEX_SHADER);
            glShaderSource(vertex, 1, (const char **)&vsCode, NULL);
            glCompileShader(vertex);
            // checkCompileErrors(vertex, "VERTEX");
            // fragment Shader
            fragment = glCreateShader(GL_FRAGMENT_SHADER);
            glShaderSource(fragment, 1, (const char **)&fsCode, NULL);
            glCompileShader(fragment);
            // checkCompileErrors(fragment, "FRAGMENT");
            // shader Program
            ID = glCreateProgram();
            glAttachShader(ID, vertex);
            glAttachShader(ID, fragment);
            glLinkProgram(ID);
            // checkCompileErrors(ID, "PROGRAM");
            // delete the shaders as they're linked into our program now and no longer necessery
            glDeleteShader(vertex);
            glDeleteShader(fragment);
        }

        void use()
        {
            glUseProgram(ID);
        }

        void setBool(const std::string &name, bool value) const
        {
            glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
        }

        void setInt(const std::string &name, int value) const
        {
            glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
        }
    };
}

namespace
{
    const char *triangle_v_shader = R"(
    #version 300 es
    precision mediump float;
    layout (location = 0) in vec3 aPos;
    void main()
    {
        gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    }
    )";

    const char *triangle_f_shader = R"(
    #version 300 es
    precision mediump float;
    out vec4 FragColor;
    void main()
    {
        FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
    }
    )";

    bool IsFileExist(const std::string &file_path)
    {
        std::ifstream file(file_path);
        return file.good();
    }

}

int DrawTriangle()
{
    bool off_screen = true;

    auto &graphicsContext = GraphicsContext::get_mutable_instance();
    if (!graphicsContext.setup(1920, 1080, off_screen))
    {
        std::cout << "Failed to setup GraphicsContext! \n";
        return -1;
    }

    // create a shader
    auto shader = std::make_shared<Shader>();
    shader->initWithCode(triangle_v_shader, triangle_f_shader);
    shader->use();

    // create a triangle
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // left
        0.5f, -0.5f, 0.0f,  // right
        0.0f, 0.5f, 0.0f    // top
    };

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);

    // render loop

    cv::Mat image(1080, 1920, CV_8UC4);

    for (int frame = 0; frame < 2; frame++)
    {
        // input
        image.setTo(cv::Scalar(0, 0, 0, 255));
        // render
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // draw our first triangle
        shader->use();
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        // swap buffers
        graphicsContext.swapBuffer();

        GLint viewPort[4] = {0};
        glGetIntegerv(GL_VIEWPORT, viewPort);
        glReadPixels(viewPort[0], viewPort[1], viewPort[2], viewPort[3], GL_RGBA, GL_UNSIGNED_BYTE, image.data);

        std::string filename = "triangle_" + std::to_string(frame) + ".png";
        std::cout << "Saving image to " << filename << std::endl;

        cv::imwrite(filename, image);
        cv::imshow("triangle", image);
        cv::waitKey(0);

        std::cout << "Image saved." << std::endl;
    }

    return 0;
}

void TestAntialialiasing()
{
    auto &graphicsContext = GraphicsContext::get_mutable_instance();
    if (!graphicsContext.setup(1920, 1080, true))
    {
        std::cout << "Failed to setup GraphicsContext! \n";
        return;
    }

    auto shader = std::make_shared<Shader>();
    shader->initWithCode(triangle_v_shader, triangle_f_shader);
    shader->use();

    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // left
        0.5f, -0.5f, 0.0f,  // right
        0.0f, 0.5f, 0.0f    // top
    };

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);

    // // 屏幕四边形的顶点数据
    float quadVertices[] = {
        // 位置          // 纹理坐标
        -1.0f, 1.0f, 0.0f, 1.0f,
        -1.0f, -1.0f, 0.0f, 0.0f,
        1.0f, -1.0f, 1.0f, 0.0f,

        -1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, -1.0f, 1.0f, 0.0f,
        1.0f, 1.0f, 1.0f, 1.0f};

    // 创建 VAO 和 VBO
    unsigned int quadVAO, quadVBO;
    glGenVertexArrays(1, &quadVAO);
    glGenBuffers(1, &quadVBO);
    glBindVertexArray(quadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);

    // 设置顶点属性
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)(2 * sizeof(float)));
    glBindVertexArray(0);

    auto texture_shader = std::make_shared<Shader>();
    texture_shader->initWithCode(vertex_shader_source, fragment_shader_source);
    texture_shader->use();

    // create a multisampled framebuffer
    GLuint ms_fbo, ms_rbo, ms_texture;
    glGenFramebuffers(1, &ms_fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, ms_fbo);

    // create a multisampled color attachment texture
    glGenRenderbuffers(1, &ms_rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, ms_rbo);
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA, 1920, 1080);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, ms_rbo);

    // create a multisampled depth attachment texture
    GLuint ms_depth_stencil_rbo;
    glGenRenderbuffers(1, &ms_depth_stencil_rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, ms_depth_stencil_rbo);
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, 1920, 1080);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, ms_depth_stencil_rbo);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    {
        std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // create a normal framebuffer
    GLuint fbo, rbo, texture;
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // create a color attachment texture
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1920, 1080, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    {
        std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    cv::Mat image(1080, 1920, CV_8UC4);

    for (int frame = 0; frame < 2; frame++)
    {

        // render to ms_fbo
        glBindFramebuffer(GL_FRAMEBUFFER, ms_fbo);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // draw our first triangle
        shader->use();
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        // resolve multisampled buffer to normal buffer
        glBindFramebuffer(GL_READ_FRAMEBUFFER, ms_fbo);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
        glBlitFramebuffer(0, 0, 1920, 1080, 0, 0, 1920, 1080, GL_COLOR_BUFFER_BIT, GL_NEAREST);

        // draw to screen
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        texture_shader->use();
        glBindVertexArray(quadVAO);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);
        texture_shader->setInt("s_texture", 0);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        graphicsContext.swapBuffer();

        GLint viewPort[4] = {0};
        glGetIntegerv(GL_VIEWPORT, viewPort);
        glReadPixels(viewPort[0], viewPort[1], viewPort[2], viewPort[3], GL_RGBA, GL_UNSIGNED_BYTE, image.data);

        std::string filename = "antialiasing_" + std::to_string(frame) + ".png";
        std::cout << "Saving image to " << filename << std::endl;

        cv::imwrite(filename, image);
        cv::imshow("antialiasing", image);
        if (cv::waitKey(0) == 27)
        {
            break;
        }
    }
}

#endif

int main()
{
#if !(defined(__arm__) || defined(__aarch64__))

    DrawTriangle();
    // TestFBO();
    TestAntialialiasing();
    std::cout << "Done! \n";
#endif

    return 0;
}

在可执行程序旁边保存图片,整体看如下:
在这里插入图片描述
放大对比如下,左侧为多重采样的效果:
在这里插入图片描述

参考连接

  1. https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/11%20Anti%20Aliasing/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值