ОБРАТНАЯ ТРАССИРОВКА ЛУЧЕЙ.
ПРЕЛОМЛЕНИЯ

Фролов В., Фролов А.
Ray-tracing.ru

Источник: http://www.iit.bme.hu/~szirmay/ibl3.pdf

Аннотация

С преломлениями в трассировке лучей все немного сложнее, чем с отражениями. Во-первых, нужно учитывать эффект так называемого "полного внутреннего отражения", который говорит о том, что при больших углах преломления не происходит и свет полностью отражается. Во-вторых, необходимо получить коэффициенты преломления двух сред: той, из которой луч выходит той, в которую он входит. Как это сделать имея информацию только о поверхности, о которую ударился луч? И в третиьх, нужно учитывать, что луч прошел определенное растояние внутри преломляющего объекта, который вообще говоря может быть мутным. То есть нужно учитывать затухание (Закон Бугера — Ламберта — Бера). И наконец, неплохо бы учесть волновую природу света. Для разных длинн волн свет будет преломляться на разные углы и затухание у каждой длинны волны вообще говоря свое (коэффициенты преломления и затухания задаются для каждой длинны волны).

Разберемся сначала с первыми двумя проблемами. Если произошло полное внутреннее отражение, то преломляющий луч вообще не нужно траверсить (то есть его просто нет). Далее, как нам получить коэффициенты преломления? В общем случае это непросто. Если преломляющие объекты пересекаются друг с другом, то имея лишь информацию о том, каким материалом обладает поверхность, о которую ударился луч, невозможно сказать, в какую среду луч попадет при выходе из объекта. Не будем усложнять себе жизнь. Предположим, что все преломляющие объекты не пересекаются друг с другом а преломлющий показатель воздуха равен 1. Тогда нетрудно заметить, что если угол между нормалью к поверхности и направлением луча тупой, то луч переходит из воздуха в среду с показателем преломления материала, какорым обладает поверхность. Если же угол острый, то наоборот - луч вылетает из среды в воздух.



template <int n, class T>
inline bool refract(RAY <n,T> * inout_ray, const VECTOR <n,T> & normal, T eta)
{
eta = 1.0f/eta;
// тут где-то глюк был, нужно инвертирвать отношение, eta = material.n/1.0f;
// посчитаем косинус угла между нормалью к поверхности и направлением луча

T cos_theta = -dot(normal, inout_ray->dir);
// если косинус меньше нуля то мы "выходим" из объекта в воздух
// иначе - "входим" из воздуха в объект

if(cos_theta < 0)
{
normal *= (-1);
cos_theta *= -1.0f;
eta = 1.0f/eta;

}
T k = 1.0f - eta*eta*(1.0-cos_theta*cos_theta);
if(k > 0)
{
inout_ray->dir = eta*inout_ray->dir + (eta*cos_theta - sqrt(k))*normal;
inout_ray->dir.Normalize();
}
else // полное внутреннее отражение.
{
in_out.ray.pos = invalid; // убить луч
}
return (k > 0);
}


Математика того, как рассчитывается преломление описана тут http://users.skynet.be/bdegreve/writings/reflection_transmission.pdf. Однако, код из статьи обладает тем недостатком, что он не различает случаи входа в среду и выхода из нее и никак не говорит о том, как же получить коэффициенты преломления сред. Плюс в ней видимо считается, что нормаль к поверхности всегда смотрит в ту сторону, откуда прилетел луч.

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

Результат вроде похоже на правду. Преломляющая стеклянная сфера расположена слева. Изображение переворачивется, как и должно быть.

пример изображения