代码之家  ›  专栏  ›  技术社区  ›  fluffels

为什么我的光线跟踪器不能重现“山”的场景?

  •  2
  • fluffels  · 技术社区  · 16 年前

    我正在尝试渲染“mount”场景 Eric Haines' Standard Procedural Database (SPD) 但是折射部分就是不想合作。我已经尽了我所能去修复它。

    这一个是我的渲染(用瓦特公式):

    My render. http://www.philosoraptor.co.za/programming/SPD/mount_mine.png

    这是我使用“正常”公式进行的渲染:

    Normal formula. http://www.philosoraptor.co.za/programming/SPD/mount_mine_normal.png

    这是正确的渲染:

    Correct render. http://www.philosoraptor.co.za/programming/SPD/mount_haines.png

    正如你所看到的,只有几个错误,主要是在球体的极点附近。这让我觉得折射,或者是某种精度的误差是罪魁祸首。

    请注意,场景中实际上有4个球体,它们的NFF定义( s x_coord y_coord z_coord radius 是:

    s -0.8 0.8 1.20821 0.17
    s -0.661196 0.661196 0.930598 0.17
    s -0.749194 0.98961 0.930598 0.17
    s -0.98961 0.749194 0.930598 0.17
    

    也就是说,在前景中更明显的三个球体后面有第四个球体。在这三个球体之间的空隙中可以看到。

    这是第四个球体的图片:

    'Fourth' sphere. http://www.philosoraptor.co.za/programming/SPD/mount_sphere_2.png

    这是第一个球体的图片:

    'First' sphere. http://www.philosoraptor.co.za/programming/SPD/mount_sphere_1.png

    您会注意到我的版本和正确的版本中都缺少许多奇怪的地方。我们可以得出结论,这些效应是球体之间相互作用的结果,问题是哪种相互作用?

    我做错什么了?以下是我已经考虑过的一些潜在错误:

    • 折射矢量公式。

    据我所知,这是正确的。这是几个网站使用的相同公式,我亲自验证了推导过程。我是这样计算的:

    double sinI2 = eta * eta * (1.0f - cosI * cosI);

    Vector transmit = (v * eta) + (n * (eta * cosI - sqrt(1.0f - sinI2)));

    transmit = transmit.normalise();

    我在3D计算机图形学中找到了另一个公式,第三版是艾伦·瓦特。它更接近正确的图像:

    double etaSq = eta * eta;
    double sinI2 = etaSq * (1.0f - cosI * cosI);
    Vector transmit = (v * eta) + (n * (eta * cosI - (sqrt(1.0f - sinI2) / etaSq)));
    transmit = transmit.normalise();
    

    唯一的区别是我最后除以了eta^2。

    • 全内反射。

    我在剩余的交集代码之前使用以下条件对此进行了测试:

    if (sinI2 <= 1)
    
    • 预计到达时间的计算。

    对于这个问题,我使用类似堆栈的方法:

            /* Entering object. */
            if (r.normal.dot(r.dir) < 0)
            {
               double eta1 = r.iorStack.back();
               double eta2 = m.ior;
               eta = eta1 / eta2;
    
               r.iorStack.push_back(eta2);
            }
            /* Exiting object. */
            else
            {
               double eta1 = r.iorStack.back();
               r.iorStack.pop_back();
               double eta2 = r.iorStack.back();
    
               eta = eta1 / eta2;
            }
    

    如您所见,它将包含此光线的先前对象存储在堆栈中。退出代码时,会将当前IOR从堆栈中弹出并使用该IOR及其下的IOR来计算ETA。据我所知,这是最正确的方法。

    这适用于嵌套的传输对象。但是,它在 相交 传输对象。这里的问题是,您需要独立地为交叉点定义IOR,而NFF文件格式没有这样做。现在还不清楚“正确”的行动方针是什么。

    • 移动新光线的原点。

    新射线的原点必须沿传输路径轻微移动,这样它就不会与前一个点相交。

    p = r.intersection + transmit * 0.0001f;
    

    p += transmit * 0.01f;

    我试过把这个值变小(0.001f)和(0.0001f),但这使得球体看起来是实心的。我想这些值不会将光线移动到离上一个交点足够远的地方。

    编辑:这里的问题是反射代码在做同样的事情。因此,当一个物体具有反射性和折射性时,光线的原点就完全错了。

    • 光线反弹的量。

    我人为地把射线的反弹量限制在4。我试着把这个限制提高到10,但这并没有解决问题。

    • 法线。

    我很确定我正在正确计算球体的法线。我取交点,减去球体的中心,再除以半径。

    2 回复  |  直到 14 年前
        1
  •  3
  •   BCS    16 年前

    只是一个基于图像差异的猜测(并且不阅读其余问题)。在我看来,问题是球面背面的折射。你可能是:

    • 倒转:例如倒转(或不倒转)折射率。
    • 完全错过了?

    检查这一点的一种方法是通过一个几乎正对相机的立方体观察底座。如果折射是正确的,图片应该稍微偏移,否则不会改变。如果不正确,那么图片看起来会稍微倾斜。

        2
  •  0
  •   fluffels    14 年前

    一年多过去了,我终于知道这里发生了什么。头脑清醒。我完全偏离了公式。我现在用的是Heckbert的公式,我确信这是正确的,因为我自己用几何和离散数学证明了它。

    下面是正确的向量计算:

    double c1 = v.dot(n) * -1;
    double c1Sq = pow(c1, 2);
    
    /* Heckbert's formula requires eta to be eta2 / eta1, so I have to flip it here. */
    eta = 1 / eta;
    double etaSq = pow(eta, 2);
    
    if (etaSq + c1Sq >= 1)
    {
       Vector transmit = (v / eta) + (n / eta) * (c1 - sqrt(etaSq - 1 + c1Sq));
       transmit = transmit.normalise();
    ...
    }
    else
    {
       /* Total internal reflection. */
    }
    

    在上面的代码中,eta是eta1(光线来自的表面的IOR)在eta2(目标表面的IOR)上,v是入射光线,n是正常光线。

    还有一个问题,使问题更加混乱。我必须翻转 正常的 当退出一个对象时(这是显而易见的——我错过了它,因为其他错误掩盖了它)。

    最后,我的视线算法(确定表面是否被点光源照亮)没有正确地穿过透明表面。

    所以现在我的图像排列正确:)