深度插值的差错原因



当投影的图形与投影的平面不平行时,这时进行透视投影,从上图中可以看出,投影平面上的线段时均匀的,但是在原图形上的线段是非均匀的,这只是一个例子,但也可以看出投影会导致图形的变形,在我们利用重心坐标,进行深度插值时原空间中的重心坐标会发生变形,导致我们得到的深度不是正确的,这一点在对纹理坐标进行插值时尤其明显

透视深度插值公式推导

虽然在原空间与投影平面上的三角形可能发生变形,但是它们的重心坐标依然满足一定的关系:

投影平面:

\(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 透视深度插值矫正与抗锯齿分析的更多相关文章

  1. 透视校正插值(Perspective-Correct Interpolation)

    在渲染器光栅化每个三角形的过程中,需要对根据顶点属性对三角形进行扫描线插值.此时由于投影面上顶点的2D坐标与顶点属性不成线性关系,因此是不能简单地使用线性插值来计算顶点属性的. 此时应当利用透视校正插 ...

  2. 【ShaderToy】基础篇之再谈抗锯齿(antialiasing,AA)

    写在前面 在之前的基础篇中,我们讲到了在绘制点线时如何处理边缘的锯齿,也就是使用smoothstep函数.而模糊参数是一些定值,或者是跟屏幕分辨率相关的数值,例如分辨率宽度的5%等等.但这种方法其实是 ...

  3. 【ShaderToy】抗锯齿相关函数

    *示例代码可以直接在ShaderToy中运行. *我放在这里咯ShaderToy基础学习中~欢迎交流(ノ>ω<)ノ 先上未抗锯齿的两个圆形图案,可以清楚看清图案边缘像素块,即“锯齿”. 附 ...

  4. DirectX11 With Windows SDK--40 抗锯齿:FXAA

    前言 在默认的情况下渲染,会看到物体的边缘会有强烈的锯齿感,究其原因在于采样不足.但是,尝试提升采样的SSAA会增大渲染的负担:而硬件MSAA与延迟渲染又不能协同工作.为此我们可以考虑使用后处理的方式 ...

  5. 深度解析Java8 – AbstractQueuedSynchronizer的实现分析(上)

    本文首发在infoQ :www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer 前言: Java中的FutureTask作为可异步执行任 ...

  6. Android 抗锯齿的两种方法

    Android 抗锯齿的两种方法 (其一:paint.setAntiAlias(ture);paint.setBitmapFilter(true))   在Android中,目前,我知道有两种出现锯齿 ...

  7. Linux 下 netbeans 字体抗锯齿正解

    转自:http://leenjewel.blog.163.com/blog/static/601937922010124444051/ 说来这个不难,主要是我看网上有的写的不是很明确,甚至有的写的根本 ...

  8. CSS抗锯齿 font-smoothing 属性介绍

    CSS3里面加入了一个“-webkit-font-smoothing”属性. 这个属性可以使页面上的字体抗锯齿,使用后字体看起来会更清晰舒服. 加上之后就顿时感觉页面小清晰了. 淘宝也在用哦! 它有三 ...

  9. PHP合成图片、生成文字、居中对齐、画线、矩形、三角形、多边形、图片抗锯齿、不失真 高性能源码示例

    function generateImg($source, $text1, $text2, $text3, $font = './msyhbd.ttf') { $date = '' . date ( ...

  10. Unity抗锯齿

    Unity抗锯齿设置是针对模型,对模型的阴影的锯齿设置无效,不知道我这样的理解是否正确. 遇到的问题 而我是要对灯光照射在模型上产生的阴影进行抗锯齿,暂时还未研究出解决方案,希望知道的朋友告知一声. ...

随机推荐

  1. [jmeter]简介与安装

    简介 JMeter是开源软件Apache基金会下的一个性能测试工具,用来测试部署在服务器端的应用程序的性能. 安装 安装jmeter 从 官网 下载jmeter的压缩包 安装jdk并配置 JAVA_H ...

  2. 应用程序接口(API)安全的入门指南

    ​  什么是 API?​ 对于初学者来说,API 是指为两个不同的应用之间实现流畅通信,而设计的应用程序编程接口.它通常被称为应用程序的"中间人".由于我们需要保护用户的持有数据. ...

  3. Go学习笔记3

    九.错误处理 1.defer+recover机制处理异常错误 展示错误: 发现:程序中出现错误/恐慌以后,程序被中断,无法继续执行. 错误处理/捕获机制: 内置函数recover: 2.自定义错误 需 ...

  4. 小知识:vi 查找如何不区分大小写

    在使用vi查找数据库的truncate记录日志时,发现对应语句夹杂了大小写,不够规范: 而vi默认查找是区分大小写的,如何不区分大小写查找指定内容呢? 有两种方式: (1)在查找指令后面额外加上\c标 ...

  5. Mysql中文字符串提取datetime

    DATE_FORMAT无法用于提取含中文字符的时间字符串中的时间, 可以通过STR_TO_DATE来提取其中的信息, 如下: SELECT STR_TO_DATE("2018年11月05日 ...

  6. ConcurrentHashMap底层源码分析

    ConcurrentHashMap源码底层分析 1.ConcurrentHashMap初始化 jdk8之后,ConcurrentHashMap采用了HashMap的底层结构(数据,链表,红黑树),在此 ...

  7. Redis最常见的5种应用场景

    Redis作为当今最流行的内存数据库,已经成为服务端加速的必备工具之一.对于Redis为什么那么快?以及Redis采用单线程,但为什么反而获得更高的性能的疑问,在之前的Redis为什么那么快?一文中, ...

  8. 【ASP.NET Core】在 Mini-API 中注入服务

    经过版本更新,Mini API 的功能逐步完善,早期支持得不太好的 mini API 现在许多特性都可以用了,比如灰常重要的依赖注入. 咱们先来个相当简单的注入测试.来,定义一个服务类,为了偷懒,老周 ...

  9. 【Azure Function App】Python Function调用Powershell脚本在Azure上执行失败的案例

    问题描述 编写Python Function,并且在Function中通过 subprocess  调用powershell.exe 执行 powershell脚本. import azure.fun ...

  10. 文心一言 VS 讯飞星火 VS chatgpt (122)-- 算法导论10.4 3题

    三.用go语言,给定一个n结点的二叉树,写出一个 O(n)时间的非递归过程,将该树每个结点的关键字输出.可以使用一个栈作为辅助数据结构. 文心一言: 以下是一个使用 Go 语言编写的函数,该函数使用一 ...