回顾games101中的AA(抗锯齿)

前言

善于进行课后总结,可以更加巩固自己的知识和具体细节

锯齿(走样)产生的原因

本质上,在光栅化阶段中,用有限离散的数据想表示连续的(类似三角形的某一边),就可能存在采样点不够的问题,也就引申出了锯齿(走样 Aliasing)的这个概念,在信号处理以及相关领域中,走样(混叠)在对不同的信号进行采样时,导致得出的信号相同的现象。它也可以指信号从采样点重新信号导致的跟原始信号不匹配的瑕疵

具体到实时渲染领域中,走样有以下三种:[3]

  • 几何体走样(几何物体的边缘有锯齿),几何走样由于对几何边缘采样不足导致。
  • 着色走样,由于对着色器中着色公式(渲染方程)采样不足导致。比较明显的现象就是高光闪烁。

上面一张图显示了由于对使用了高频法线贴图的高频高光BRDF采样不足时产生的着色走样。下面这张图显示了使用4倍超采样产生的效果。

  • 时间走样,主要是对高速运动的物体采样不足导致。比如游戏中播放的动画发生跳变等。

SSAA(超采样反走样)

产生锯齿的原因本质上是因为采样点个数不够,少了,那我给你多一倍的采样点不就可以弥补了吗,比如一张800x600分辨率的图,我先长宽都加倍采样,变成1600x1200,那我在把它缩放回800x600不就可以了吗

过程:

对每个像素取n个子采样点,然后针对每个子像素点进行着色计算。最后根据每个子像素的值来合成最终的图像

MSAA(多重采样反走样)

在前面提到的SSAA中,每个子采样点都要进行单独的着色,这样在片断(像素)着色器比较复杂的情况下还是很费的。那么能不能只计算每个像素的颜色,而对于那些子采样点只计算一个覆盖信息(coverage)和遮挡信息(occlusion)来把像素的颜色信息写到每个子采样点里面呢?最终根据子采样点里面的颜色值来通过某个重建过滤器来降采样生成目标图像。这就是MSAA的原理。注意这里有一个很重要的点,就是每个子像素都有自己的颜色、深度模板信息,并且每一个子采样点都是需要经过深度和模板测试才能决定最终是不是把像素的颜色得到到这个子采样点所在的位置,而不是简单的作一个覆盖测试就写入颜色

代码实现

没有SSAA和MSAA之前,可以看到边缘锯齿化特别明显

SSAA

#define ssaa_sample 2
float sampling_period = 1.0f / ssaa_sample; // 2x2SSAA
for (int x = xmin; x <= xmax; x++) {
for (int y = ymin; y <= ymax; y++) {
int in_num = 0;
Eigen::Vector3f color_sum;
for (int i = 0; i < ssaa_sample; ++i) {
for (int j = 0; j < ssaa_sample; ++j) {
// 中心点
float new_x = x + (i + 0.5) * sampling_period;
float new_y = y + (j + 0.5) * sampling_period; if (insideTriangle(new_x, new_y, t.v)) {
auto [alpha, beta, gamma] = computeBarycentric2D(new_x, new_y, t.v);
float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float z_interpolated =
alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
z_interpolated *= w_reciprocal; // 左下角的点
int depth_buf_x, depth_buf_y;
depth_buf_x = x * ssaa_sample + i;
depth_buf_y = y * ssaa_sample + j; if (z_interpolated < depth_buf[get_index(depth_buf_x, depth_buf_y)]) {
depth_buf[get_index(depth_buf_x, depth_buf_y)] = z_interpolated;
Vector3f temp_point = { depth_buf_x * 1.0f,depth_buf_y * 1.0f,0.0f };
Vector3f color = t.getColor();
set_temp_pixel(temp_point, color); }
}
}
}
}
} // Down Sample
for (int x = xmin; x <= xmax; x++)
{
for (int y = ymin; y <= ymax; y++)
{
Eigen::Vector3f color{ 0,0,0 };
Eigen::Vector3f point{ x * 1.0f, y * 1.0f, 0 }; for (int i = 0; i < ssaa_sample; ++i)
{
for (int j = 0; j < ssaa_sample; ++j)
{
int depth_buf_x, depth_buf_y;
depth_buf_x = x * ssaa_sample + i;
depth_buf_y = y * ssaa_sample + j;
color += temp_frame_buf[get_index(depth_buf_x, depth_buf_y)];
}
}
color /= (ssaa_sample * ssaa_sample);
set_pixel(point, color);
}
}

