优化光线追踪器性能

24

我正在使用D语言编写一个相对简单的光线跟踪器/路径跟踪器(http://dsource.org/projects/stacy),即使进行了全面优化,仍需要每个光线数千个处理器周期。有没有其他方法可以加速它?更一般地说,您是否知道用于光线跟踪的良好优化/更快方法?

编辑:这是我已经做过的。

  • 代码已经高度并行化运行
  • 临时数据以高效缓存的方式结构化,并对齐到16b
  • 屏幕分成32x32个瓷砖
  • 目标数组的排列方式使得瓷砖中所有后续像素在内存中都是连续的
  • 执行基本场景图优化
    • 替换了常见的对象组合(例如盒子中的平面-平面CSG)与预优化的对象
  • 向量结构能够利用GDC的自动向量化支持
  • 通过惰性计算来找到射线上的后续命中;这可以防止不必要的CSG计算
  • 只支持普通原始形状以及CSG操作和基本材质属性,不支持三角形
  • 支持边界

FeepingCreature,如果时间分析告诉你,你的光线/平面光线/球体相交是时间消耗的关键,那么答案就是要减少测试次数。除非我漏掉了什么,否则我关于空间划分的评论似乎是正确的方向。 - simon
9个回答

11
光线跟踪速度的典型一阶提升是某种空间划分方案。仅基于您的项目概述页面,似乎您还没有这样做。
可能最常见的方法是八叉树,但最好的方法可能是多种方法的组合(例如,空间划分树和类似邮箱的东西)。包围盒/球测试是一种快速、简单且低成本的方法,但需要注意两点:1)在许多情况下它们并没有太大帮助;2)如果你的对象已经是简单的基元,你不会得到太多收益(甚至可能失去)。相较于八叉树,你可以更轻松地实现一个规则网格进行空间划分,但只有表面位置相对均匀的场景才能发挥出其真正的作用。
很多事情取决于你所代表的对象的复杂性、内部设计(即是否允许局部变换、对象的引用副本、隐式表面等),以及你试图达到的精度。如果你正在编写一个带有隐式表面的全局光照算法,那么权衡可能与你编写用于网格对象的基本光线跟踪器有些不同。我还没有详细研究您的设计,所以我不确定您已经考虑了以上哪些内容。
像任何性能优化过程一样,你需要首先测量找出你实际上花费时间的地方,然后通过改进算法为首选来提高性能,次要是通过改进代码。

已经记录下来的最有效的数据结构是kd树(用于可以轻松分割的简单几何基元)和包围体层次结构(用于不能轻易分割的更复杂的基元)。通常,这些树会平衡考虑两个嵌套框的表面积商以估计随机光线与内部框相交的概率(这种考虑通常称为表面积启发式)。例如,参见Havran的研究(http://dcgi.felk.cvut.cz/home/havran/publications.html),他的大多数论文都致力于索引rt场景。 - Dude

10

我在我的光线追踪器中学到的一件事情是许多旧的规则不再适用。例如,许多光线跟踪算法需要进行大量测试以从计算上昂贵的计算中“提前退出”。但在某些情况下,我发现消除额外的测试并始终运行完整的计算会更好。在现代机器上,算术运算速度很快,但是预测错误的分支开销很高。通过最小化条件分支来重写射线-多边形相交测试,我获得了近30%的加速。

有时候,最佳方法可能与常识相反。例如,我发现许多具有少量大型对象的场景在将它们分解成大量小对象后运行得更快。根据场景几何形状,这可以使空间细分算法放弃许多相交测试,而且交点测试只能快到一定程度。要实现显著的加速,必须消除它们。

分层包围体非常有用,但我最终理解了kd树,并获得了巨大的速度提升。当然,构建树需要成本,这可能会使其在实时动画方面变得禁止。

注意同步瓶颈。

你必须进行性能分析以确保将注意力集中在正确的地方。


2
逆直觉是当今很多事情的方式。 十年前我了解的优化方法都已不再适用。 分支预测是非常重要的理解内容。 - DarenW

6

还有其他方法可以加速吗?

D在实现和编译器方面表现良好。由于您没有解释使用的光线追踪方法和优化,我无法提供更多帮助。

接下来,需要对程序进行时间分析,并在汇编中重新编写最常用或影响性能最大的最慢代码。

更一般地,可以查看以下问题中的资源:

我真的很喜欢使用显卡(一个大规模并行计算机)来完成部分工作的想法。

此站点上还有许多其他与光线追踪相关的资源,其中一些列在此问题边栏中,大多数可以在光线追踪标签中找到。


我试图避免使用汇编语言 :)Ray hits是惰性评估的,并且执行基本场景图优化(将平移合并到旋转矩阵中,诸如此类)。时间分析表明,ray/sphere和ray/plane代码占用了大部分时间,但是我真的不认为我能比GDC做得更好。 - FeepingCreature

