在在线阅读了几篇文章之后,我可以自信地说我对使用射线追踪时抗锯齿的工作原理一无所知。
我所了解的是,单个像素/射线被分成4个子像素和4条射线,而不是1条。
有人可以解释一下这是怎么做的(最好是用代码)吗?
foreach pixel : p{acc = 0; foreach subsample : s { acc+=sample_scene(s);} store(p, acc);}
在在线阅读了几篇文章之后,我可以自信地说我对使用射线追踪时抗锯齿的工作原理一无所知。
我所了解的是,单个像素/射线被分成4个子像素和4条射线,而不是1条。
有人可以解释一下这是怎么做的(最好是用代码)吗?
foreach pixel : p{acc = 0; foreach subsample : s { acc+=sample_scene(s);} store(p, acc);}
Answers:
我认为可以肯定地说,有两种方法可以在光线追踪中进行AA:
1:如果您拥有最终图像和深度图像,则可以应用游戏中使用的几乎所有现有技术(FXAA等),这些技术直接作用于最终图像,并且与光线追踪无关
2:第二种方法是考虑每个像素的多条光线,然后平均结果。对于一个非常简单的版本,可以这样考虑:
此方法还有其他变体。例如,您可以调整恰好位于几何图形边缘的像素的样本数量,这意味着对于某些像素,您将只有4个样本,而对于其他像素,则只有16个样本。
检查上面评论中的链接。
Raxvan完全正确地说,“传统”抗锯齿技术将在光线跟踪中起作用,包括那些使用诸如深度之类的信息进行抗锯齿的技术。例如,您甚至可以在光线跟踪中进行时间抗锯齿。
朱利安(Julien)在Raxvan的第二项内容上进行了扩展,这是对超级采样的解释,并展示了您实际上是如何做的,还提到您可以随机化像素内采样的位置,但是随后您进入了信号处理国家,更深,这绝对是!
如果这样做,您仍然可以使用别名。最好不要执行此操作,因为您可以提高采样率,因此能够处理更高频率的数据(又称较小的细节),但仍可能导致混叠。
当您仅使用“常规”随机数(例如从rand()或std :: uniform_int_distribution获得)时,称为“白噪声”,因为它包含所有频率,例如白光如何由其他所有颜色(频率)组成)的光。
使用白噪声将像素内的样本随机化会带来问题,有时您的样本会聚集在一起。例如,如果您平均一个像素中有100个样本,但是它们最终都位于该像素的左上角,那么您将不会获得有关该像素其他部分的任何信息,因此最终得到的像素颜色将缺少有关其应使用的颜色的信息。
更好的方法是使用仅包含高频成分的蓝噪声(例如蓝光是高频光)。
蓝色噪点的好处是,您可以获得均匀的像素覆盖范围,就像使用均匀的采样网格一样,但是,您仍然会得到一些随机性,这将混叠化为噪点并为您提供更好的图像。
不幸的是,蓝噪声的计算成本非常高,最好的方法似乎都已申请了专利(到底是什么?!),但是一种方法是由pixar发明的(我认为也已获得专利,但并非100%肯定)就是制作一个均匀的采样点网格,然后将每个采样点随机偏移少量-就像采样网格宽度和高度的正负一半之间的随机量一样。这样,您就可以以相当便宜的价格获得某种蓝噪声采样。
请注意,这是分层采样的一种形式,泊松磁盘采样也是这种形式的一种,这也是产生蓝噪声的一种方式:https : //www.jasondavies.com/poisson-disc/
如果您有兴趣进一步研究,可能还需要检查一下这个问题并回答!
最后,这些东西开始误入蒙特卡洛路径跟踪领域,这是进行逼真的光线跟踪的常用方法。如果您有兴趣了解更多有关此内容,请阅读本书!
http://blog.demofox.org/2016/09/21/path-tracing-getting-started-with-diffuse-and-emissive/
让我们假设一个相当典型的光线跟踪主循环:
struct Ray
{
vec3 origin;
vec3 direction;
};
RGBColor* image = CreateImageBuffer(width, height);
for (int j=0; j < height; ++i)
{
for (int i=0; i < width; ++i)
{
float x = 2.0 * (float)i / (float)max(width, height) - 1.0;
float y = 2.0 * (float)j / (float)max(width, height) - 1.0;
vec3 dir = normalize(vec3(x, y, -tanHalfFov));
Ray r = { cameraPosition, dir };
image[width * j + i] = ComputeColor(r);
}
}
可以做4个MSAA样本的一种可能修改是:
float jitterMatrix[4 * 2] = {
-1.0/4.0, 3.0/4.0,
3.0/4.0, 1.0/3.0,
-3.0/4.0, -1.0/4.0,
1.0/4.0, -3.0/4.0,
};
for (int j=0; j < height; ++i)
{
for (int i=0; i < width; ++i)
{
// Init the pixel to 100% black (no light).
image[width * j + i] = RGBColor(0.0);
// Accumulate light for N samples.
for (int sample = 0; sample < 4; ++sample)
{
float x = 2.0 * (i + jitterMatrix[2*sample]) / (float)max(width, height) - 1.0;
float y = 2.0 * (i + jitterMatrix[2*sample+1]) / (float)max(width, height) - 1.0;
vec3 dir = normalize(vec3(x, y, -tanHalfFov) + jitter);
Ray r = { cameraPosition, dir };
image[width * j + i] += ComputeColor(r);
}
// Get the average.
image[width * j + i] /= 4.0;
}
}
另一种可能性是做一个随机抖动(而不是上面的基于矩阵的抖动),但是您很快就会进入信号处理领域,需要大量阅读才能知道如何选择一个好的噪声函数。
但是这个想法仍然是一样的:考虑像素代表一个很小的正方形区域,而不是只发射一条穿过像素中心的光线,而是发射覆盖整个像素区域的许多光线。射线分布越密集,得到的信号越好。
PS:我是即时编写上面的代码,所以我希望其中有一些错误。这仅是为了展示基本思想。
只是添加到上面的答案:
分布式射线追踪(库克,波特和木匠)。允许您同时进行空间AA,时间AA(即运动模糊)和景深/景深。最好阅读论文,但基本上每个像素发射的N射线也可以分配伪随机时间(用于运动模糊)和透镜上的位置(以获得聚焦效果)。
自适应超级采样:您最初会在每个像素上发射一定数量的光线。但是,如果本地附近的光线返回的结果明显不同,则可以在本地增加采样率(例如2倍)以改善结果。您可以选择重复此过程。由于对象仍然可以“落入样本之间”,因此它不是完美的,但是它可能比以更高的速率进行均匀采样便宜。
带有圆锥体(Amanatides)的光线追踪:不是使用无限细的光线,而是通过圆锥体来近似每条光线,比如说它的直径覆盖了所有像素(和一些邻居),可以评估部分覆盖率。它还对纹理采样/抗锯齿,柔和阴影以及潜在的模型LOD有好处。我做了一个实现许多年前-它肯定更困难的事,但它避免了大量的附加射线。IIRC也有一个称为铅笔追踪的方案(但是我最初的搜索没有找到论文的链接)