在光线追踪器中执行折射后,透明球体大部分为黑色

时间:2018-11-30 00:35:05

标签: c++ graphics 3d raytracing

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

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

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

Two opaque spheres and a transparent sphere.

中心球的透射系数为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%。

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

Result without bias on intersection point

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

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

Spheres further away from camera

更靠近相机:

Spheres closer to the camera


编辑:我发现代码中有几个错误。

当我向交点添加偏置时,它应该与透射方向相同。我通过在球体内部添加负偏向来以错误的方向添加它。这是没有意义的,因为当射线来自球体内部时,它将在球体外部透射(避免使用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)
    );
}

但是,这一切仍然给我带来了意外的结果:

New result with proper normal handling

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

  • 与法线平行进入球体中心的光线将通过球体而不会弯曲(这测试了两个refractionDir调用,一个在外部,一个在内部)。
  • 通过玻璃平板与法线成45度的折射将使平板内部向法线弯曲15度,从而远离原始射线方向。它离开球体时的方向将是原始射线方向。
  • 在75度下进行类似测试。
  • 确保当光线从物体内部入射并且处于45度或更宽时,会发生全内反射。

我将在此处包括一个单元测试,您可以找到rest at this 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);
}

0 个答案:

没有答案