3
每隔一个像素进行光线追踪。通过插值获取中间的颜色。如果颜色变化很大(你在物体的边缘),则在中间位置进行光线追踪。这是一种欺骗行为,但在简单场景中,可以将性能提高近一倍,同时牺牲一些图像质量。
在GPU上渲染场景,然后重新加载。这将以GPU速度给出第一个光线/场景碰撞。如果场景中没有许多反射表面,则大部分工作都是普通的渲染。不幸的是,在GPU上渲染CSG并非完全直观。
阅读PovRay源代码以获取灵感。 :)

3

我完全不了解D语言,所以无法查看代码并找到特定的优化方法,但我可以就一般情况发表意见。

这真的取决于您的要求。最简单的优化之一就是减少任何特定光线可以跟随的反射/折射次数,但这样您就开始失去“完美结果”了。

光线追踪也是一个"尴尬的并行问题",因此如果您拥有资源(例如多核处理器),您可以考虑并行计算多个像素。

除此之外,您可能只能进行分析并找出具体需要优化的部分。是交点检测吗?然后致力于优化该代码,依此类推。


它已经高度并行化了 :) 问题是,初步的分析似乎表明射线/球体和射线/平面碰撞测试用时最长,我不确定该怎么办。我已经在预先计算所有可能计算的东西了。 - FeepingCreature

3

一些建议。

  • 使用边界对象进行快速失败。
  • 在第一步中对场景进行投影(就像常见的图形卡一样),并仅对光线计算使用光线追踪。
  • 并行化代码。

2
首先,你必须确保使用非常快的算法(实现它们可能会很痛苦,但你想做什么、想走多远以及速度应该有多快,这是一种权衡)。
以下是我的一些提示: - 不要使用邮箱技术,因为在实际体系结构中由于计数开销,它们不会很好地扩展。 - 不要使用BSP / Octtrees,它们相对较慢。 - 不要将GPU用于光线追踪,它对于像反射、阴影、折射和光子映射等高级效果来说太慢了(我只用它进行着色,但这是我的个人见解)。
对于完整的静态场景,kd-树是无与伦比的,对于动态场景,有一些聪明的算法可以在四核上很好地扩展(我不确定性能是否超过此范围)。
当然,为了获得真正良好的性能,您需要使用非常多的SSE代码(当然不能有太多的跳转),但是对于“不太好”的性能(我在此谈论大约10-15%),编译器内置函数足以实现您的SSE代码。
以下是我谈到的一些算法的一些不错的论文:
"快速射线/轴对齐边界框 - 使用射线斜率进行重叠测试" (非常快,非常好的可并行化(SSE)AABB-射线碰撞检测)(请注意,论文中的代码并不是全部代码,只需在Google中搜索论文标题即可找到它)

http://graphics.tu-bs.de/publications/Eisemann07RS.pdf

“使用动态边界体层次结构对可变形场景进行光线追踪”

http://www.sci.utah.edu/~wald/Publications/2007///BVH/download//togbvh.pdf

如果您知道上述算法的工作原理,那么这个算法就更加出色:“在动态场景中使用预计算的三角形簇以加速光线跟踪”。

http://garanzha.com/Documents/UPTC-ART-DS-8-600dpi.pdf

我还使用了普吕克测试来快速确定是否击中多边形(不是非常精确,但是你不能拥有所有东西),在SSE及以上的情况下效果非常好。
因此,我的结论是,有关光线追踪的许多伟大论文与许多主题相关(如何构建快速、高效的树和如何着色(BRDF模型)等等),这是一个真正令人惊奇和有趣的“实验”领域,但你需要有很多空闲时间,因为它非常复杂但有趣。

0

我的第一个问题是,您是试图优化单个静态屏幕的追踪,还是要优化多个屏幕的追踪以计算动画?

如果您想要计算连续帧的动画,则要进行优化。这涉及到许多新事物需要考虑和优化。


0

你可以:

  • 使用经过SAH优化的包围体层次结构...
  • ...最终使用数据包遍历,
  • 引入重要性采样,
  • 按Morton码排序访问瓦片以获得更好的缓存一致性,以及

还有很多其他方法 - 但这些是我能够立即想到的建议。更详细地说:

您可以基于统计数据构建优化的层次结构,以便在相交几何体时快速识别候选节点。在您的情况下,您将不得不将自动层次结构与建模层次结构相结合,即限制构建或最终克隆建模信息。

“数据包遍历”意味着您使用SIMD指令计算4个并行标量,每个标量都是自己的射线,用于遍历层次结构(通常是热点),以从硬件中挤取最大的性能。

您可以执行一些针对每条射线的统计信息,以控制采样率(发射的次级射线数量)基于其对结果像素颜色的贡献。

在瓦片上使用面积曲线可以减少像素之间的平均空间距离,从而降低您从缓存命中中受益的概率。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接