光线追踪中的折射?

Posted

技术标签:

【中文标题】光线追踪中的折射?【英文标题】:Refraction in Raytracing? 【发布时间】:2014-11-23 02:10:48 【问题描述】:

我又开始研究我的光线追踪器了。我添加了反射和多线程支持。目前我正在努力添加折射,但它只工作了一半。

如您所见,有一个中心球体(没有镜面高光)、一个反射球体(右侧)和一个折射球体(左侧)。我对反射很满意,它看起来确实很好。对于折射它有点工作......光线被折射并且球体的所有阴影在球体中可见(折射率为 1.4),但有一个外部黑色环。

编辑:当我增加球体的折射率时,显然黑环变大,因此球体变小。相反,当降低折射率时,球体变大,黑色环变小……直到折射率设置为 1,环完全消失。 IOR = 1.9 IOR = 1.1 IOR = 1.00001 有趣的是,在 IOR = 1 时,球体失去透明度并变成白色。

我想我涵盖了全内反射,这不是这里的问题。

现在代码: 我将operator | 用于点积,因此(vec|vec) 是点积,operator ~ 用于反转向量。对象,包括光照和球体都存储在Object **objects; 中。 光线追踪功能

Colour raytrace(const Ray &r, const int &depth)

    //first find the nearest intersection of a ray with an object
    Colour finalColour = skyBlue *(r.getDirection()|Vector(0,0,-1)) * SKY_FACTOR;
    double t, t_min = INFINITY;
    int index_nearObj = -1;
    for(int i = 0; i < objSize; i++)
    
        if(!dynamic_cast<Light *>(objects[i]))//skip light src
        
            t = objects[i]->findParam(r);
            if(t > 0 && t < t_min)
            
                t_min = t;
                index_nearObj = i;
            
        
    
    //no intersection
    if(index_nearObj < 0)
        return finalColour;

    Vector intersect = r.getOrigin() + r.getDirection()*t_min;
    Vector normal = objects[index_nearObj]->NormalAtIntersect(intersect);
    Colour objectColor = objects[index_nearObj]->getColor();
    Ray rRefl, rRefr; //reflected and refracted Ray
    Colour refl = finalColour, refr = finalColour; //reflected and refracted colours
    double reflectance = 0, transmittance = 0;

    if(objects[index_nearObj]->isReflective() && depth < MAX_TRACE_DEPTH)
    
        //handle reflection
        rRefl = objects[index_nearObj]->calcReflectingRay(r, intersect, normal);
        refl = raytrace(rRefl, depth + 1);
        reflectance = 1;
    

    if(objects[index_nearObj]->isRefractive() && depth < MAX_TRACE_DEPTH)
    
        //handle transmission
        rRefr = objects[index_nearObj]->calcRefractingRay(r, intersect, normal, reflectance, transmittance);
        refr = raytrace(rRefr, depth + 1);
    

    Ray rShadow; //shadow ray
    bool shadowed;
    double t_light = -1;

    Colour localColour;
    Vector tmpv;

    //get material properties
    double ka = 0.2; //ambient coefficient
    double kd; //diffuse coefficient
    double ks; //specular coefficient

    Colour ambient = ka * objectColor; //ambient component
    Colour diffuse, specular;
    double brightness;
    localColour = ambient;
    //look if the object is in shadow or light
    //do this by casting a ray from the obj and
    // check if there is an intersection with another obj
    for(int i = 0; i < objSize; i++)
    
        if(dynamic_cast<Light *>(objects[i])) //if object is a light
        
            //for each light
            shadowed = false;
            //create Ray to light
            tmpv = objects[i]->getPosition() - intersect;
            rShadow = Ray(intersect  + (!tmpv) * BIAS, tmpv);
            t_light = objects[i]->findParam(rShadow);

            if(t_light < 0) //no imtersect, which is quite impossible
                continue;

            //then we check if that Ray intersects one object that is not a light
            for(int j = 0; j < objSize; j++)
            
                    if(!dynamic_cast<Light *>(objects[j]) && j != index_nearObj)//if obj is not a light
                    
                        t = objects[j]->findParam(rShadow);
                        //if it is smaller we know the light is behind the object
                        //--> shadowed by this light
                        if (t >= 0 && t < t_light)
                        
                            // Set the flag and stop the cycle
                            shadowed = true;
                            break;
                        
                    
            

            if(!shadowed)
            
                rRefl = objects[index_nearObj]->calcReflectingRay(rShadow, intersect, normal);
                //reflected ray from ligh src, for ks
                kd = maximum(0.0, (normal|rShadow.getDirection()));
                if(objects[index_nearObj]->getShiny() <= 0)
                    ks = 0;
                else
                    ks = pow(maximum(0.0, (r.getDirection()|rRefl.getDirection())), objects[index_nearObj]->getShiny());
                diffuse = kd * objectColor;// * objects[i]->getColour();
                specular = ks * objects[i]->getColor();
                brightness = 1 /(1 + t_light * DISTANCE_DEPENDENCY_LIGHT);
                localColour += brightness * (diffuse + specular);
            
        
    
    finalColour = localColour + (transmittance * refr + reflectance * refl);
    return finalColour;

现在计算折射光线的函数,我使用了几个不同的站点作为资源,每个站点都有相似的算法。这是迄今为止我能做的最好的。这可能只是我没有看到的一个小细节......

