一文搞懂 OpenGL 多重采样抗锯齿,再也不怕面试被问到了

多重采样抗锯齿(MSAA,Multisample Anti-Aliasing)是一种用于减少图形渲染中锯齿效应的技术。

锯齿是怎样产生的?

锯齿效应是由于在屏幕上渲染的图形对象边缘处像素颜色变化突然而导致的,它使得图形看起来不够平滑,影响了视觉质量。

一文搞懂 OpenGL 多重采样抗锯齿,再也不怕面试被问到了

如图示,我们渲染一个三角形,每个像素中心包含一个采样点,它被用来决定一个像素是否被三角形所覆盖(即是否在渲染区域内)。

红色的采样点如果被三角形覆盖,那么就会为这个被覆盖像素生成一个片段。即使三角形覆盖了部分屏幕像素,但是采样点没被覆盖,就不会生成片段。

一文搞懂 OpenGL 多重采样抗锯齿,再也不怕面试被问到了

由于屏幕像素总量的限制,有些边上的像素能被渲染出来,而有些则不会。结果就是我们渲染出的基本图形的非光滑边缘产生了上图的锯齿边。

多重采样抗锯齿原理

多重采样抗锯齿通过在渲染过程中对图像进行额外的抽样来解决这个问题。

一文搞懂 OpenGL 多重采样抗锯齿,再也不怕面试被问到了

多重采样对每个像素使用多个样本点来决定三角形的覆盖范围,这样三角形边缘附近每个片段的颜色将会由多个采样点共同决定,不再按照中心的样本一刀切。

一文搞懂 OpenGL 多重采样抗锯齿,再也不怕面试被问到了

使用多重采样之后,三角形的硬边就被比实际颜色浅一些的颜色所包围,因此观察者从远处看上去就比较平滑了。

多重采样抗锯齿实现

通过 EGL 设置多重采样

我们知道 EGL 创建 OpenGL 的渲染上下文,会调用一系列的 egl 函数,例如 eglGetDisplay() ,eglInitialize() , eglChooseConfig() 等。

其中 eglChooseConfig 时会设置一个 attrib_list 用于确定表面的配置信息,我们可以在这个 attrib_list 设置多重采样的信息。

 const EGLint attribsMSAA[] = {
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
            EGL_BLUE_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_RED_SIZE, 8,
            EGL_ALPHA_SIZE, 8,// if you need the alpha channel
            EGL_DEPTH_SIZE, 16,// if you need the depth buffer
            EGL_STENCIL_SIZE,8,
            EGL_SAMPLE_BUFFERS, 1,//打开多采样抗锯齿
            EGL_SAMPLES, 4, //设置每个片段的采样点数
            EGL_NONE
};

然后就是正常的流程,创建渲染表面和 EGLContext。

EGL_SAMPLES, 用来指定每个片段的样本数,样本数越多抗锯齿效果越好,一般推荐设置 2、4、8 。

但是采样数不能随便设置,我们可以通过 GL_MAX_SAMPLES 查询设备最大支持的采样数。

//Java
int[] maxSamples = new int[1];
GLES32.glGetIntegerv(GLES32.GL_MAX_SAMPLES, maxSamples, 0);

//C++
int maxSamples = 0;
glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);

Android 平台可以直接通过 GLSurfaceView 中的内置函数 setEGLConfigChooser() 来设置。

先创建一个自定义的 GLSurfaceView.EGLConfigChooser 类;

class MyConfigChooser implements GLSurfaceView.EGLConfigChooser {
    @Override
    public EGLConfig chooseConfig(EGL10 egl,
                                  javax.microedition.khronos.egl.EGLDisplay display) {

        int attribs[] = {
                EGL10.EGL_LEVEL, 0,
                EGL10.EGL_RENDERABLE_TYPE, 4,  // EGL_OPENGL_ES2_BIT
                EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RGB_BUFFER,
                EGL10.EGL_RED_SIZE, 8,
                EGL10.EGL_GREEN_SIZE, 8,
                EGL10.EGL_BLUE_SIZE, 8,
                EGL10.EGL_DEPTH_SIZE, 16,
                EGL10.EGL_SAMPLE_BUFFERS, 1,
                EGL10.EGL_SAMPLES, 4,  // 在这里修改MSAA的倍数,4就是4xMSAA,再往上开程序可能会崩
                EGL10.EGL_NONE
        };
        EGLConfig[] configs = new EGLConfig[1];
        int[] configCounts = new int[1];
        egl.eglChooseConfig(display, attribs, configs, 1, configCounts);

        if (configCounts[0] == 0) {
            // Failed! Error handling.
            return null;
        } else {
            return configs[0];
        }
    }
}

然后在 GLSurfaceView 的构造函数内设置;

public class MyGLSurfaceView extends GLSurfaceView {

    private final MyGLRenderer mRenderer;

    public MyGLSurfaceView(Context context) {
        super(context);

        setEGLContextClientVersion(3);

        setEGLConfigChooser(new MyConfigChooser()); // 注意在 setRenderer 之前调用

        mRenderer = new MyGLRenderer();
        setRenderer(mRenderer);
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }
}

离屏渲染抗锯齿

离屏渲染抗锯齿是 GLES 3.1 才支持的,流程比较简单,就是创建一个多重采样纹理或者多重采样缓冲区,作为帧缓冲区的颜色附着 GL_COLOR_ATTACHMENT0 ,涉及 3D 场景的话也需要创建对应的多重采样深度和模版缓冲区。

   glGenFramebuffers(1, &m_FboId);
    glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);

//  创建多重采样缓冲区
//        glGenRenderbuffers(1, &m_RboId);
//        glBindRenderbuffer(GL_RENDERBUFFER, m_RboId);
//        glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, screenW, screenH);
//        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_RboId);

//  创建多重采样纹理
    glGenTextures(1, &m_MsTextureId);
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_MsTextureId);
    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, screenW,
                              screenH, GL_TRUE);
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
    glFramebufferTexture2D(
            GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, m_MsTextureId, 0
    );
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
        LOGCATE("MultiSampleAntiAliasingSample:: Framebuffer is not complete!");
    }

值得注意的是,多重采样的渲染结果无法直接上屏渲染,需要 Blit 到另外一个普通的帧缓冲区或者再进行一次普通的离屏渲染。

    glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);
    glViewport(0, 0, screenW, screenH);
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    UpdateMVPMatrix(m_MVPMatrix, m_AngleX, m_AngleY, (float)screenW / screenH);
    glUseProgram (m_ProgramObj);
    glBindVertexArray(m_VaoId);
    GLUtils::setMat4(m_ProgramObj, "u_MVPMatrix", m_MVPMatrix);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, m_TextureId);
    GLUtils::setInt(m_ProgramObj, "s_Texture", 1);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);

    //多重采样缓冲区无法直接上屏(渲染),先搞到另外一个缓冲区,然后再上屏
    glBindFramebuffer(GL_FRAMEBUFFER, m_FboId2);
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, m_FboId);
    glReadBuffer(GL_COLOR_ATTACHMENT0);
    glBlitFramebuffer(0, 0, screenW, screenH,
                      0, 0, screenW, screenH,
                      GL_COLOR_BUFFER_BIT, GL_LINEAR);

抗锯齿结果对比:

一文搞懂 OpenGL 多重采样抗锯齿,再也不怕面试被问到了
开启多重采样
一文搞懂 OpenGL 多重采样抗锯齿,再也不怕面试被问到了
未开启多重采样

完整实现代码下方扫码添加微信获取

参考文章:https://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/11%20Anti%20Aliasing/

进技术交流群,扫码添加我的微信:Byte-Flow

字节流动

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论