首页 > 解决方案 > 在光线追踪器中实现折射后,透明球体大部分是黑色的

问题描述

注意:我已经编辑了我的代码。请参见下面的分隔线。

我在我的(相当基本的)光线追踪器中实现折射,用 C++ 编写。我一直在关注(1)(2)

我得到下面的结果。为什么球的中心是黑色的?

两个不透明球体和一个透明球体。

中心球体的透射系数为0.9,反射系数为0.1。它的折射率是1.5,它被放置在1.5远离相机的单位。其他两个球体仅使用漫反射照明,没有反射/折射组件。我将这两个不同颜色的球体放置在透明球体的后面和前面,以确保我看不到反射而不是透射。

我已经将背景颜色(当来自相机的光线不与任何物体相交时获得的颜色)设置为黑色以外的颜色,因此球体的中心不仅仅是背景颜色。

我还没有实现菲涅耳效应。

我的跟踪函数看起来像这样(逐字复制,为简洁起见省略了一些部分):

bool isInside(Vec3f rayDirection, Vec3f intersectionNormal) {
    return dot(rayDirection, intersectionNormal) > 0;
}

Vec3f trace(Vec3f origin, Vec3f ray, int depth) {
    // (1) Find object intersection
    std::shared_ptr<SceneObject> intersectionObject = ...;
    // (2) Compute diffuse and ambient color contribution
    Vec3f color = ...;

    bool isTotalInternalReflection = false;
    if (intersectionObject->mTransmission > 0 && depth < MAX_DEPTH) {
        Vec3f transmissionDirection = refractionDir(
            ray,
            normal,
            1.5f,
            isTotalInternalReflection
        );
        if (!isTotalInternalReflection) {
            float bias = 1e-4 * (isInside(ray, normal) ? -1 : 1);
            Vec3f transmissionColor = trace(
                add(intersection, multiply(normal, bias)),
                transmissionDirection,
                depth + 1
            );
            color = add(
                color,
                multiply(transmissionColor, intersectionObject->mTransmission)
            );
        }
    }

    if (intersectionObject->mSpecular > 0 && depth < MAX_DEPTH) {
        Vec3f reflectionDirection = computeReflectionDirection(ray, normal);
        Vec3f reflectionColor = trace(
            add(intersection, multiply(normal, 1e-5)),
            reflectionDirection,
            depth + 1
        );
        float intensity = intersectionObject->mSpecular;
        if (isTotalInternalReflection) {
            intensity += intersectionObject->mTransmission;
        }
        color = add(
            color,
            multiply(reflectionColor, intensity)
        );
    }

    return truncate(color, 1);
}

如果物体是透明的,那么它会计算透射光线的方向并递归地追踪它,除非折射导致全内反射。在这种情况下,将透射分量添加到反射分量,因此颜色将是跟踪反射颜色的 100%。

当递归追踪透射光线时,我在法线方向(如果在内部则反转)向交点添加一点偏差。如果我不这样做,那么我会得到这个结果:

结果在交点上没有偏差

传输光线方向的计算在 中执行refractionDir。这个函数假设我们在另一个物体内部没有透明物体,并且外部材料是空气,系数为1

Vec3f refractionDir(Vec3f ray, Vec3f normal, float refractionIndex, bool &isTotalInternalReflection) {
    float relativeIndexOfRefraction = 1.0f / refractionIndex;
    float cosi = -dot(ray, normal);
    if (isInside(ray, normal)) {
        // We should be reflecting across a normal inside the object, so
        // re-orient the normal to be inside.
        normal = multiply(normal, -1);
        relativeIndexOfRefraction = refractionIndex;
        cosi *= -1;
    }
    assert(cosi > 0);

    float base = (
        1 - (relativeIndexOfRefraction * relativeIndexOfRefraction) *
        (1 - cosi * cosi)
    );
    if (base < 0) {
        isTotalInternalReflection = true;
        return ray;
    }

    return add(
        multiply(ray, relativeIndexOfRefraction),
        multiply(normal, relativeIndexOfRefraction * cosi - sqrtf(base))
    );
}

这是球体远离相机时的结果:

远离相机的球体

更靠近相机:

球体更靠近相机


编辑:我注意到我的代码中有几个错误。

当我向交点添加偏差时,它应该与传输方向相同。我通过在球体内添加负偏差将它添加到错误的方向。这没有意义,因为当光线来自球体内部时,它将在球体外部传输(当避免 TIR 时)。

旧代码:

add(intersection, multiply(normal, bias))

新代码:

add(intersection, multiply(transmissionDirection, 1e-4))

类似地,refractionDir接收的法线是指向远离球体中心的表面法线。在计算透射方向时,我想使用的法线是指向外部的,如果透射光线要离开物体外部,或者如果透射光线要进入物体内部,则指向内部。因此,如果我们进入球体,指向球体的表面法线应该反转,因此光线在外面。

新代码:

Vec3f refractionDir(Vec3f ray, Vec3f normal, float refractionIndex, bool &isTotalInternalReflection) {
    float relativeIndexOfRefraction;
    float cosi = -dot(ray, normal);
    if (isInside(ray, normal)) {
        relativeIndexOfRefraction = refractionIndex;
        cosi *= -1;
    } else {
        relativeIndexOfRefraction = 1.0f / refractionIndex;
        normal = multiply(normal, -1);
    }
    assert(cosi > 0);

    float base = (
        1 - (relativeIndexOfRefraction * relativeIndexOfRefraction) * (1 - cosi * cosi)
    );
    if (base < 0) {
        isTotalInternalReflection = true;
        return ray;
    }

    return add(
        multiply(ray, relativeIndexOfRefraction),
        multiply(normal, sqrtf(base) - relativeIndexOfRefraction * cosi)
    );
}

然而,这一切仍然给了我一个意想不到的结果:

正常处理的新结果

我还添加了一些单元测试。他们通过以下内容:

我将在此处包含其中一个单元测试,您可以在此 gist 中找到其余的单元测试。

TEST_CASE("Refraction at 75 degrees from normal through glass slab") {
    Vec3f rayDirection = normalize(Vec3f({ 0, -sinf(5.0f * M_PI / 12.0f), -cosf(5.0f * M_PI / 12.0f) }));
    Vec3f normal({ 0, 0, 1 });
    bool isTotalInternalReflection;
    Vec3f refraction = refractionDir(rayDirection, normal, 1.5f, isTotalInternalReflection);
    REQUIRE(refraction[0] == 0);
    REQUIRE(refraction[1] == Approx(-sinf(40.0f * M_PI / 180.0f)).margin(0.03f));
    REQUIRE(refraction[2] == Approx(-cosf(40.0f * M_PI / 180.0f)).margin(0.03f));
    REQUIRE(!isTotalInternalReflection);

    refraction = refractionDir(refraction, multiply(normal, -1), 1.5f, isTotalInternalReflection);
    REQUIRE(refraction[0] == Approx(rayDirection[0]));
    REQUIRE(refraction[1] == Approx(rayDirection[1]));
    REQUIRE(refraction[2] == Approx(rayDirection[2]));
    REQUIRE(!isTotalInternalReflection);
}

标签: c++graphics3draytracing

解决方案


推荐阅读