前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文搞懂 OpenGL 多重采样抗锯齿,再也不怕面试被问到了

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

作者头像
字节流动
发布2024-04-11 16:50:51
2460
发布2024-04-11 16:50:51
举报
文章被收录于专栏:字节流动字节流动

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

锯齿是怎样产生的?

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

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

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

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

多重采样抗锯齿原理

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

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

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

多重采样抗锯齿实现

通过 EGL 设置多重采样

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

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

代码语言:javascript
复制
 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 查询设备最大支持的采样数。

代码语言:javascript
复制
//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 类;

代码语言:javascript
复制
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 的构造函数内设置;

代码语言:javascript
复制
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 场景的话也需要创建对应的多重采样深度和模版缓冲区。

代码语言:javascript
复制
    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 到另外一个普通的帧缓冲区或者再进行一次普通的离屏渲染。

代码语言:javascript
复制
    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);

抗锯齿结果对比:

开启多重采样

未开启多重采样

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 字节流动 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 锯齿是怎样产生的?
  • 多重采样抗锯齿原理
  • 多重采样抗锯齿实现
    • 通过 EGL 设置多重采样
    • 离屏渲染抗锯齿
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档