Raytracing Shadows

Итак, я прочитал о трассировке лучей в сети и в свободное время начал писать Raytracer с нуля. Я использую C ++, который изучаю около месяца. Я ознакомился с теорией трассировки лучей в сети, и пока она работает достаточно хорошо. Это просто базовый Raytracer, который не использует ни модели, ни текстуры.

Сначала это сделал Raycaster и был вполне доволен результатом.
введите описание изображения здесь

Тогда я попробовал несколько объектов, и это тоже сработало. Я просто использовал диффузное затенение в этой реализации и добавил к цвету света цвет объекта в точках, где он не затенен.
введите описание изображения здесь
К сожалению, этот Кодекс не работал для нескольких источников света. Тогда я начал переписывать свой код, чтобы он поддерживал несколько источников света.
Я также прочитал об освещении Фонг и приступил к работе:
введите описание изображения здесь
Он даже работает с несколькими источниками света:
введите описание изображения здесь

Пока что я доволен, но теперь я застрял. Я пытался исправить это в течение довольно долгого времени, но я ничего не придумал. Когда я добавляю вторую сферу или даже третью, подсвечивается только последняя сфера. Под последним я подразумеваю объект в моем массиве, где я храню все объекты. См. Код ниже.
введите описание изображения здесь

Ясно, что фиолетовая Сфера должна иметь аналогичное освещение, поскольку их центр лежит в одной плоскости. К моему удивлению, сфера имеет только внешнее освещение -> затенено, чего не должно быть.

Итак, теперь моя функция трассировки:

Colour raytrace(const Ray &r, const int &depth)
{
//first find the nearest intersection of a ray with an object
//go through all objects an find the one with the lowest parameter

double t, t_min = INFINITY;
int index_nearObj = -1;//-1 to know if obj found
for(int i = 0; i < objSize; i++)
{
//skip light src
if(!dynamic_cast<Light *>(objects[i]))
{
t = objects[i]->findParam(r);
if(t > 0 && t < t_min) //if there is an intersection and
//its distance is lower than the current min --> new min
{
t_min = t;
index_nearObj = i;
}
}
}
//now that we have the nearest intersection, calc the intersection point
//and the normal at that point
//r.position + t * r.direction

if(t_min < 0 || t_min == INFINITY) //no intersection --> return background Colour
return White;
Vector intersect = r.getOrigin() + r.getDirection()*t;
Vector normal = objects[index_nearObj]->NormalAtIntersect(intersect);

//then calculate light ,shading and colourRay shadowRay;
Ray rRefl;//reflected ray
bool shadowed;
double t_light = -1;
Colour finalColour = White;
Colour objectColor = objects[index_nearObj]->getColour();
Colour localColour;
Vector tmpv;

//get material properties
double ka = 0.1; //ambient coefficient
double kd; //diffuse coefficient
double ks; //specular coefficient
Colour ambient = ka * objectColor; //ambient component
//the minimum Colour the obj has, even if object is not hit by light
Colour diffuse, specular;
double brightness;
int index = -1;
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
//its origin is the intersection point
//its direction is the position of the light - intersection
tmpv = objects[i]->getPosition() - intersect;
shadowRay = Ray(intersect  + (!tmpv) * BIAS, tmpv);
//the ray construcor automatically normalizes its direction
t_light = objects[i]->findParam(shadowRay);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]))//if obj is not a light
{
//we compute the distance to the object and compare it
//to the light distance, for each light seperately
//if it is smaller we know the light is behind the object
//--> shadowed by this light

t = objects[j]->findParam(shadowRay);
if(t < 0) // no intersection
continue;
if(t < t_light) // intersection that creates shadow
shadowed = true;
else
{
shadowed = false;
index = j;//not using the index now,maybe later
break;
}
}
}

//we know if intersection is shadowed or not
if(!shadowed)// if obj is not shadowed
{
rRefl = objects[index_nearObj]->calcReflectingRay(shadowRay, intersect); //reflected ray from ligh src, for ks
kd = maximum(0.0, (normal|shadowRay.getDirection()));
ks = pow(maximum(0.0, (r.getDirection()|rRefl.getDirection())), objects[index_nearObj]->getMaterial().shininess);
diffuse = kd * objectColor;// * objects[i]->getColour();
specular = ks * objects[i]->getColour();//not sure if obj needs specular colour
brightness = 1 /(1 + t_light * DISTANCE_DEPENDENCY_LIGHT);
localColour += brightness * (diffuse + specular);
}
}
}
//handle reflection

//handle transmission

//combine colours
//localcolour+reflectedcolour*refl_coeff + transmittedcolor*transmission coeff

finalColour = localColour; //+reflcol+ transmcol
return finalColour;
}

Следующая функция рендеринга:

for(uint32_t y = 0; y < h; y++)
{
for(uint32_t x = 0; x < w; x++)
{
//pixel coordinates for the scene, depends on implementation...here camera on z axis
pixel.X() = ((x+0.5)/w-0.5)*aspectRatio *angle;
pixel.Y() = (0.5 - (y+0.5)/w)*angle;
pixel.Z() = look_at.getZ();//-1, cam at 0,0,0

rTmp = Ray(cam.getOrigin(), pixel - cam.getOrigin());
cTmp = raytrace(rTmp, depth);//depth == 0
pic.setPixel(y, x, cTmp);//writes colour of pixel in picture
}
}

Итак, вот мои функции пересечения:

double Sphere::findParam(const Ray &r) const
{
Vector rorig = r.getOrigin();
Vector rdir = r.getDirection();
Vector center = getPosition();
double det, t0 , t1; // det is the determinant of the quadratic equation: B² - 4AC;

double a = (rdir|rdir);
//one could optimize a away cause rdir is a unit vector
double b = ((rorig - center)|rdir)*2;
double c = ((rorig - center)|(rorig - center)) - radius*radius;
det = b*b - 4*c*a;
if(det < 0)
return -1;

t0 = (-b - sqrt(det))/(2*a);
if(det == 0)//one ontersection, no need to compute the second param!, is same
return t0;

t1 = (-b + sqrt(det))/(2*a);
//two intersections, if t0 or t1 is neg, the intersection is behind the origin!

if(t0 < 0 && t1 < 0) return -1;
else if(t0 > 0 && t1 < 0) return t0;
else if(t0 < 0 && t1 > 0) return t1;
if(t0 < t1)
return t0;
return t1;
}

Ray Sphere::calcReflectingRay(const Ray &r, const Vector &intersection)const
{
Vector rdir = r.getDirection();
Vector normal = NormalAtIntersect(intersection);
Vector dir = rdir - 2 * (rdir|normal) * normal;
return Ray(intersection, !dir);
}

//Light intersection(point src)
double Light::findParam(const Ray &r) const
{
double t;
Vector rorig = r.getOrigin();
Vector rdir = r.getDirection();
t = (rorig - getPosition())|~rdir; //~inverts a Vector
//if I dont do this, then both spheres are not illuminated-->ambient
if(t > 0)
return t;
return -1;
}

Вот абстрактный класс Object. Каждая сфера, свет и т. Д. — это объект.

class Object
{
Colour color;
Vector pos;
//Colour specular;not using right now
Material_t mat;
public:
Object();
Object(const Colour &);
Object(const Colour &, const Vector &);
Object(const Colour &, const Vector &, const Material_t &);
virtual ~Object()=0;

virtual double findParam(const Ray &) const =0;
virtual Ray calcReflectingRay(const Ray &, const Vector &)const=0;
virtual Ray calcRefractingRay(const Ray &, const Vector &)const=0;
virtual Vector NormalAtIntersect(const Vector &)const=0;

Colour getColour()const {return color;}
Colour & colour() {return color;}
Vector getPosition()const {return pos;}
Vector & Position() {return pos;}
Material_t getMaterial()const {return mat;}
Material_t & material() {return mat;}

friend bool operator!=(const Object &obj1, const Object &obj2)
{//compares only references!
if(&obj1 != &obj2)
return true;
return false;
}
};

Я использую глобальный массив указателей объектов для хранения всех источников света, сфер и т. Д. Мира:

Object *objects[objSize];

Я знаю, что мой код беспорядок, но если у кого-то есть идея, что происходит, я был бы очень благодарен.

РЕДАКТИРОВАТЬ 1 Я добавил фотографии.

РЕДАКТИРОВАТЬ 2 Обновлен код, исправлена ​​незначительная ошибка. Все еще нет решения.

Обновление: добавлен код рендеринга, который создает лучи.

3

Решение

Мне удалось отладить ваш трассировщик лучей, используя Linux и gcc.
Что касается проблемы, ну … как только я ее нашел, я почувствовал желание несколько раз ударить головой о клавиатуру. 🙂
Ваш алгоритм правильный, Кроме для небольшой подлости:

Vector intersect = r.getOrigin() + r.getDirection()*t;

Когда вы вычисляете точку пересечения, вы используете t вместо t_min,
Исправление заключается в изменении вышеуказанной строки на:

Vector intersect = r.getOrigin() + r.getDirection()*t_min;

Правильный вывод следующий:

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

Я думаю, что проблема заключается в вашей петле теневых лучей:

        //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]))//if obj is not a light
{
//we compute the distance to the object and compare it
//to the light distance, for each light seperately
//if it is smaller we know the light is behind the object
//--> shadowed by this light

t = objects[j]->findParam(shadowRay);
if(t < 0) // no intersection
continue;
if(t < t_light) // intersection that creates shadow
shadowed = true;
else
{
shadowed = false;
index = j;//not using the index now,maybe later
break;
}
}
}

В основном, когда пересечение найдено, вы устанавливаете shadowed флаг для trueНО вы продолжаете цикл: это неэффективно и неправильно.
Когда пересечение найдено, нет необходимости искать другое.
Я думаю, что ваш shadow flag снова устанавливается в false, потому что вы не останавливаете цикл.
Кроме того, когда t >= t_light вы прерываете цикл, что тоже неверно (это так же, как t < 0).
Я хотел бы изменить код на следующее:

            //then we check if that Ray intersects one object that is not a light
for (int j = 0; j < objSize; j++)
{
// Exclude lights AND the closest object found
if(j != index_nearObj && !dynamic_cast<Light *>(objects[j]))
{
//we compute the distance to the object and compare it
//to the light distance, for each light seperately
//if it is smaller we know the light is behind the object
//--> shadowed by this light

t = objects[j]->findParam(shadowRay);

// If the intersection point lies between the object and the light source,
// then the object is in shadow!
if (t >= 0 && t < t_light)
{
// Set the flag and stop the cycle
shadowed = true;
break;
}
}
}

Некоторые другие предложения:

  • Рефакторинг вашего кода рендеринга, добавив функцию, которая дает луч находит самое близкое / первое пересечение со сценой. Это позволяет избежать дублирования кода.

  • Не перегружайте операторы для точечного произведения и нормализации: используйте вместо этого выделенные функции.

  • Постарайтесь максимально уменьшить область действия ваших переменных: это улучшает читабельность кода.

  • Продолжайте изучать raytracing, потому что это круто: D

6

Другие решения


По вопросам рекламы [email protected]