Ray Sphere::calcRefractingRay(const Ray &r, const Vector &intersection,Vector &normal, double & refl, double &trans)const

    double n1, n2, n;
    double cosI = (r.getDirection()|normal);
    if(cosI > 0.0)
    
        n1 = 1.0;
        n2 = getRefrIndex();
        normal = ~normal;//invert
    
    else
    
        n1 = getRefrIndex();
        n2 = 1.0;
        cosI = -cosI;
    
    n = n1/n2;
    double sinT2 = n*n * (1.0 - cosI * cosI);
    double cosT = sqrt(1.0 - sinT2);
    //fresnel equations
    double rn = (n1 * cosI - n2 * cosT)/(n1 * cosI + n2 * cosT);
    double rt = (n2 * cosI - n1 * cosT)/(n2 * cosI + n2 * cosT);
    rn *= rn;
    rt *= rt;
    refl = (rn + rt)*0.5;
    trans = 1.0 - refl;
    if(n == 1.0)
        return r;
    if(cosT*cosT < 0.0)//tot inner refl
    
        refl = 1;
        trans = 0;
        return calcReflectingRay(r, intersection, normal);
    
    Vector dir = n * r.getDirection() + (n * cosI - cosT)*normal;
    return Ray(intersection + dir * BIAS, dir);

编辑:我也改变了周围的折射率。来自

    if(cosI > 0.0)
    
        n1 = 1.0;
        n2 = getRefrIndex();
        normal = ~normal;
    
    else
    
        n1 = getRefrIndex();
        n2 = 1.0;
        cosI = -cosI;
    

if(cosI > 0.0)

    n1 = getRefrIndex();
    n2 = 1.0;
    normal = ~normal;

else

    n1 = 1.0;
    n2 = getRefrIndex();
    cosI = -cosI;

然后我得到了这个,几乎相同(仍然颠倒),折射率为 1! 以及反射计算:

Ray Sphere::calcReflectingRay(const Ray &r, const Vector &intersection, const Vector &normal)const

    Vector rdir = r.getDirection();
    Vector dir = rdir - 2 * (rdir|normal) * normal;
    return Ray(intersection + dir*BIAS, dir);
    //the Ray constructor automatically normalizes directions

所以我的问题是:如何修复外部黑色圆圈?哪个版本是正确的?

非常感谢您的帮助:)

这是在 Linux 上使用 g++ 4.8.2 编译的。

【问题讨论】:

切线:不要滥用运算符重载! 您的第二个版本看起来是正确的——预计会产生倒置图像的折射。 为了反驳@OliverCharlesworth 的评论,我想说,operator| 的使用使 this 代码更具可读性,因此它不是滥用 here我>。最好注意优先级,并在任何地方使用() 正如@JerryCoffin 所说:需要倒置的图像。看看这个相关的话题,这是非常有说明性的答案:***.com/questions/13386003/… 我明白了……我还是很好奇,有没有关于黑环的解释? 【参考方案1】:

警告:以下是猜测,不是确定的。我必须更详细地查看代码以确定发生了什么以及为什么。

也就是说,在我看来,您的原始代码基本上是在模拟凹透镜而不是凸透镜。

凸透镜基本上是一个放大镜,将来自相对较小区域的光线聚焦在一个平面上:

这也说明了为什么更正后的代码会显示颠倒的图像。从一侧顶部射出的光线投射到另一侧底部(反之亦然)。

回到凹透镜:凹透镜是一种缩小透镜,可从透镜前面显示广角图像:

如果你看一下这里的右下角,它就会显示出我怀疑的问题:尤其是在高折射率的情况下,试图进入镜头的光线与镜头本身的边缘相交。对于比这更宽的所有角度,您通常会看到一个黑色环,因为镜头的前缘起到了遮光作用,以防止光线进入。

增加折射率会增加黑色环的宽度,因为光线更弯曲,因此边缘处的较大部分与镜片的外边缘相交。

如果您关心他们如何通过广角相机镜头避免这种情况,通常的方法是使用弯月形镜头,至少对于前部元素:

这不是灵丹妙药,但至少可以防止入射光线与前透镜元件的外缘相交。取决于镜头需要覆盖的确切角度宽,弯月面的激进程度通常会比这要小得多(在某些情况下,它会是平凹面),但你明白了。

最后警告:当然,所有这些都是手绘的,仅用于提供一般概念,而不是(例如)反映任何特定镜头的设计,具有任何特定折射率的元素等。

【讨论】:

【参考方案2】:

我在使用光线追踪器时也偶然发现了这个确切的问题。 @lightxbulb 关于规范化光线方向向量的评论为我解决了这个问题。

首先,保留计算折射指数的代码在编辑之前。换句话说,您应该在渲染中看到那些黑环。

然后,在计算cosIcalcRefractingRay 函数中,使用normalize(r.getDirection())normal 的点积。目前您正在使用r.getDirection()normal 的点积。

其次,当您计算折射光线方向dir 时,请使用normalize(r.getDirection()) 而不是r.getDirection()。同样,您当前正在使用 r.getDirection() 在您的计算中。

此外,您检查全内反射的方式也存在问题。 实际计算平方根之前,您应该检查您取 (1.0 - sinT2) 平方根的项是否为非负数。

希望有帮助!

【讨论】:

以上是关于光线追踪中的折射?的主要内容,如果未能解决你的问题,请参考以下文章

一个光线追踪demo

Ray Tracing in One Weekend 超详解 光线追踪1-7 Dielectric 半径为负,实心球体镂空技巧

2 追踪光线=》2.7 透射光线

DXR 实时光线追踪技术概览

C中的光线追踪器,光线平面相交

如何将光线追踪器中的光线从世界空间反向旋转到对象空间