通过放大对比,其实可以看到SSAA的效果比MSAA好很多,解决了出现黑边的问题,整体也是接近于完美的效果

MSAA

以下例子为8x8的MSAA,MSAA其实就是求出一个面积的覆盖率,然后通过覆盖率乘以rgb,然后更新深度缓冲区和颜色缓冲区

    auto v = t.toVector4();

    int xmin = MIN(MIN(floor(v[0].x()), floor(v[0].x())), floor(v[2].x()));
int xmax = MAX(MAX(floor(v[0].x()), floor(v[1].x())), floor(v[2].x())); int ymin = MIN(MIN(floor(v[0].y()), floor(v[1].y())), floor(v[2].y()));
int ymax = MAX(MAX(floor(v[0].y()), floor(v[1].y())), floor(v[2].y())); int sample_num = 8;
std::vector<float> offset; for (int i = 0; i < sample_num; ++i)
{
offset.push_back((0.5 + i) * 1.0 / static_cast<float>(sample_num));
} int index; // MSAA
for (int x = xmin; x <= xmax; x++) {
for (int y = ymin; y <= ymax; y++) {
int in_num = 0;
for (int i = 0; i < sample_num; ++i) {
for (int j = 0; j < sample_num; ++j) {
if (insideTriangle(x + offset[i], y + offset[j], t.v)) {
++in_num;
}
}
}
if (in_num > 0 &&insideTriangle(x + 0.5, y + 0.5, t.v)) {
auto [alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float z_interpolated =
alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
z_interpolated *= w_reciprocal; index = get_index(x, y); if (index < frame_buf.size() && depth_buf[index] > z_interpolated) {
depth_buf[index] = z_interpolated;
Eigen::Vector3f point;
point << static_cast<float>(x), static_cast<float>(y), z_interpolated;
set_pixel(point, t.getColor() * in_num / (sample_num * sample_num));
} }
}
}

其中会遇到几个问题,一是三角形的边可能会呈现黑色,这是因为当覆盖率过低的时候,乘上rgb基于接近于0,也就是黑色,然后蓝色三角形在绿色三角形的后面,计算深度的时候大于绿色三角形的深度,所以无法写入,就会呈现黑边的情况。

不过总体上效果也还算看得过去

SSAA和MSAA的优缺点

通过实践中可以看出,SSAA需要额外用到扩大的缓冲空间,以及在计算所有像素点后,还会经过downSample的过程,可以说从时间还是空间上消耗都比MSAA要大,但是他的效果也是显著的好

MSAA性能上优于SSAA,不需要扩展额外的深度缓存空间,但是效果不是特别好,可能需要后续的其他改进方法吧

参考博文

antialiasing 抗锯齿

深入剖析MSAA

回顾games101中的SSAA和MSAA的更多相关文章

  1. 回顾Games101图形学(一)几何变换中一些公式的推导

    回顾Games101 chatper1 - 6 前言 本文只写回顾后重新加深认识的知识 透视除法的意义 经过MVP矩阵之后,将模型空间下某点的坐标,转换成了裁剪空间下的坐标,此时因为裁剪空间的范围是x ...

  2. 25.redux回顾,redux中的action函数异步

    回顾:Redux: 类似于 Vuex 概念:store/reducer/action action:动作 {type,.....} 一定要有type 其他属性不做限制 reducer:通过计算产生st ...

  3. 简单回顾C++中的字符串

    C++中有两种字符串形式,一种是C语言字符数组,一般可以使用 char*指针来操作它:另一种是C++中基于标准库的string类型,这算是更高层次的抽象数据类型. 主要讨论一下string类型,既然是 ...

  4. 回顾MySQL中的事务特征

    一.事务定义Transaction事务:一个最小的不可再分的工作单元:通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)一个完整的业务需要批量的DML(insert. ...

  5. 回顾js中的cookie/localstorage

    1.首先简单总结下cookie cookie:可以做会话跟踪 特点:      1.大小限制(不能超过4k)      2.每个域下cookie不能超过50个      3.有效期(和设定时间有关), ...

  6. 专攻知识小点——回顾JavaWeb中的servlet(二)

    续前篇... ServletConfig对象 Servlet的配置对象,ServletConfig对象作用域只能在一个Servlet类中使用.每个Servlet类都维护一个ServletConfig对 ...

  7. 专攻知识小点——回顾JavaWeb中的servlet(三)

    HttpSession基本概述 ** ** 1.HttpSession:是服务器端的技术.和Cookie一样也是服务器和客户端的会话.获得该对象是通过HTTPServletRequest的方法getS ...

  8. 讲解Canvas中的一些重要方法

    Canvas所提供的各种方法根据功能来看大致可以分为几类: 第一是以drawXXX为主的绘制方法: 第二是以clipXXX为主的裁剪方法: 第三是以scale.skew.translate和rotat ...

  9. 思考 Swift 中的 MirrorType 协议

    Swift中的反射非常有限,仅允许以只读方式访问元数据的类型子集.或许 Swift 因有严格的类型检验而不需要反射.编译时已知各种类型,便不再需要进行进一步检查或区分.然后大量的 Cocoa API ...

随机推荐

  1. Redux-基本概念

    相关文档 1)         英文文档: https://redux.js.org/ 2)         中文文档: http://www.redux.org.cn/ 3)         Git ...

  2. 在 Golang 中实现一个简单的Http中间件

    本文主要针对Golang的内置库 net/http 做了简单的扩展,通过添加中间件的形式实现了管道(Pipeline)模式,这样的好处是各模块之间是低耦合的,符合单一职责原则,可以很灵活的通过中间件的 ...

  3. session及cookie详解(七)

    前言 文章说明 在每整理一个技术点的时候,都要清楚,为什么去记录它.是为了工作上项目的需要?还是为了搭建技术基石,为学习更高深的技术做铺垫? 让每一篇文章都不是泛泛而谈,复制粘贴,都有它对自己技术提升 ...

  4. zookeeper与eureka比较

    一个分布式系统不可能同时满足C(一致性).A(可用性)和P(分区容错性) zookeeper确保cp 当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接d ...

  5. 基于小熊派Hi3861鸿蒙开发的IoT物联网学习【二】

    HarmonyOS内核开发-信号量开发案例学习记录   一.LiteOS里面的任务管理介绍: 任务状态通常分为以下四种: 就绪(Ready):该任务在就绪列表中,只等待CPU. 运行(Running) ...

  6. Maven安装配置及与 IDEA2021集成

    ============================================== 搭建Java Web开发环境 Windows10 64bit+IDEA2021.2+JDK11+Tomca ...

  7. Python基础之PyQt5关闭界面

    想让执行完程序后自动关闭窗口,而不用点击右上角叉叉的方法是self.close(),具体应用还是以treewidget为例. 前面我们写了一个treewidget的界面,并且实现了界面代码分离,具体实 ...

  8. 第九篇 -- 可以上网,连WIFI弹出网页

    最近在调试WIFI模块时,程序路径没走对,导致运行了其他的函数,修改了配置文件,之后每次连接WIFI时都会弹出网页,并且明明可以上网,下面电脑符号那儿还会出现黄标,甚是心烦.上网搜索一番,终是解决了. ...

  9. Python - 可变和不可变对象

    前置知识 在 Python 中,一切皆为对象 Python 中不存在值传递,一切传递的都是对象的引用,也可以认为是传址 有哪些可变对象,哪些不可变对象? 不可变对象:字符串.元组.数字(int.flo ...

  10. mongo-express 远程代码执行漏洞(CVE-2019-10758)

    影响版本 mongo-express 0.53.0 POST /checkValid HTTP/1.1 Host: 192.168.49.2:8081 Accept-Encoding: gzip, d ...