注意:我已经编辑了代码。请参见分隔线。
我正在用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)
);
}
但是,这一切仍然给我带来了意外的结果:
我还添加了一些单元测试。他们通过了以下内容:
refractionDir
调用,一个在外部,一个在内部)。我将在此处包括一个单元测试,您可以找到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);
}