Как правильно обрабатывать преломление при трассировке лучей
В настоящее время я работаю над 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);
}
Вот еще один вид тех же сфер:
![Сферы]()
Ответы
Ответ 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 сторону границы раздела между материалами, что, очевидно, не имеет смысла в качестве дифракции. В этой ситуации поверхность будет действовать как отражающая поверхность. Это ситуация, которую часто называют полным внутренним отражением.
Если быть полностью точным, то почти всегда рефрактивный объект также будет частично отражать, а доля света, которая отражается или передается (и, следовательно, преломлена), определяется Уравнения Френеля. Для этого случая, хотя это было бы хорошим приближением к простому лечению, оно будет отражать, если угол слишком далеко и передается (и, следовательно, преломляющий) в противном случае.
Также существуют ситуации, когда этот эффект черного кольца можно увидеть, если отражение невозможно (из-за его темного в этих направлениях), но возможен свет, который передается. Это можно сделать, произнеся трубку с картой, которая плотно прилегает к краю объекта и направлена прямо в сторону и светит только внутри трубки не снаружи.