我写了一个基本的光线追踪器来跟踪屏幕空间。每个片段都有一个相关的像素半径。当一条射线从dir中挤出时撞击几何体,我计算撞击的法线向量,并将其与另外四条射线组合。在伪代码中:distanceeyeN
def distance := shortestDistanceToSurface(sdf, eye, dir, pixelRadius)
def p := eye + dir * distance
def N := estimateNormal(sdf, p)
def glance := distance * glsl.dot(dir, N)
def dx := (dirPX / glsl.dot(dirPX, N) - dirNX / glsl.dot(dirNX, N)) * glance
def dy := (dirPY / glsl.dot(dirPY, N) - dirNY / glsl.dot(dirNY, N)) * glance
这里,dirPX、dirNX、dirPY和dirNY是在屏幕空间中的四个方向中的每一个方向上偏移像素半径的光线dir,但仍然瞄准相同的参考点。这给出了dx和dy,它们是像素上的偏导数,表示当光线穿过屏幕空间时,命中沿着几何体表面移动的速率。
因为我跟踪屏幕空间,所以我可以使用预先过滤的采样器,正如 Inigo Quilez 所讨论的那样。他们看起来很棒。但是,现在我想添加反射(和折射),这意味着我需要递归,而且我不确定如何计算这些光线并跟踪屏幕空间。
基本问题是,为了弄清楚几何图形上某个位置反射的光是什么颜色,我不仅需要采集点样本,还需要检查反射的整个屏幕空间。我可以使用偏导数在几何上给我四个新点,它们近似于一个椭圆,这是原始像素从屏幕上的投影:
def px := dx * pixelRadius
def py := dy * pixelRadius
def pPX := p + px
def pNX := p - px
def pPY := p + py
def pNY := p - py
我可以通过将椭圆弄成圆形来计算近似的像素半径。我知道这会破坏某些理想的各向异性模糊,但是没有作弊的光线追踪器是什么?
def nextRadius := (glsl.length(dx) * glsl.length(dy)).squareRoot() * pixelRadius
但是,我不知道在哪里将这些点反映到几何中;我不知道将它们的光线聚焦在哪里。如果我必须选择焦点,那么它将是任意的,并且取决于几何体反映其自身图像的位置,那么这可能会任意模糊或摩尔反射图像。
我需要取二阶偏导数吗?我可以像一阶导数一样对它们进行近似,然后我可以使用它们来调整法线N并进行细微的变化,就像使用 hit 一样p。然后法线引导椭圆的焦点,并将其映射到近似圆锥截面。我担心三件事:
- 我担心做一些额外的向量加法和乘法的成本,这可能可以忽略不计;
- 还有关于精度的损失,在做这些廉价的衍生品时已经很糟糕了,在多次反射中是否会有太大的损失;
- 最后,我应该如何处理屏幕空间爆炸的情况;当我有一个镜像球体时,我应该如何对反射空间的大楔形进行采样,例如将棋盘图案平均成灰色?
虽然不用担心,但我根本不知道如何获取四个向量并快速为它们拟合一个令人信服的锥体,但这可能仅仅是花一些时间在白板上做代数的问题。
编辑:在 John Amanatides 1984 年的论文Ray Tracing with Cones中,确实计算了曲率信息,并用于将估计的锥体拟合到反射光线上。在 Homan Igehy 1999 年的论文Tracing Ray Differentials中,仅使用一阶导数,而明确忽略了二阶导数。
也许还有其他选择?我已经尝试在一次反射后丢弃像素半径并仅采集点样本,它们看起来很糟糕,有很多混叠和噪声。也许存在可以基于每种材料计算的视场或景深近似值。像往常一样,多重采样可以提供帮助,但我想要一个分析解决方案,这样我就不会不必要地浪费这么多 CPU。
(sdf是一个有符号距离函数,我正在做球体追踪;同样的程序既计算距离又计算法线。glsl是 GLSL 标准库。)
