Преломление в Raytracing?
Я снова работаю над своим raytracer. Я добавил поддержку рефлексии и многопоточности. В настоящее время я работаю над добавлением преломлений, но его половина работает.
![Spheres]()
Как вы можете видеть, есть центральная сфера (без зеркальной подсветки), отражающая сфера (справа) и преломляющая сфера (слева). Я очень доволен размышлениями, это выглядит очень хорошо. Для преломлений его любопытная работа... свет преломляется, и все сферы сфер видны в сфере (показатель преломления 1.4), но есть внешнее черное кольцо.
EDIT: По-видимому, черное кольцо становится больше, и поэтому сфера меньше, когда я увеличиваю показатель преломления сферы. Напротив, при уменьшении показателя преломления Сфера становится больше, а черное кольцо меньше... до тех пор, пока показатель преломления не станет равным единице, кольцо полностью исчезнет.
IOR = 1,9
IOR = 1,1
IOR = 1.00001
И интересно, что при IOR = 1 сфера теряет прозрачность и становится белой.
![enter image description here]()
Я думаю, что я рассмотрел полное внутреннее отражение, и здесь это не проблема.
Теперь код:
Я использую operator |
для точечного продукта, поэтому (vec|vec)
является точечным продуктом и operator ~
для инвертирования векторов. Объекты, как лиги, так и сферы, хранятся в Object **objects;
.
Функция Raytrace
Colour raytrace(const Ray &r, const int &depth)
{
//first find the nearest intersection of a ray with an object
Colour finalColour = skyBlue *(r.getDirection()|Vector(0,0,-1)) * SKY_FACTOR;
double t, t_min = INFINITY;
int index_nearObj = -1;
for(int i = 0; i < objSize; i++)
{
if(!dynamic_cast<Light *>(objects[i]))//skip light src
{
t = objects[i]->findParam(r);
if(t > 0 && t < t_min)
{
t_min = t;
index_nearObj = i;
}
}
}
//no intersection
if(index_nearObj < 0)
return finalColour;
Vector intersect = r.getOrigin() + r.getDirection()*t_min;
Vector normal = objects[index_nearObj]->NormalAtIntersect(intersect);
Colour objectColor = objects[index_nearObj]->getColor();
Ray rRefl, rRefr; //reflected and refracted Ray
Colour refl = finalColour, refr = finalColour; //reflected and refracted colours
double reflectance = 0, transmittance = 0;
if(objects[index_nearObj]->isReflective() && depth < MAX_TRACE_DEPTH)
{
//handle reflection
rRefl = objects[index_nearObj]->calcReflectingRay(r, intersect, normal);
refl = raytrace(rRefl, depth + 1);
reflectance = 1;
}
if(objects[index_nearObj]->isRefractive() && depth < MAX_TRACE_DEPTH)
{
//handle transmission
rRefr = objects[index_nearObj]->calcRefractingRay(r, intersect, normal, reflectance, transmittance);
refr = raytrace(rRefr, depth + 1);
}
Ray rShadow; //shadow ray
bool shadowed;
double t_light = -1;
Colour localColour;
Vector tmpv;
//get material properties
double ka = 0.2; //ambient coefficient
double kd; //diffuse coefficient
double ks; //specular coefficient
Colour ambient = ka * objectColor; //ambient component
Colour diffuse, specular;
double brightness;
localColour = ambient;
//look if the object is in shadow or light
//do this by casting a ray from the obj and
// check if there is an intersection with another obj
for(int i = 0; i < objSize; i++)
{
if(dynamic_cast<Light *>(objects[i])) //if object is a light
{
//for each light
shadowed = false;
//create Ray to light
tmpv = objects[i]->getPosition() - intersect;
rShadow = Ray(intersect + (!tmpv) * BIAS, tmpv);
t_light = objects[i]->findParam(rShadow);
if(t_light < 0) //no imtersect, which is quite impossible
continue;
//then we check if that Ray intersects one object that is not a light
for(int j = 0; j < objSize; j++)
{
if(!dynamic_cast<Light *>(objects[j]) && j != index_nearObj)//if obj is not a light
{
t = objects[j]->findParam(rShadow);
//if it is smaller we know the light is behind the object
//--> shadowed by this light
if (t >= 0 && t < t_light)
{
// Set the flag and stop the cycle
shadowed = true;
break;
}
}
}
if(!shadowed)
{
rRefl = objects[index_nearObj]->calcReflectingRay(rShadow, intersect, normal);
//reflected ray from ligh src, for ks
kd = maximum(0.0, (normal|rShadow.getDirection()));
if(objects[index_nearObj]->getShiny() <= 0)
ks = 0;
else
ks = pow(maximum(0.0, (r.getDirection()|rRefl.getDirection())), objects[index_nearObj]->getShiny());
diffuse = kd * objectColor;// * objects[i]->getColour();
specular = ks * objects[i]->getColor();
brightness = 1 /(1 + t_light * DISTANCE_DEPENDENCY_LIGHT);
localColour += brightness * (diffuse + specular);
}
}
}
finalColour = localColour + (transmittance * refr + reflectance * refl);
return finalColour;
}
Теперь функция, которая вычисляет преломленный луч, я использовал несколько разных сайтов для ресурса, и у каждого были схожие алгоритмы. Это лучшее, что я мог сделать до сих пор. Это может быть крошечная деталь, которую я не вижу...
Ray Sphere::calcRefractingRay(const Ray &r, const Vector &intersection,Vector &normal, double & refl, double &trans)const
{
double n1, n2, n;
double cosI = (r.getDirection()|normal);
if(cosI > 0.0)
{
n1 = 1.0;
n2 = getRefrIndex();
normal = ~normal;//invert
}
else
{
n1 = getRefrIndex();
n2 = 1.0;
cosI = -cosI;
}
n = n1/n2;
double sinT2 = n*n * (1.0 - cosI * cosI);
double cosT = sqrt(1.0 - sinT2);
//fresnel equations
double rn = (n1 * cosI - n2 * cosT)/(n1 * cosI + n2 * cosT);
double rt = (n2 * cosI - n1 * cosT)/(n2 * cosI + n2 * cosT);
rn *= rn;
rt *= rt;
refl = (rn + rt)*0.5;
trans = 1.0 - refl;
if(n == 1.0)
return r;
if(cosT*cosT < 0.0)//tot inner refl
{
refl = 1;
trans = 0;
return calcReflectingRay(r, intersection, normal);
}
Vector dir = n * r.getDirection() + (n * cosI - cosT)*normal;
return Ray(intersection + dir * BIAS, dir);
}
EDIT: я также изменил показатель преломления. Из
if(cosI > 0.0)
{
n1 = 1.0;
n2 = getRefrIndex();
normal = ~normal;
}
else
{
n1 = getRefrIndex();
n2 = 1.0;
cosI = -cosI;
}
к
if(cosI > 0.0)
{
n1 = getRefrIndex();
n2 = 1.0;
normal = ~normal;
}
else
{
n1 = 1.0;
n2 = getRefrIndex();
cosI = -cosI;
}
Тогда я получаю это и почти то же (все еще вверх ногами) с показателем преломления в 1!
И расчет отражения:
Ray Sphere::calcReflectingRay(const Ray &r, const Vector &intersection, const Vector &normal)const
{
Vector rdir = r.getDirection();
Vector dir = rdir - 2 * (rdir|normal) * normal;
return Ray(intersection + dir*BIAS, dir);
//the Ray constructor automatically normalizes directions
}
Итак, мой вопрос: как исправить внешний черный круг? Какая версия верна?
Помощь с благодарностью:)
Это скомпилировано в Linux с помощью g++ 4.8.2.
Ответы
Ответ 1
Предупреждение: следующее предположение, а не определенность. Мне нужно будет посмотреть на код более подробно, чтобы убедиться, что происходит и почему.
Тем не менее, мне кажется, что ваш оригинальный код в основном имитирует вогнутую линзу вместо выпуклой.
![enter image description here]()
Выпуклая линза представляет собой в основном увеличительную линзу, принося лучи света с относительно небольшой площади в фокус на плоскости:
![enter image description here]()
Это также показывает, почему исправленный код показывает перевернутое изображение. Лучи света, идущие сверху с одной стороны, проецируются снизу на другую (и наоборот).
Возвращение к вогнутой линзе: вогнутая линза - это уменьшающая линза, которая показывает широкий угол изображения перед объективом:
![enter image description here]()
Если вы посмотрите на нижний правый угол здесь, это показывает, что я подозреваю, это проблема: особенно с высоким показателем преломления, лучи света, пытающиеся проникнуть в объектив, пересекают край самого объектива. Для всех углов шире, чем вы, вы обычно увидите черное кольцо, потому что передняя кромка объектива действует как тень, чтобы предотвратить попадание света.
Увеличение показателя преломления увеличивает ширину этого черного кольца, поскольку свет больше изогнут, поэтому большая часть на краях пересекает внешний край линзы.
Если вам небезразлично, как они избегают этого с вещами, такими как широкоугольные объективы камеры, обычным путем является использование менискового объектива, по крайней мере для переднего элемента:
![enter image description here]()
Это не панацея, но, по крайней мере, предотвращает пересечение входящих световых лучей от внешнего края переднего линзового элемента. В зависимости от того, насколько широкий угол, который должен покрывать объектив, он часто будет немного менее радикальным из мениска, чем это (и в некоторых случаях это будет планово-вогнутое), но вы получите общую идею.
Заключительное предупреждение: конечно же, все это нарисовано вручную и предназначено только для того, чтобы дать общее представление, а не (например) отражать дизайн любого конкретного объектива, элемент с каким-либо конкретным показателем преломления и т.д.