光线追踪:反射与折射
光线追踪的另一个优点是,通过扩展光线传播的思想,我们可以非常轻松地模拟反射和折射等效果,这在模拟玻璃材料或镜面时非常方便。 在 1979 年题为“改进的阴影显示照明模型”的论文中,Turner Whitted 首次描述了如何扩展 Appel 的光线追踪算法以实现更高级的渲染。 Whitted的想法扩展了阿佩尔的射线模型,纳入了反射和折射的计算。
在光学中,反射(reflection)和折射(refraction)是众所周知的现象。 尽管后面的课程专门讨论反射和折射,但我们将研究模拟它们需要什么。 我们将以具有折射和反射特性的玻璃球为例。 只要我们知道光线与球相交的方向,就很容易计算出它会发生什么。
反射和折射方向均基于交点处的法线和入射光线(主光线)的方向。 为了计算折射方向,我们还需要指定材料的折射率。 尽管我们之前说过光线沿直线传播,但我们可以将折射视为光线弯曲。 当光子撞击不同介质(因此折射率不同)的物体时,其方向会发生变化。 稍后将更深入地讨论其科学性。 只要我们记住这两种效应取决于法线矢量和入射光线方向,并且折射取决于材料的折射率,我们就可以继续前进了。
同样,我们还必须意识到像玻璃球这样的物体同时具有反射性和折射性。 我们需要为表面上的给定点计算两者,但我们如何混合它们呢? 我们是否将 50% 的反射结果与 50% 的折射结果混合在一起? 不幸的是,事情比这更复杂。 值的混合取决于主光线(或观察方向)与物体法线和折射率之间的角度。 然而,对我们来说幸运的是,有一个方程可以精确计算每种物质应该如何混合。 该方程称为菲涅耳方程(Fresnel equation)。 为了保持简洁,我们现在需要知道的是它存在并且将有助于确定混合值。
让我们回顾一下。 Whitted 算法如何工作? 我们从眼睛以及与场景中物体最近的交叉点(如果有)发射主光线(primary ray)。 如果光线击中非漫反射或不透明的物体,我们必须做额外的计算工作。 要计算玻璃球上该点的最终颜色,你需要计算反射颜色和折射颜色并将它们混合。 请记住,我们分三步完成。 计算反射颜色,计算折射颜色,然后应用菲涅尔方程。
首先,我们计算反射方向。 为此,我们需要两项:交点处的法线和主光线的方向。 一旦我们获得了反射方向,我们就会发射一条新的光线。 回到我们以前的例子,假设反射光线射到红色球体上。 使用阿佩尔算法,我们通过向光发射阴影光线来确定有多少光到达红色球体上的该点。 这将获得颜色(如果有阴影则为黑色)乘以光强度并返回到玻璃球的表面。
现在,我们对折射做同样的事情。 由于光线穿过玻璃球,因此被称为透射光线(光从球体的一侧传播到另一侧;它被透射)。 为了计算传输方向,我们需要命中点的法线、主光线方向和材料的折射率(在本例中,玻璃材料的折射率可能约为 1.5)。 计算出新方向后,折射光线继续传播到玻璃球的另一侧。 同样,由于介质发生了变化,光线又被折射了一次。 正如你在相邻图像中所看到的,当光线进入和离开玻璃物体时,光线的方向会发生变化。 每次改变介质时都会发生折射,并且光线从其中射出的一种介质和进入的两种介质具有不同的折射率。 空气的折射率非常接近1,玻璃的折射率在1.5左右。 为了达到效果,折射使光线稍微弯曲。 当透过或观察不同折射率的物体时,这个过程使得物体看起来发生了移动。 让我们想象一下,当折射光线离开玻璃球时,它击中了一个绿色球体。 我们计算了绿色球体和折射光线之间交叉点的局部照明(通过拍摄阴影光线)。 然后将颜色(如果有阴影则为黑色)乘以光强度并返回到玻璃球的表面
最后,我们计算菲涅尔方程。 我们需要玻璃球的折射率、主光线与命中点法线之间的角度。 使用点积(我们将在稍后解释),菲涅尔方程返回两个混合值。
这里有一些伪代码来强化它的工作原理:
// compute reflection color
color reflectionCol = computeReflectionColor();
// compute refraction color
color refractionCol = computeRefractionColor();
float Kr; // reflection mix value
float Kt; // refraction mix value
fresnel(refractiveIndex, normalHit, primaryRayDirection, &Kr, &Kt);
// mix the two colors. Note that Kt = 1 - Kr
glassBallColorAtHit = Kr * reflectionColor + Kt * refractionColor;
在上面的代码中,我们在注释中说明 Kt = 1 - Kr
。 换句话说, Kr + Kt = 1
。这是因为,在自然界中,光无法被创造或毁灭。 因此,如果一些入射光被反射,那么剩下的入射光(未被反射的部分)必然会被折射。 如果计算反射光和折射光的总和,则它等于入射光的量。 通常,菲涅尔方程为我们提供了 Kr 和 Kt 的值(如果它做得正确,它们的总和应等于 1),因此你可以直接使用函数返回的值。 然而,如果我们只有其中之一就足够了。 如果有 Kr,就可以获得 Kt= (1 - Kr)
。 如果有 Kt,您可以获得 Kr =(1 - Kt)
。
该算法的最后一个优点是它是递归的(这在某种程度上也是一个诅咒!)。 在我们目前研究的情况中,反射光线照射到红色、不透明的球体,折射光线照射到绿色、不透明、漫射的球体。 然而,我们会想象红色和绿色的球体也是玻璃球。 为了找到反射和折射光线返回的颜色,我们必须对原始玻璃球使用的红色和绿色球体执行相同的过程:也就是说,将更多的反射和折射光线射入场景中。 这是光线追踪算法的一个缺点,有时会让人头疼。 想象一下,我们的相机位于一个只有反光面的盒子里。 理论上,光线被捕获并将继续从盒子的壁上无休止地反弹(或直到您停止模拟)。 因此,我们必须设置一个任意限制,防止光线相互作用,从而无限递归。 每次光线被反射或折射时,其深度都会增加。 当光线深度大于最大递归深度时,我们停止递归过程。 你的图像不一定看起来完全准确,但有一个近似结果总比没有结果好。
原文链接:Adding Reflection and Refraction
BimAnt翻译整理,转载请标明出处