Прозрачная сфера в основном черная после реализации рефракции в трассировщике лучей - PullRequest
0 голосов
/ 30 ноября 2018

ПРИМЕЧАНИЕ. Я отредактировал свой код.См. Разделитель ниже.

Я реализую рефракцию в моем (довольно простом) трассировщике лучей, написанном на 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

Редактировать: я заметил пару ошибок в моем коде.

Когда я добавляю смещение к точке пересечения, оно должно быть в том же направлении, что и передача.Я добавил это в неправильном направлении, добавив отрицательный уклон, когда внутри сферы.Это не имеет смысла, так как, когда луч выходит из сферы, он будет передавать за пределы сферы (когда МДП избегают).

Старый код:

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 градусов или шире.

Я включу здесь один из модульных тестов, и вы можете найти отдых в этой сущности .

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