【问题标题】:Refraction methods don't agree with each other - or Snell's law折射方法彼此不一致 - 或 Snell 定律
【发布时间】:2021-09-23 19:55:05
【问题描述】:

我一直在尝试组合一种折射方法,该方法采用 Eigen::Vector3d 的射线和表面法线,以及界面前后的折射率。

它需要处理任意表面法线、入射光线和折射率。它不需要处理全内反射,我可以在方法之外检测到并避免它。

我有两个,但他们不同意,而且似乎都不符合 Snell 定律。我认为这两个可能最初都假设界面的一侧始终是空气,这在我的情况下不正确

第一个实现来自这里:https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/reflection-refraction-fresnel

它是:

Eigen::Vector3d refract(
  const Eigen::Vector3d &incVector, 
  Eigen::Vector3d surfaceNormal, 
  double etai, 
  double etat )
{
  Eigen::Vector3d incidentVector = incVector.normalized();
  double cosTheta = std::clamp( incidentVector.dot( surfaceNormal ), -1.0, 1.0 );

  if ( cosTheta < 0.0 ) {
    cosTheta = -cosTheta;
  } else {
    std::swap( etai, etat );
    surfaceNormal = -surfaceNormal;
  }

  double eta = etai / etat;

  Eigen::Vector3d newVector;
  double k = 1 - eta * eta * ( 1 - cosTheta * cosTheta );

  if ( k < 0 ) {
    // Total internal reflection, not handled ATM
    // Prefer to detect this outside?
    newVector = incidentVector;
    //fprintf(stderr, "total internal reflection, ignore\n");
  } else {
    newVector = eta * incidentVector + ( eta * cosTheta - std::sqrt( k ) ) * surfaceNormal;
    //fprintf(stderr, "good data:\n");
  }

  return newVector;
}

第二个实现来自这里:https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/snell'slaw

它是:

Eigen::Vector3d refract2(
  const Eigen::Vector3d &incVector, 
  Eigen::Vector3d surfaceNormal, 
  double etai, 
  double etat )
{
  Eigen::Vector3d incidentVector = incVector.normalized();

  double eta = etai / etat;
  double cosTheta = std::min( ( -incidentVector ).dot( surfaceNormal ), 1.0 );
  Eigen::Vector3d rOutPerpendicular = eta * ( incidentVector + cosTheta * surfaceNormal );
  double rOutPerpSq = std::pow( rOutPerpendicular.norm(), 2 );
  Eigen::Vector3d rOutParallel = -std::sqrt( std::abs( 1.0 - rOutPerpSq ) ) * surfaceNormal; 
  Eigen::Vector3d newVector = rOutPerpendicular + rOutParallel; 
  newVector.normalize(); // unsure if needed

  return newVector;
}

(需要说明的是,目前在测试时我只查看没有发生全内反射时的结果,所以我没有比较垃圾数据)

失败的例子:

入射光线:{0.18942838831751452, 0.013336588641010838, -0.62199774163029875}

正常:{-0.27689443647572254, -0.47422711874752033, 0.13779107028412740}

第一个 IOR:1.6393627735221412

第二个 IOR:1.9297769096957305

第一种方法结果:{0.35033145873009269, 0.19363689493923739, -0.86368789659903644}

第二种方法结果:{0.37149016792865353, 0.21243159055249269, -0.90380743218214299}

这些很接近,但不一样。

如果我理解正确的话,他们似乎也不同意斯涅尔定律:

IOR1/IOR2 = 0.84950895892967282

sin(点积(折射,正常) / sin(点积(事件,正常) = 2.1044755362563823

sin(点积(折射,正常)/ sin(点积(事件,正常)= 2.2383953074347627

我哪里做错了?

【问题讨论】:

  • @Vertexwahn 该方法似乎与#2一致。不过,我的测试一定做错了,因为结果似乎仍然不正确。 sin(dot product(refracted, normal) / sin(dot product(incident, normal) 的比率应该等于 etai/etat,不是吗?我是否遗漏了什么?

标签: c++ eigen raytracing


【解决方案1】:

原来我做错了测试。

点积 = cos(角度)。所以你首先必须用 arc-cos 去除 cos。

IE,以下测试通过:

    double cosThetaIncident = incident.dot( normal );
    double thetaIncident = std::acos( cosThetaIncident );
    double sinThetaIncident = std::sin(thetaIncident);

    double cosThetaRefracted = refracted.dot( normal );
    double thetaRefracted = std::acos( cosThetaRefracted );
    double sinThetaRefracted = std::sin(thetaRefracted);

在这种情况下,sinThetaRefracted / sinThetaIncident ) == ( startIOR / endinIOR )

(在浮点公差范围内,并假设我们不是全内反射情况)

更新:

我学到了更多。大多数 refract() 方法似乎都假设了被击中表面的方向。其中,有些人选择在计算中使用 -incidentVector,就像 vertexwahn.de 中的那样。

通过删除负号并添加校正,我不再看到发生折射时沿轴的翻转。

【讨论】:

  • 假设 normal 和 event 是单位向量,你引用的 cos() 是正确的。对于生产代码,每当我看到 arc sin/cos 值时,我都会退后一步,确保你没有做错什么,因为它们的计算成本很高,并且经常指向算法本身的错误。
  • 这个acos和sin只用在单元测试中,不计算折射本身。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-04
  • 1970-01-01
  • 1970-01-01
  • 2021-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多