回顾games101中的SSAA和MSAA
回顾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,不需要扩展额外的深度缓存空间,但是效果不是特别好,可能需要后续的其他改进方法吧
参考博文
回顾games101中的SSAA和MSAA的更多相关文章
- 回顾Games101图形学(一)几何变换中一些公式的推导
回顾Games101 chatper1 - 6 前言 本文只写回顾后重新加深认识的知识 透视除法的意义 经过MVP矩阵之后,将模型空间下某点的坐标,转换成了裁剪空间下的坐标,此时因为裁剪空间的范围是x ...
- 25.redux回顾,redux中的action函数异步
回顾:Redux: 类似于 Vuex 概念:store/reducer/action action:动作 {type,.....} 一定要有type 其他属性不做限制 reducer:通过计算产生st ...
- 简单回顾C++中的字符串
C++中有两种字符串形式,一种是C语言字符数组,一般可以使用 char*指针来操作它:另一种是C++中基于标准库的string类型,这算是更高层次的抽象数据类型. 主要讨论一下string类型,既然是 ...
- 回顾MySQL中的事务特征
一.事务定义Transaction事务:一个最小的不可再分的工作单元:通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)一个完整的业务需要批量的DML(insert. ...
- 回顾js中的cookie/localstorage
1.首先简单总结下cookie cookie:可以做会话跟踪 特点: 1.大小限制(不能超过4k) 2.每个域下cookie不能超过50个 3.有效期(和设定时间有关), ...
- 专攻知识小点——回顾JavaWeb中的servlet(二)
续前篇... ServletConfig对象 Servlet的配置对象,ServletConfig对象作用域只能在一个Servlet类中使用.每个Servlet类都维护一个ServletConfig对 ...
- 专攻知识小点——回顾JavaWeb中的servlet(三)
HttpSession基本概述 ** ** 1.HttpSession:是服务器端的技术.和Cookie一样也是服务器和客户端的会话.获得该对象是通过HTTPServletRequest的方法getS ...
- 讲解Canvas中的一些重要方法
Canvas所提供的各种方法根据功能来看大致可以分为几类: 第一是以drawXXX为主的绘制方法: 第二是以clipXXX为主的裁剪方法: 第三是以scale.skew.translate和rotat ...
- 思考 Swift 中的 MirrorType 协议
Swift中的反射非常有限,仅允许以只读方式访问元数据的类型子集.或许 Swift 因有严格的类型检验而不需要反射.编译时已知各种类型,便不再需要进行进一步检查或区分.然后大量的 Cocoa API ...
随机推荐
- 【LeetCode】28. 实现 strStr()
28. 实现 strStr() 知识点:字符串:KMP算法 题目描述 实现 strStr() 函数. 给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 ne ...
- Python+Scrapy+Crawlspider 爬取数据且存入MySQL数据库
1.Scrapy使用流程 1-1.使用Terminal终端创建工程,输入指令:scrapy startproject ProName 1-2.进入工程目录:cd ProName 1-3.创建爬虫文件( ...
- Docker的学习体验
由于兴致使然,便想学习一点Docker技术.于是,写了这篇学习Docker的体会.笔拙,见谅. 第一件事--把网线插上 相信很多人都被官网的<Sample application>的 do ...
- 每天五分钟Go - 闭包
闭包的示例代码 func getSequence() func() int{ i:=0 return func() int { i+=1 return i } } 首先,函数名getSequence, ...
- POJ3264线段树求最值
刚开始还觉得有点怪怪的.因为想着如果每个树只是单纯地记录它所在的区间的话会不会有不在区间内的数据给更新了,但是我好像是傻掉了因为如果有这种情况出现的话在父亲节点就会分成l,mid和mid+1,r两个区 ...
- 【洛谷P2028 龙兄摘苹果】动态规划
分析 第二类striling数 考虑最后一个数到底是放在之前的任意一个集合内,还是自成一个集合 \[F_{i\ j}=F_{i-1\ j-1}+j\times F_{i-1,j} \] AC代码 #i ...
- Navicat Premium 12安装及破解
特别提醒,Navicat Premium 12安装包请用我给的链接下载,不然会无法破解 下载Navicat Premium 12地址:https://pan.baidu.com/s/1AQsryKpJ ...
- Cypress 高级用法系列 一
1. Multiple Assertions cy .get('[data-cy=task]') .then( item => { expect(item[0]).to.contain.text ...
- 通过jstack日志分析和问题排查
简介 jstack用于生成java虚拟机当前时刻的线程快照.线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁.死循环 ...
- wdlinux一键安装包
下载安装(ssh登录服务器,执行如下操作即可,需要用到root用户权限来安装) v3版本已经发布,更多可看论坛 wdCP v3版本讨论区 更多安装请看 http://www.wdlinux.cn/bb ...