光线追踪(RayTracing)算法

1、Forward Tracing

假设有一个每次只发射一个光子的光源,光子从光源发出并沿着直线路径行进,直至撞击到物体表面,忽略光子的吸收,该光子会以随机的方向反射。如果光子撞击到我们的眼睛表面,则我们会看到光子被反射的点。

现在从计算机图形的角度来看待这种情况。首先,我们用像素组成的平面代替我们的眼睛。在这种情况下,发射的光子将撞击图形平面上许多像素的一个,并将该点的亮度增加到大于零的值。重复多次直到所有的像素被调整,创建一个计算机生成的图像。这种技术称为前向光线追踪(Forward Tracing),因为我们是沿着光子从光源向观察者的前进的路径。

这种技术在计算机中模拟光子与物体相互作用是不太现实的,因为在实际中反射的光子击中眼睛表面的可能性是非常非常低的,我们必须投射大量的光子才能找到一个能够引起眼睛注意的。此外,我们也不能保证物体的表面被光子完全覆盖,这是这项技术的主要缺点。

2、Backward Tracing

并追踪从眼睛进入到场景中的光线。光线照到一个物体时,我们可以通过将另一条光线(称为光线或阴影光线)从击中点投射到场景的光线,得到它所接受到的光子数量。这个“光线”有的时候会被另一个物体阻挡,这意味着我们原来的撞击点在阴影中,没有获得任何照明。

3.算法实现

(1)光线追踪算法采用由像素组成的图像。对于图像中的每个像素,它将主光线投射到场景中。该主光线的方向是通过追踪从眼睛到像素中心线获得的。一旦我们确定了主射线的方向,我们就开始检查场景中的每个对象,看它是否与其中的任何一个相交。当发生主射线与多个对象相交的情况时,我们选择交点离眼睛最近的物体。

(2)我们从交叉点向光源投射阴影射线。如果这条特定的光线在通往光源的路上不与某个物体相交,那么这个点就被照亮了。

(3)如果它与另一个物体相交,则该物体在其上投下阴影。

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

4.伪代码

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 intersection
        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; 
    } 
} 

 5.加入反射和折射

在光学中,反射和折射是总所周知的现象。反射和折射分向都是基于相交点处的法线和入射光线(主光线)的方向。为了计算折射方向,我们还需指定材料的折射率。

光线追踪(RayTracing)算法_第1张图片

加入反射折射后,进行以下三个步骤:

(1)计算反射:我们需要交点处的法线和主光线的方向。一旦我们获得了反射方向,我们就朝这个方向发射新的光线。我们假设反射光线撞击了红色球体,通过向光线投射阴影射线来找出到达红色球体上的那个点的光线多少。这会得到一种颜色(如果是阴影,则为黑色),然后乘以光强并返回到玻璃球的表面。

(2)计算折射:我们需要在知道击中点的法线,主射线方向和材料的折射率。当光线进入并离开玻璃物体时,光线的方向会改变。当折射的光线离开玻璃球时,它会碰到一个绿色的球体。在那里,我们再次计算绿色球体和折射射线之间交点处的局部照明(通过拍摄阴影射线)。然后,将颜色(如果被遮挡,则为黑色)乘以光强并返回到玻璃球的表面。

(3)应用菲涅尔方程:我们需要玻璃球的折射率,主光线的角度,以及击中点的法线。使用点积,菲涅耳方程返回两个混合值。

这种算法的美妙之处在于它是递归的。迄今为止,在我们研究过的情况下,反射光线照射到一个红色的、不透明的球体上,而折射光线照射到一个绿色的、不透明的和漫射的球体上。但是,我们会想象红色和绿色的球体也是玻璃球。为了找到由反射和折射光线返回的颜色,我们必须按照与原始玻璃球一起使用的红色和绿色球体的相同过程。这是光线追踪算法的一个严重缺陷。想象一下,我们的相机是在一个只有反射面的盒子里。从理论上讲,光线被困住了,并且会持续不断地从箱子的墙壁反弹(或者直到你停止模拟)。出于这个原因,我们必须设置一个任意的限制值,从而防止光线相互作用导致的无限递归。每当光线反射或折射时,其深度都会增加。当光线深度大于最大递归深度时,我们就停止递归过程。

伪代码

// 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
color glassBallColorAtHit = Kr * reflectionColor + (1-Kr) * refractionColor; 

转自:https://blog.csdn.net/hmbxsy/article/details/80509876

你可能感兴趣的:(Computer,Graphics)