c++ - 在光线追踪器中实现折射后,透明球体大部分是黑色的
问题描述
注意:我已经编辑了我的代码。请参见下面的分隔线。
我在我的(相当基本的)光线追踪器中实现折射,用 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
调用,一个在外部,一个在内部)。 - 通过玻璃板与法线呈 45 度角的折射将在玻璃板内部向法线方向弯曲 15 度,远离原始光线方向。它离开球体时的方向将是原始射线方向。
- 75度的类似测试。
- 确保当光线来自物体内部且角度为 45 度或更宽时发生全内反射。
我将在此处包含其中一个单元测试,您可以在此 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);
}
解决方案
推荐阅读
- php - 在多对多关系中使用 Symfony 和 Doctrine 进行搜索
- python - 如何检查输入是否在数组中并打印另一个数组的相应元素?
- konvajs - 如何将 id 添加到 konvajs 节点?
- tensorflow - tensorflow 到 tflite - 从 .pb 转换为 .tflite 时,tflite_converted 失败并显示“轴 0 计算为 0 的 StridedSlice”
- c++ - 转换和打印进程的模块名称
- r - 如何在匹配特定条件的数据框中找到最小值并返回特定列
- ios - 配置文件安装失败:SCEP 服务器返回无效响应(在修改配置文件时发生)
- go - 我使用 pprof 进行的 golang 程序分析显示内存在 std/json 包中的 json (* decodeState) objectInterface 处增加
- javascript - 如何在 React 中使用由 Stenciljs 创建的嵌套 Web 组件
- xcode - 如何修复在本地构建良好的 Travis CI 错误