当前位置:网站首页>【OpenGL】笔记二十九、抗锯齿(MSAA)
【OpenGL】笔记二十九、抗锯齿(MSAA)
2022-07-04 22:03:00 【ycrsw】
1. 流程
经过之前的教程,我们目前渲染出来的画面已经有了足够的表现力,但是还是有一些缺陷,比如当我们的渲染画面分辨率跟不上屏幕分辨率时,在我们渲染的图形边缘一些比较严重的锯齿效果就会显现:
自然,这种效果就是因为画面采样像素太少,从而在一条直线上出现了明显的“断带”:

那么,理所当然的,解铃还须系铃人,我们可以增大采样数量,而目前应用比较广泛的抗锯齿方法也是多重采样MSAA,这种方法的原理很简单,就是将每个采样的像素分为四个像素进行采样计算,最后将结果进行平均计算:

2. 实现
2.1 增大帧缓冲
在【OpenGL】笔记二十三、帧缓冲中,我通过减少自定义帧缓冲的采样个数,最后还原到正常视口中完成了一种马赛克风的效果:
那么同样的,根据上面介绍的MSAA原理,我也可以通过增加自定义帧缓冲的采样个数,最后还原视口大小(其中有自动求平均的功能)来完成抗锯齿的效果(以4X4采样,离屏渲染为例):
首先在每次渲染前增大视口:
while (!glfwWindowShouldClose(window))
{
glViewport(0, 0, 800 * 4, 600 * 4);//增大视口,绑定自定义帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, screen->framebuffer);
...
//渲染场景
...
screen->Draw(screenShader);//渲染帧缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}
然后在初始化自定义帧缓冲时按比例增大纹理和渲染对象附件
void setup() {
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)));
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glGenTextures(1, &textureColorbuffer);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800 * 4, 600 * 4, 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, textureColorbuffer, 0);
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800 * 4, 600 * 4);//增大渲染缓冲对象
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
最后渲染我们的帧缓冲前还原视口大小:
void Draw(Shader& shader) {
glViewport(0, 0, 800, 600);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
shader.use();
shader.setInt("screenTexture", 0);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(quadVAO);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer); // use the color attachment texture as the texture of the quad plane
glDrawArrays(GL_TRIANGLES, 0, 6);
}
前后效果对比:

可见实际的效果不错,但是我自己的感觉来看渲染的帧数下降了许多,那么有没有更高效的方法呢?当然
2.2 OpenGL自带MSAA
OpenGL有自带的MSAA功能,它的使用也是十分的方便:
首先创建窗口之前调用glfwWindowHint来创建多重采样缓冲:
glfwWindowHint(GLFW_SAMPLES, 16);
其中第二个参数就是我们的采样个数,每个像素采样16个其实就是4X4的采样
接下来我们就可以像之前那样在渲染前开启多重采样的功能:
glEnable(GL_MULTISAMPLE);

可以看到,效果甚至比我们上一个效果还好,甚至帧数也更高了,但是这种方法有一种弊端,那就是不能在离屏渲染时使用,意思是离屏时这样就是无效的,那么离屏渲染的前提下我们该怎么更好的完成抗锯齿的功能呢?
2.3 离屏MSAA
其实离屏MSAA并没有新增多少操作,和之前的【OpenGL】笔记二十三、帧缓冲中所说的差不多,只是我们在绑定自定义缓冲的纹理附件和渲染缓冲对象时,改变了部分调用的函数和纹理类型:
glGenTextures(1, &textureColorbuffer);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, textureColorbuffer);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGB, 800, 600, GL_TRUE);//多重采样纹理附件
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, textureColorbuffer, 0);
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, 800, 600);//多重采样渲染缓冲对象
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
但是多重采样缓冲有一点特别,我们不能直接将它们的缓冲图像用于其他运算,比如在着色器中对它们进行采样。
一个多重采样的图像包含比普通图像更多的信息,我们所要做的是缩小或者还原(Resolve)图像。多重采样帧缓冲的还原通常是通过glBlitFramebuffer来完成,它能够将一个帧缓冲中的某个区域复制到另一个帧缓冲中,并且将多重采样缓冲还原。
void Draw(Shader& shader) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, 800, 600, 0, 0, 800, 600, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// now bind back to default framebuffer and draw a quad plane with the attached framebuffer color texture
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST); // disable depth test so screen-space quad isn't discarded due to depth test.
// clear all relevant buffers
glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // set clear color to white (not really necessary actually, since we won't be able to see behind the quad anyways)
glClear(GL_COLOR_BUFFER_BIT);
shader.use();
shader.setInt("screenTexture", 0);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(quadVAO);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer); // use the color attachment texture as the texture of the quad plane
glDrawArrays(GL_TRIANGLES, 0, 6);
}

