Подтвердить что ты не робот

Как правильно обрабатывать преломление при трассировке лучей

В настоящее время я работаю над raytracer только для удовольствия, и у меня проблемы с обработкой рефракции.

Источник кода всего raytracer можно найти в Github.

Вот изображение рендера:

Ошибка рефракции

В правой сфере установлено значение рефракции 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 внутренне. (Чтобы я мог легко меняться, если захочу)

Я не отношусь к понятиям n1 и n2: n2 считается всегда для воздуха.

Я вижу что-то очевидное?


ИЗМЕНИТЬ

После добавления способа узнать, находится ли луч внутри объекта (и отрицает нормальный, если это так), у меня есть это:

Проблема преломления

Оглядываясь, чтобы найти помощь, я наткнулся на эту статью, но я не думаю, что ответ отвечает на все. Читая его, я не понимаю, что я должен делать вообще.


РЕДАКТИРОВАТЬ 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);
}

Вот еще один вид тех же сфер:

Сферы

4b9b3361

Ответ 1

EDIT: Я понял, что предыдущая версия была не совсем корректной, поэтому я редактирую ответ.

Прочитав все комментарии, новые версии вопроса и сделав некоторые эксперименты, я произвел следующую версию 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);

Единственное, что я до сих пор не уверен, это инвертирование показателя преломления при выполнении пересечения внутренних лучей. В моем тесте полученное изображение не сильно отличается, независимо от того, инвертировал ли индекс или нет.

Ниже некоторых изображений с разными индексами:

введите описание изображения здесь введите описание изображения здесь

Для получения дополнительных изображений см. ссылку , потому что сайт не позволяет мне размещать их здесь больше.

Ответ 2

Я отвечаю на это как физик, а не программист, так как не успел прочитать весь код, поэтому не будет давать код делать исправление только общей идеей.

Из того, что вы сказали выше, черное кольцо - это когда n_object меньше n_air. Это обычно справедливо, если вы находитесь внутри объекта, скажем, если вы были внутри воды или тому подобное, но материалы были построены с такими странными свойствами, и это должно поддерживаться.

В этом типе ситуации есть лучи света, которые не могут быть дифрагированы, поскольку дифракционная формула помещает преломленный луч на SAME сторону границы раздела между материалами, что, очевидно, не имеет смысла в качестве дифракции. В этой ситуации поверхность будет действовать как отражающая поверхность. Это ситуация, которую часто называют полным внутренним отражением.

Если быть полностью точным, то почти всегда рефрактивный объект также будет частично отражать, а доля света, которая отражается или передается (и, следовательно, преломлена), определяется Уравнения Френеля. Для этого случая, хотя это было бы хорошим приближением к простому лечению, оно будет отражать, если угол слишком далеко и передается (и, следовательно, преломляющий) в противном случае.

Также существуют ситуации, когда этот эффект черного кольца можно увидеть, если отражение невозможно (из-за его темного в этих направлениях), но возможен свет, который передается. Это можно сделать, произнеся трубку с картой, которая плотно прилегает к краю объекта и направлена ​​прямо в сторону и светит только внутри трубки не снаружи.