回顾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. vuejs第一集之:vuejs了解

    1,了解到前后端分离2,连接到vuejs3,搜集书籍: Vuejs前端开发基础与项目实战 (https://detail.tmall.com/item.htm?spm=a230r.1.14.107.6 ...

  2. PAT乙级:1077 互评成绩计算 (20分)

    PAT乙级:1077 互评成绩计算 (20分) 在浙大的计算机专业课中,经常有互评分组报告这个环节.一个组上台介绍自己的工作,其他组在台下为其表现评分.最后这个组的互评成绩是这样计算的:所有其他组的评 ...

  3. 前端基础EL表达式(八)

    一.什么是EL表达式? 1.什么是EL表达式? EL(Expression Language) 是为了使JSP写起来更加简单.表达式语言的灵感来自于 ECMAScript 和 XPath 表达式语言, ...

  4. Allure测试框架 python

    关于Allure Allure是一个report框架,可以基于一些测试框架生成测试报告,比较常用的一般是Junit/Testng框架: Allure 生成的报告样式简洁美观,同时又支持中文: Allu ...

  5. SAML 2.0 实例分析 sp向idp发送请求(3)

    user没有登陆过sp,此时sp向idp发送请求,下文是请求的xml形式 <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAM ...

  6. python项目案例

    python项目案例1:----此学习案例用python3编写,摘自明日科技,感谢! 学生管理系统: 功能描述:具有增删改查,排序,保存并显示学生的全部信息. 1.主界面---函数menu(),显示功 ...

  7. 构建前端第6篇之---内嵌css样式 <el-button style="width:100%"> 登录 </el-button>

    张艳涛写于2021-1-20日 What: 如何让button的长度和input长度一致呢 最先想到的是给这个button加一个class ="buttonclass",然后在vu ...

  8. Java文件I/O简单介绍

    目录 一.File类 1.1 构造方法 1.2 常用方法 1.3 例子 二.基础I/O:字节流.字符流 2.1 字节流 2.1.1 字节输出流 OutputStream 2.1.2 FileOutpu ...

  9. 添加xxx到右键菜单

    1. 添加notepad++到右键菜单[1] 添加到 右键菜单 将以下内容保存为 OpenWithNotepad++.reg 文件,双击运行即可(其中可执行文件路径和菜单项名称请自行替换): 注: 下 ...

  10. mysql中的with rollup得到group by的汇总信息

    使用mysql中的with rollup可以得到每个分组的汇总级别的数据: 表如下: CREATE TABLE `test3` (  `id` int(5) unsigned NOT NULL AUT ...