效果不错,但是有个问题,如果我们想要使用多重采样帧缓冲的纹理输出来做像是后期处理这样的事情呢?我们不能直接在片段着色器中使用多重采样的纹理。但我们能做的是将多重采样缓冲位块传送到一个没有使用多重采样纹理附件的FBO中。然后用这个普通的颜色附件来做后期处理,从而达到我们的目的。然而,这也意味着我们需要生成一个新的FBO,作为中介帧缓冲对象,将多重采样缓冲还原为一个能在着色器中使用的普通2D纹理。
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// create a color attachment texture
glGenTextures(1, &screenTexture);
glBindTexture(GL_TEXTURE_2D, screenTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 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);
然后我们渲染时将原缓冲复制到这个新缓冲中,并且改绑纹理为这个新纹理:
void Draw(Shader& shader) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glBlitFramebuffer(0, 0, 800, 600, 0, 0, 800, 600, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// now bind back to default framebuffer and draw a quad plane with the attached framebuffer color texture
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST); // disable depth test so screen-space quad isn't discarded due to depth test.
// clear all relevant buffers
glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // set clear color to white (not really necessary actually, since we won't be able to see behind the quad anyways)
glClear(GL_COLOR_BUFFER_BIT);
shader.use();
shader.setInt("screenTexture", 0);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(quadVAO);
glBindTexture(GL_TEXTURE_2D, screenTexture); // use the color attachment texture as the texture of the quad plane
glDrawArrays(GL_TRIANGLES, 0, 6);
}
这样我们就可以对多重采样后的纹理在着色器中进行后期处理了,比如下面这个灰度图的处理:

2.4 自定义抗锯齿
将一个多重采样的纹理图像不进行还原直接传入着色器也是可行的。GLSL提供了这样的选项,让我们能够对纹理图像的每个子样本进行采样,所以我们可以创建我们自己的抗锯齿算法。在大型的图形应用中通常都会这么做。
要想获取每个子样本的颜色值,你需要将纹理uniform采样器设置为sampler2DMS,而不是平常使用的sampler2D:
uniform sampler2DMS screenTextureMS;
使用texelFetch函数就能够获取每个子样本的颜色值了:
vec4 colorSample = texelFetch(screenTextureMS, TexCoords, 3); // 第4个子样本
边栏推荐
- Logo Camp d'entraînement section 3 techniques créatives initiales
- About stack area, heap area, global area, text constant area and program code area
- PostgreSQLSQL高级技巧透视表
- i. Mx6ull driver development | 24 - platform based driver model lights LED
- 都说软件测试很简单有手就行,但为何仍有这么多劝退的?
- How to manage 15million employees easily?
- 机器人相关课程考核材料归档实施细则2022版本
- In Linux, I call odspcmd to query the database information. How to output silently is to only output values. Don't do this
- Microservices -- Opening
- PostgreSQL JOIN实践及原理
猜你喜欢

LOGO特训营 第三节 首字母创意手法

LOGO特训营 第四节 字体设计的重要性

赋能数字经济 福昕软件出席金砖国家可持续发展高层论坛

Introduction and application of bigfilter global transaction anti duplication component

【Acwing】第58场周赛 题解

Machine learning notes mutual information

常用的开源无代码测试工具

Concurrent network modular reading notes transfer

Energy momentum: how to achieve carbon neutralization in the power industry?

DevEco Device Tool 3.0 Release带来5大能力升级,让智能设备开发更高效
随机推荐
30余家机构联合发起数字藏品行业倡议,未来会如何前进?
leetcode 72. Edit distance edit distance (medium)
傳智教育|如何轉行互聯網高薪崗比特之一的軟件測試?(附軟件測試學習路線圖)
PostgreSQL JOIN实践及原理
idea中pom.xml依赖无法导入
Logo Camp d'entraînement section 3 techniques créatives initiales
POM in idea XML dependency cannot be imported
Alibaba launched a new brand "Lingyang" and is committed to becoming a "digital leader"
# 2156. Find the substring of the given hash value - post order traversal
Logo special training camp section 1 Identification logo and logo design ideas
并发优化总结
MySQL存储数据加密
Flask 上下文详解
Postgresqlql advanced skills pivot table
How can the advertising system of large factories be upgraded without the presence of large models
Play with grpc - go deep into concepts and principles
[acwing] solution of the 58th weekly match
Introducing QA into the software development lifecycle is the best practice that engineers should follow
HUAWEI nova 10系列发布 华为应用市场筑牢应用安全防火墙
Challenges faced by virtual human industry