如何正确处理光线追踪中的折射

Posted

技术标签:

【中文标题】如何正确处理光线追踪中的折射【英文标题】:How to properly handle refraction in raytracing 【发布时间】:2017-07-02 07:16:38 【问题描述】:

我目前正在开发一个光线追踪器,只是为了好玩,但我在折射处理方面遇到了麻烦。

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

这是渲染的图像:

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

在折射之上,我想处理一个定义为这样的“透明度”系数:

0 --> 对象 100% 不透明 1 --> 对象 100% 透明(没有原始对象颜色的痕迹)

这个球体的透明度为 1。

这是处理折射部分的代码。可以找到on github here。

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。

我错过了什么明显的东西吗?


编辑

在添加了一种方法来了解光线是否在对象内部(如果是,则否定法线)我有这个:

在四处寻找帮助时,我偶然发现了this 的帖子,但我认为答案无法回答任何问题。通过阅读它,我根本不明白我应该做什么。


编辑 2

我已经尝试了很多东西,我目前处于这一点:

这更好,但我仍然不确定它是否正确。我用这张图片作为灵感:

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

我在我的代码中进行了本质上的更改:

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

这是相同领域的另一个视图:

【问题讨论】:

if (eta != 1 这会促进 eta 加倍还是促进 1 浮动? 我认为这会促进 1 浮动。 (顺便说一句,Float_tdouble 你确定eta > Globals::Epsilon 是真的吗?也许有些光线无法进入球体并且无法到达相机?您是否尝试过从正交角度看光源?你是在做反向追踪光线追踪吗? 是的,data.material->getRefraction() 返回一个常量。这是一个用户定义的值,给出给定对象的折射指数。 (在这个特定的测试用例中它的值为1.5) transmittance = 1.0 - reflectance; 覆盖 Float_t transmittance = data.material->getTransparency(); 也许? 【参考方案1】:

编辑:我认为以前的版本并不完全正确,所以我编辑了答案。

在阅读了所有的cmets,新版本的问题并自己做了一些实验后,我制作了以下版本的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);

我唯一不确定的是在进行内部光线相交时折射率的反转。在我的测试中,无论我是否反转索引,生成的图像都没有太大差异。

下面是一些不同索引的图片:

更多图片请查看link,因为该网站不允许我将更多图片放在这里。

【讨论】:

使用你的refract 给我this。正确的图像是你的折射,正确的图像是我的。每个球体的 eta 分别为 0.7 和 1.5。 可能我没有很好地处理inside折射。 @Ninetainedo 在进行 inside 折射或不进行折射时是否会反转索引? 我不反转索引,但如果我在里面,我会反转正常。 @Ninetainedo 那么您是否在refract 例程中进行正常反转?或者只是将倒置法线传递给它?现在看起来怎么样?它与您之前在上次编辑问题时发布的完全一样吗?【参考方案2】:

我是作为物理学家而不是程序员来回答这个问题的,因为我还没有时间阅读所有代码,所以不会给出代码来修复这个问题。

从你上面所说的黑色环是当 n_object 小于 n_air 时。这通常只有在你在一个物体内部时才成立,比如你在水或类似的地方,但材料的构造具有类似的奇怪属性,应该得到支持。

在这种情况下,存在无法衍射的光线,因为衍射公式将折射光线放在材料之间界面的 SAME 侧,这显然没有衍射的意义。在这种情况下,表面将改为像反射表面一样。这种情况通常被称为全内反射。

如果完全精确,那么几乎所有折射物体也会部分反射,并且反射或透射(因此折射)的光的比例由Fresnel equations 给出。在这种情况下,如果角度太远,将其视为反射仍然是一个很好的近似值,否则会透射(因此是折射的)。

还有一些情况下,如果反射是不可能的(由于在这些方向上是黑暗的),但可以透射光,则可以看到这种黑环效果。这可以通过拿一管卡紧贴在物体边缘并直接指向外面并且只在管内而不是外面照射光来完成。

【讨论】:

以上是关于如何正确处理光线追踪中的折射的主要内容,如果未能解决你的问题,请参考以下文章

Ray Tracing in One Weekend 超详解 光线追踪1-7 Dielectric 半径为负,实心球体镂空技巧

一个光线追踪demo

2 追踪光线=》2.7 透射光线

2 追踪光线=》2.1

GAMES202 笔记-实时光线追踪

如何将光线追踪器中的光线从世界空间反向旋转到对象空间