基于光线追踪的渲染算法实现

本文翻译自Scratchapixel 3.0[1],是一个关于计算机图形学的系统性的学习教程。
如果有误,欢迎在评论区讨论。
翻译:iOS成长指北


我们已经涵盖了所有需要说的内容!我们现在准备写我们的第一个光线追踪器。你现在应该能够猜到光线追踪算法是如何工作的了。

首先,注意到自然界中光的传播只是从光源发出无数条射线,反弹直到它们撞到我们眼睛的表面。因此,光线追踪是优雅的,因为它直接基于我们周围发生的事情。除了它按照相反的顺序跟随光线的路径外,它是一个完美的自然模拟器。

光线追踪算法使用由像素组成的图像。对于图片中的每个像素,它向场景中发射一个主射线。该主射线的方向是通过从眼睛到该像素中心的线追踪得到的。一旦我们设置了该主射线的方向,我们检查场景中的每个对象,看它们是否与任何对象相交。在某些情况下,主射线将与多个对象相交。当这种情况发生时,我们选择离眼睛最近的交点所在的对象。然后,我们从交点向光源发射一个阴影射线(图 1)。

图1:我们通过像素中心发射一个主射线来检查可能的对象相交。当我们找到一个对象时,我们发射一个阴影射线来确定该点是否被照亮或在阴影中。

如果这条射线在到达光源的路上没有与其他对象相交,那么击中点就被照亮了。如果它与另一个对象相交,那个对象就会对它产生阴影(图 2)。

图2:小球在大球上投下了阴影。阴影射线在到达光源之前与小球相交。

如果我们对每个像素重复这个操作,我们就可以得到我们三维场景的二维表示(图 3)。

图3:为了渲染一个帧,我们为每个帧缓冲区的像素发射一个主射线。

以下是算法的伪代码实现:

for (int j = 0; j < imageHeight; ++j) {
    for (int i = 0; i < imageWidth; ++i) {
        // compute primary ray direction
        Ray primRay;
        computePrimRay(i, j, &primRay);
        // shoot prim ray in the scene and search for the intersection
        Point pHit;
        Normal nHit;
        float minDist = INFINITY;
        Object object = NULL;
        for (int k = 0; k < objects.size(); ++k) {
            if (Intersect(objects[k], primRay, &pHit, &nHit)) {
                float distance = Distance(eyePosition, pHit);
                if (distance < minDistance) {
                    object = objects[k];
                    minDistance = distance;  //update min distance
                }
            }
        }
        if (object != NULL) {
            // compute illumination
            Ray shadowRay;
            shadowRay.direction = lightPosition - pHit;
            bool isShadow = false;
            for (int k = 0; k < objects.size(); ++k) {
                if (Intersect(objects[k], shadowRay)) {
                    isInShadow = true;
                    break;
                }
            }
        }
        if (!isInShadow)
            pixels[i][j] = object->color * light.brightness;
        else
            pixels[i][j] = 0;
    }
}

正如我们所看到的,光线追踪的美妙之处在于它只需要几行代码;一个基本的光线追踪器只需要 200 行代码。与其他算法(如扫描线渲染器)不同,光线追踪的实现需要很少的努力。

Arthur Appel 在 1969 年的一篇名为“一些用于给固体着色的机器渲染技术”的论文中首次描述了这种技术。那么,如果这个算法如此出色,为什么它没有取代所有其他渲染算法呢?主要原因在于速度,当时(甚至今天在某种程度上)是这样的。正如 Appel 在他的论文中所提到的:

❝ 这种方法非常耗时,通常需要比线框图绘制多几千倍的计算时间才能获得有益的结果。其中约有一半时间用于确定投影和场景之间的点对点对应关系。 ❞

换句话说,它很慢(正如 Kajiya 所说的,他是所有计算机图形学历史上最有影响力的研究人员之一:“光线追踪不慢,计算机慢”)。查找光线和几何图形之间的交点非常耗时。几十年来,算法的速度一直是光线追踪的主要缺点。然而,随着计算机变得更快,它越来越不是问题。尽管仍有一件事必须说:与其他技术(如 z 缓冲算法)相比,光线追踪仍然要慢得多。然而,随着今天的快速计算机,我们可以在几分钟内计算出以前需要一个小时才能完成的帧。实时和交互式光线追踪器是一个热门话题。

总之,重要的是要记住,渲染例程可以被认为是两个单独的过程。一步确定一个物体表面上的点是否从特定的像素可见(可见性部分),第二步着色该点(着色部分)。不幸的是,这两个步骤都需要昂贵和耗时的光线-几何交点测试。这个算法是优雅而强大的,但是它迫使我们在渲染时间和精度之间进行权衡。自 Appel 发表论文以来,已经进行了大量研究来加速光线-物体交点例程。随着计算机变得更加强大并结合这些加速技术,光线追踪成为了日常生产环境中可用的方法,并且是大多数渲染离线软件所使用的事实标准。视频游戏引擎仍在使用光栅化算法。然而,随着 GPU 加速光线追踪技术(2017-2018)和 RTX 技术的最近出现,实时光线追踪也在可及范围内。虽然一些视频游戏已经提供了可以打开光线追踪的模式,但仅限于简单的效果,如清晰的反射和阴影。



本文转自:iOS成长指北,转载此文目的在于传递更多信息,版权归原作者所有。如不支持转载,请联系小编demi@eetrend.com删除。

最新文章