如何在光线追踪中正确处理折射问题。

22

我目前正在为了好玩而编写一个光线追踪器,但是我在处理折射时遇到了问题。

整个光线追踪器的代码可以在Github上找到。编辑:代码迁移到Gitlab

这是渲染的图像:

Refraction bug

右侧的球的折射率设置为1.5(玻璃)。

除了折射之外,我还想处理“透明度”系数,其定义如下:

  • 0-->物体是100%不透明的
  • 1-->物体是100%透明的(没有原始物体颜色的痕迹)

这个球的透明度为1。

以下是处理折射部分的代码。代码可以在Github上找到

Color handleTransparency(const Scene& scene,
                         const Ray& ray,
                         const IntersectionData& data,
                         uint8 depth)
{
  Ray refracted(RayType::Transparency, data.point, ray.getDirection());
  Float_t eta = data.material->getRefraction();

  if (eta != 1 && eta > Globals::Epsilon)
    refracted.setDirection(Tools::Refract(ray.getDirection(), data.normal, eta));
  refracted.setOrigin(data.point + Globals::Epsilon * refracted.getDirection());
  return inter(scene, refracted, depth + 1);
}

// http://graphics.stanford.edu/courses/cs148-10-summer/docs/2006--degreve--reflection_refraction.pdf
Float_t getFresnelReflectance(const IntersectionData& data, const Ray& ray)
{
  Float_t n = data.material->getRefraction();
  Float_t cosI = -Tools::DotProduct(ray.getDirection(), data.normal);
  Float_t sin2T = n * n * (Float_t(1.0) - cosI * cosI);

  if (sin2T > 1.0)
    return 1.0;

  using std::sqrt;
  Float_t cosT = sqrt(1.0 - sin2T);
  Float_t rPer = (n * cosI - cosT) / (n * cosI + cosT);
  Float_t rPar = (cosI - n * cosT) / (cosI + n * cosT);
  return (rPer * rPer + rPar * rPar) / Float_t(2.0);
}

Color handleReflectionAndRefraction(const Scene& scene,
                                    const Ray& ray,
                                    const IntersectionData& data,
                                    uint8 depth)
{
  bool hasReflexion = data.material->getReflexion() > Globals::Epsilon;
  bool hasTransparency = data.material->getTransparency() > Globals::Epsilon;

  if (!(hasReflexion || hasTransparency) || depth >= MAX_DEPTH)
    return 0;

  Float_t reflectance = data.material->getReflexion();
  Float_t transmittance = data.material->getTransparency();

  Color reflexion;
  Color transparency;

  if (hasReflexion && hasTransparency)
  {
    reflectance = getFresnelReflectance(data, ray);
    transmittance = 1.0 - reflectance;
  }

  if (hasReflexion)
    reflexion = handleReflection(scene, ray, data, depth) * reflectance;

  if (hasTransparency)
    transparency = handleTransparency(scene, ray, data, depth) * transmittance;

  return reflexion + transparency;
}

Tools::Refract内部简单地调用glm::refract。(以便我可以轻松更改)

我不涉及n1n2的概念:空气中的n2始终被认为是1。

我错过了一些明显的东西吗?


编辑

添加了一种方法来知道光线是否在物体内(如果是,则取反法线),我有:

Refraction problem

在寻找帮助时,我偶然发现了这个帖子,但我认为答案并没有回答任何问题。通过阅读它,我根本不明白该怎么做。


编辑2

我尝试了很多东西,目前我到了这个地步:

Current

这已经好了,但我还不确定它是否正确。我使用这个图像作为灵感:

Example

但是这个图像使用了两个折射率(为了更接近现实),而我想简化并始终将空气视为第二种(内部或外部)材料。

我在代码中本质上所做的更改在这里:

inline Vec_t Refract(Vec_t v, const IntersectionData& data, Float_t eta)
{
  Float_t n = eta;

  if (data.isInside)
    n = 1.0 / n;
  double cosI = Tools::DotProduct(v, data.normal);

  return v * n - data.normal * (-cosI + n * cosI);
}

这是同一组球的另一个视角:

球体


如果 (eta != 1),这会将 eta 提升为 double 还是将 1 提升为 float? - huseyin tugrul buyukisik
你确定从你所看到的那个黑色球体的角度来看,eta > Globals::Epsilon 是正确的吗?也许有些光线无法进入球体并且无法到达相机?你尝试从光源的正交角度观察了吗?你正在进行反向跟踪光线追踪吗? - huseyin tugrul buyukisik
这不应该发生,因为它上面有 if (hasReflexion) - Telokis
eta并不是计算出来的,而是用户输入的值。 - Telokis
刚刚尝试了使用0.001(Epsilon非常小,如0.0000001)。结果是完全相同的图像。 - Telokis
显示剩余21条评论
2个回答

6

编辑:我发现之前的版本并不完全正确,所以我修改了答案。

在阅读所有评论、新版本的问题并进行一些实验后,我编写了以下版本的refract例程:

float3 refract(float3 i, float3 n, float eta)
{
    eta = 2.0f - eta;
    float cosi = dot(n, i);
    float3 o = (i * eta - n * (-cosi + eta * cosi));
    return o;
}

这次调用不需要任何额外的操作:
float3 refr = refract(rayDirection, normal, refrIdx);

我唯一不确定的是在内部光线交点处反转折射率。在我的测试中,无论我是否反转指数,生成的图像都没有太大区别。

以下是具有不同折射率的一些图像:

enter image description here enter image description here

如需更多图片,请查看链接,因为该网站不允许我在此处放置更多图片。


使用您的“refract”给我带来了这个结果(http://image.prntscr.com/image/52166780f62942398063b295a8c77297.png)。右边的图像是您的“refract”,左边的是我的。每个球体的eta分别为0.7和1.5。 - Telokis
可能我没有很好地处理内部折射。 - Matso
@Ninetainedo 在进行内部折射时是否需要反转索引? - Matso
@Ninetainedo,你是在refract例程内部执行正常的反转操作吗?还是只是将反转后的法线传递给它?现在看起来是什么样子?它是否与您在上一个问题的最后一次编辑中发布的那个完全相同? - Matso
我已经编辑了这个程序,因为 sgn 对其结果没有影响。现在它与您发布的程序完全相同,但在开头进行了减法运算。 - Matso
显示剩余2条评论

5
我是一名物理学家,而不是程序员,因为我没有时间阅读所有代码,所以我不会提供修复的代码,只提供一般思路。根据您上面所说的,黑色环是当n_object小于n_air时出现的。这通常只在您在物体内部时才是真实的,比如您在水中或类似物质中,但是材料具有奇怪的特性,应该支持它。
在这种情况下,存在无法衍射的光线,因为衍射公式将折射光线放在材料之间的同一侧,这显然不符合衍射规律。在这种情况下,表面将像反射表面一样起作用。这种情况通常被称为全反射。
如果要完全准确,则几乎每个折射物体也会部分反射,反射或透过(因此折射)的光的比例由Fresnel方程给出。对于这种情况,如果角度太大,则仍然将其视为反射的好近似,否则将其传输(因此折射)。
此外,如果无法反射(由于在那些方向上很暗),但是可以传输光线,则可能会出现黑环效应。例如,可以通过取一个与物体边缘紧密贴合并指向远离物体的纸板管,并仅在管内照明而不是管外来实现这一点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接