games101-2 透视深度插值矫正与抗锯齿分析
深度插值的差错原因

当投影的图形与投影的平面不平行时,这时进行透视投影,从上图中可以看出,投影平面上的线段时均匀的,但是在原图形上的线段是非均匀的,这只是一个例子,但也可以看出投影会导致图形的变形,在我们利用重心坐标,进行深度插值时原空间中的重心坐标会发生变形,导致我们得到的深度不是正确的,这一点在对纹理坐标进行插值时尤其明显
透视深度插值公式推导
虽然在原空间与投影平面上的三角形可能发生变形,但是它们的重心坐标依然满足一定的关系:
投影平面:
\(1 = \alpha^{'} +\beta^{'} +\gamma^{'}\)
原空间:
\(1 = \alpha +\beta +\gamma\)
现在我们只有投影平面上三角形的bounding box中一个个像素点,我们想要得到这个像素点真实的深度值,假设一个像素点真实的深度值为\(Z\),三角形三个顶点真实的深度值分别为\(Z_{a},Z_{b},Z_{c}\),我们对第一个式子进行恒等变形:
$\frac{Z}{Z} = \frac{Z_{a}}{Z_{a}}\alpha^{'} + \frac{Z_{b}}{Z_{b}}\beta^{'} + \frac{Z_{c}}{Z_{c}}\gamma^{'} $
进一步变换得到:
\(Z = (\frac{Z}{Z_{a}}\alpha^{'})Z_{a} + (\frac{Z}{Z_{b}}\beta^{'})Z_{b} + (\frac{Z}{Z_{c}}\gamma^{'})Z_{c}\)
我们对照原空间的深度重心插值公式:
\(Z = \alpha Z_{a} + \beta Z_{b} + \gamma Z_{c}\)
可以得到:
\(\alpha = \frac{Z}{Z_{a}}\alpha^{'}\)
\(\beta = \frac{Z}{Z_{b}}\beta^{'}\)
\(\gamma = \frac{Z}{Z_{c}}\gamma^{'}\)
我们再代入之前的第二个式子:
\(1 = \frac{Z}{Z_{a}}\alpha^{'} + \frac{Z}{Z_{b}}\beta^{'} + \frac{Z}{Z_{c}}\gamma^{'}\)
两边同时除以\(Z\):
$\frac{1}{Z} = \frac{1}{Z_{a}}\alpha^{'} + \frac{1}{Z_{b}}\beta^{'} + \frac{1}{Z_{c}}\gamma^{'} $
我们可以进一步考虑更一般的情况,对任意属性(uv坐标颜色法线等)使用重心坐标进行插值:
\(I = \alpha I_{a} + \beta I_{b} + \gamma I_{c}\)
\(I = Z(\alpha^{'}\frac{I_{a}}{Z_{a}} + \beta^{'}\frac{I_{b}}{Z_{b}} + \gamma^{'}\frac{I_{c}}{Z_{c}} )\)
games101中的错误
有了上述理论基础,我们再来看看games101中的实现:
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();
注意在前面:
auto v = t.toVector4();
games101将一个三维向量拓展为四维向量,理论上一个像素点的坐标应该是(x,y,z,w),其中x,y代表投影的xy坐标,z代表压缩之后的z值,一般在[-1,1]或者[0,1]或者[n,f]之间,w一般用于存储原空间真实的深度值,但是上述拓展默认将w设置为1,w存储的不是真实的深度值,因此:
float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
这一步使用的深度值是错误的
假如是正确的,其实这一步得到的w_reciprocal已经是正确的深度矫正值,也不需要在后面再求z值
但是最终结果我们也没有发现明显的错误,可以认为即使使用错误的深度值,对最终结果也影响不大
msaa与ssaa简要定义
MSAA:多重采样抗锯齿是一种选择性的抗锯齿技术,它在渲染图像时对特定部分进行多次采样。通常,它会对几何边缘周围进行多次采样,以减少锯齿状边缘的出现。
SSAA:超级采样抗锯齿是一种全局的抗锯齿技术,它通过在整个图像上进行更高分辨率的采样,然后缩放到目标分辨率,从而减少锯齿和增强图像的质量。
games101中ssaa的实现
ssaa实现的是更高分辨率的采样,为了实现这一点我们需要为每个采样点都维护深度表与颜色表,在对每个采样点进行覆盖检测以及深度检测之后,将采样点的颜色进行平均,设置为像素点颜色:
for(int x=min_x; x<=max_x; x++) {
for(int y=min_y; y<=max_y; y++) {
int eid = get_index(x,y)*4;
for(int k = 0; k < 4; k++){//遍历像素的每个样本
if(insideTriangle(x+a[k], y+a[k+1], v.data())){
//计算重心坐标
auto[alpha, beta, gamma] = computeBarycentric2D(x+a[k],y+a[k+1], 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;
//如果此时深度值大于当前存储深度值,说明被遮挡了,不做处理
if (depth_sample[eid + k] < z_interpolated) {
continue;
}
//反之,更新当前深度值,对采样点进行着色
depth_sample[eid + k] = z_interpolated;
frame_sample[eid + k] = t.getColor();
}
}
Eigen::Vector3f p;
p << x, y, 1;
//平均四个采样点的颜色,简单的线性混合
Eigen::Vector3f color = (frame_sample[eid] + frame_sample[eid + 1] + frame_sample[eid + 2] + frame_sample[eid + 3])/4;
set_pixel(p, color);
}
}
games101中msaa的实现
msaa与ssaa类似,也是对四个采样点的颜色进行混合,也需要对采样点进行覆盖以及深度检测,不过不同的时,msaa会记录深度的变化,只有在深度发生变化,认为检测到边缘的时候,才会进行shading,并且不需要维护颜色表,减少了时间以及空间开销:
for(int x=min_x; x<=max_x; x++) {
for(int y=min_y; y<=max_y; y++) {
//使用msaa方法,统计像素覆盖率
int eid = get_index(x,y)*4;
//统计像素的覆盖率与深度变化
float count_coverage = 0,count_depth = 0;
for(int k = 0; k < 4; k++){//遍历像素的每个样本
if(insideTriangle(x+a[k], y+a[k+1], v.data())){
auto[alpha, beta, gamma] = computeBarycentric2D(x+a[k],y+a[k+1], 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;
//如果该采样点在三角形内,增加覆盖率的计数
count_coverage++;
if (depth_buf[eid + k] < z_interpolated) {
continue;
}
//如果该采样点的深度发生了变化,说明该像素分布在边缘,需要进行抗锯齿
count_depth++;
depth_buf[eid + k] = z_interpolated;
}
}
//如果该像素在边缘,需要进行抗锯齿
if(count_depth > 0){
int ind = get_index(x,y);
Eigen::Vector3f p;
p << x, y, 1;
//混合颜色
Eigen::Vector3f color = (count_coverage / 4)*t.getColor() +(1 - count_coverage/4)*frame_buf[ind];
set_pixel(p, color);
}
}
}
games101-2 透视深度插值矫正与抗锯齿分析的更多相关文章
- 透视校正插值(Perspective-Correct Interpolation)
在渲染器光栅化每个三角形的过程中,需要对根据顶点属性对三角形进行扫描线插值.此时由于投影面上顶点的2D坐标与顶点属性不成线性关系,因此是不能简单地使用线性插值来计算顶点属性的. 此时应当利用透视校正插 ...
- 【ShaderToy】基础篇之再谈抗锯齿(antialiasing,AA)
写在前面 在之前的基础篇中,我们讲到了在绘制点线时如何处理边缘的锯齿,也就是使用smoothstep函数.而模糊参数是一些定值,或者是跟屏幕分辨率相关的数值,例如分辨率宽度的5%等等.但这种方法其实是 ...
- 【ShaderToy】抗锯齿相关函数
*示例代码可以直接在ShaderToy中运行. *我放在这里咯ShaderToy基础学习中~欢迎交流(ノ>ω<)ノ 先上未抗锯齿的两个圆形图案,可以清楚看清图案边缘像素块,即“锯齿”. 附 ...
- DirectX11 With Windows SDK--40 抗锯齿:FXAA
前言 在默认的情况下渲染,会看到物体的边缘会有强烈的锯齿感,究其原因在于采样不足.但是,尝试提升采样的SSAA会增大渲染的负担:而硬件MSAA与延迟渲染又不能协同工作.为此我们可以考虑使用后处理的方式 ...
- 深度解析Java8 – AbstractQueuedSynchronizer的实现分析(上)
本文首发在infoQ :www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer 前言: Java中的FutureTask作为可异步执行任 ...
- Android 抗锯齿的两种方法
Android 抗锯齿的两种方法 (其一:paint.setAntiAlias(ture);paint.setBitmapFilter(true)) 在Android中,目前,我知道有两种出现锯齿 ...
- Linux 下 netbeans 字体抗锯齿正解
转自:http://leenjewel.blog.163.com/blog/static/601937922010124444051/ 说来这个不难,主要是我看网上有的写的不是很明确,甚至有的写的根本 ...
- CSS抗锯齿 font-smoothing 属性介绍
CSS3里面加入了一个“-webkit-font-smoothing”属性. 这个属性可以使页面上的字体抗锯齿,使用后字体看起来会更清晰舒服. 加上之后就顿时感觉页面小清晰了. 淘宝也在用哦! 它有三 ...
- PHP合成图片、生成文字、居中对齐、画线、矩形、三角形、多边形、图片抗锯齿、不失真 高性能源码示例
function generateImg($source, $text1, $text2, $text3, $font = './msyhbd.ttf') { $date = '' . date ( ...
- Unity抗锯齿
Unity抗锯齿设置是针对模型,对模型的阴影的锯齿设置无效,不知道我这样的理解是否正确. 遇到的问题 而我是要对灯光照射在模型上产生的阴影进行抗锯齿,暂时还未研究出解决方案,希望知道的朋友告知一声. ...
随机推荐
- [jmeter]简介与安装
简介 JMeter是开源软件Apache基金会下的一个性能测试工具,用来测试部署在服务器端的应用程序的性能. 安装 安装jmeter 从 官网 下载jmeter的压缩包 安装jdk并配置 JAVA_H ...
- 应用程序接口(API)安全的入门指南
什么是 API? 对于初学者来说,API 是指为两个不同的应用之间实现流畅通信,而设计的应用程序编程接口.它通常被称为应用程序的"中间人".由于我们需要保护用户的持有数据. ...
- Go学习笔记3
九.错误处理 1.defer+recover机制处理异常错误 展示错误: 发现:程序中出现错误/恐慌以后,程序被中断,无法继续执行. 错误处理/捕获机制: 内置函数recover: 2.自定义错误 需 ...
- 小知识:vi 查找如何不区分大小写
在使用vi查找数据库的truncate记录日志时,发现对应语句夹杂了大小写,不够规范: 而vi默认查找是区分大小写的,如何不区分大小写查找指定内容呢? 有两种方式: (1)在查找指令后面额外加上\c标 ...
- Mysql中文字符串提取datetime
DATE_FORMAT无法用于提取含中文字符的时间字符串中的时间, 可以通过STR_TO_DATE来提取其中的信息, 如下: SELECT STR_TO_DATE("2018年11月05日 ...
- ConcurrentHashMap底层源码分析
ConcurrentHashMap源码底层分析 1.ConcurrentHashMap初始化 jdk8之后,ConcurrentHashMap采用了HashMap的底层结构(数据,链表,红黑树),在此 ...
- Redis最常见的5种应用场景
Redis作为当今最流行的内存数据库,已经成为服务端加速的必备工具之一.对于Redis为什么那么快?以及Redis采用单线程,但为什么反而获得更高的性能的疑问,在之前的Redis为什么那么快?一文中, ...
- 【ASP.NET Core】在 Mini-API 中注入服务
经过版本更新,Mini API 的功能逐步完善,早期支持得不太好的 mini API 现在许多特性都可以用了,比如灰常重要的依赖注入. 咱们先来个相当简单的注入测试.来,定义一个服务类,为了偷懒,老周 ...
- 【Azure Function App】Python Function调用Powershell脚本在Azure上执行失败的案例
问题描述 编写Python Function,并且在Function中通过 subprocess 调用powershell.exe 执行 powershell脚本. import azure.fun ...
- 文心一言 VS 讯飞星火 VS chatgpt (122)-- 算法导论10.4 3题
三.用go语言,给定一个n结点的二叉树,写出一个 O(n)时间的非递归过程,将该树每个结点的关键字输出.可以使用一个栈作为辅助数据结构. 文心一言: 以下是一个使用 Go 语言编写的函数,该函数使用一 ...