回顾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. MySql数据库-查询、插入数据时转义函数的使用

    最近在看一部php的基础视频教程,在做案例的时,当通过用户名查询用户信息的时候,先使用了转义函数对客户提交的内容进行过滤之后再交给sql语句进行后续的操作.虽然能看到转义函数本身的作用,但是仍然有一些 ...

  2. Splay与FHQ-Treap

    两个一起学的,就放一块了. 主要是用来存板子. Splay //This is a Splay Tree. #include <cstdio> #include <cstring&g ...

  3. Servlet 单例多线程详解(六)

    一.Servlet 单例多线程 Servlet如何处理多个请求访问?Servlet容器默认是采用单实例多线程的方式处理多个请求的:1.当web服务器启动的时候(或客户端发送请求到服务器时),Servl ...

  4. odoo里面context用法

    原文转自:https://www.cnblogs.com/zhaoweihang/p/9698852.html <field name="partner_id" string ...

  5. PHP:字符串转数组,数组转字符串;字符串截取、替换、查找

    字符串转数组$str = 'one|two|three|four'; print_r(explode('|', $str)); //explode 以字符串分割字符串到数组 $str = 'one t ...

  6. 题解 P6892 [ICPC2014 WF]Baggage

    解题思路 非常好的一道构造题. 在手动模拟几个样例(也许不止几个)之后呢. 就可以发现其实这些操作的开始以及最后几步是有相通之处的. 关于手动模拟的样例放在了文章末尾,需要的自取. 先考虑操作次数. ...

  7. user-agent浏览器标识集合

    user_agent_list = [ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET ...

  8. 聊聊TCP Keepalive、Netty和Docker

    聊聊TCP Keepalive.Netty和Docker 本文主要阐述TCP Keepalive和对应的内核参数,及其在Netty,Docker中的实现.简单总结了工作中遇到的问题,与大家共勉. 起因 ...

  9. 04.委托Delegation

    1. 基本了解 1.1 委托简述 官方文档 委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用,用于将方法作为参数传递给其他方法,可将任何可访问类或结构中与委托类型匹配的任何方法分配给委 ...

  10. 一专属SRC - XSS - Bypass长亭Waf

    bypass是预言表哥绕的,擦,我这篮子玩xss什么都绕不过 https://www.cnblogs.com/yuyan-sec 这博客我直接倒背如流 主要记录下这次挖掘的过程 先说下 bypass姿 ...