Я пишу простой трассировщик лучей, и для простоты пока решил, что в моей сцене просто есть сферы. Сейчас я нахожусь на этапе, когда я просто хочу подтвердить, что мои лучи правильно пересекают сферу в сцене, и ничего больше. Я создал класс Ray и Sphere, а затем функцию в моем главном файле, которая просматривает каждый пиксель, чтобы увидеть, есть ли пересечение (соответствующий код будет опубликован ниже). Проблема в том, что все пересечение со сферой действует довольно странно. Если я создаю сферу с центром (0, 0, -20) и радиусом 1, то я получаю только одно пересечение, которое всегда находится в самом первом пикселе того, что будет моим изображением (верхний левый угол). Как только я достигаю радиуса 15, я внезапно получаю три пересечения в верхней левой области. Радиус 18 дает мне шесть пересечений, и как только я достигаю радиуса 20+, я внезапно получаю пересечение для КАЖДОГО пикселя, поэтому что-то действует так, как не должно.
Я подозревал, что мой код пересечения лучей и сферы может быть здесь ошибочным, но, просмотрев его и просмотрев сеть для получения дополнительной информации, большинство решений описывают тот же подход, который я использую, поэтому я предполагаю, что он не должен (!) Ошибаться Вот. Итак … Я не совсем уверен, что я делаю неправильно, это может быть мой код пересечения или это может быть что-то другое, вызывающее проблемы. Я просто не могу найти это. Может быть, я ошибаюсь, когда даю значения для сферы и лучей? Ниже приведен соответствующий код
Сфера класса:
Sphere::Sphere(glm::vec3 center, float radius)
: m_center(center), m_radius(radius), m_radiusSquared(radius*radius)
{
}
//Sphere-ray intersection. Equation: (P-C)^2 - R^2 = 0, P = o+t*d
//(P-C)^2 - R^2 => (o+t*d-C)^2-R^2 => o^2+(td)^2+C^2+2td(o-C)-2oC-R^2
//=> at^2+bt+c, a = d*d, b = 2d(o-C), c = (o-C)^2-R^2
//o = ray origin, d = ray direction, C = sphere center, R = sphere radius
bool Sphere::intersection(Ray& ray) const
{
//Squared distance between ray origin and sphere center
float squaredDist = glm::dot(ray.origin()-m_center, ray.origin()-m_center);
//If the distance is less than the squared radius of the sphere...
if(squaredDist <= m_radiusSquared)
{
//Point is in sphere, consider as no intersection existing
//std::cout << "Point inside sphere..." << std::endl;
return false;
}
//Will hold solution to quadratic equation
float t0, t1;
//Calculating the coefficients of the quadratic equation
float a = glm::dot(ray.direction(),ray.direction()); // a = d*d
float b = 2.0f*glm::dot(ray.direction(),ray.origin()-m_center); // b = 2d(o-C)
float c = glm::dot(ray.origin()-m_center, ray.origin()-m_center) - m_radiusSquared; // c = (o-C)^2-R^2
//Calculate discriminant
float disc = (b*b)-(4.0f*a*c);
if(disc < 0) //If discriminant is negative no intersection happens
{
//std::cout << "No intersection with sphere..." << std::endl;
return false;
}
else //If discriminant is positive one or two intersections (two solutions) exists
{
float sqrt_disc = glm::sqrt(disc);
t0 = (-b - sqrt_disc) / (2.0f * a);
t1 = (-b + sqrt_disc) / (2.0f * a);
}
//If the second intersection has a negative value then the intersections
//happen behind the ray origin which is not considered. Otherwise t0 is
//the intersection to be considered
if(t1<0)
{
//std::cout << "No intersection with sphere..." << std::endl;
return false;
}
else
{
//std::cout << "Intersection with sphere..." << std::endl;
return true;
}
}
Программа:
#include "Sphere.h"#include "Ray.h"
void renderScene(const Sphere& s);
const int imageWidth = 400;
const int imageHeight = 400;
int main()
{
//Create sphere with center in (0, 0, -20) and with radius 10
Sphere testSphere(glm::vec3(0.0f, 0.0f, -20.0f), 10.0f);
renderScene(testSphere);
return 0;
}
//Shoots rays through each pixel and check if there's an intersection with
//a given sphere. If an intersection exists then the counter is increased.
void renderScene(const Sphere& s)
{
//Ray r(origin, direction)
Ray r(glm::vec3(0.0f), glm::vec3(0.0f));
//Will hold the total amount of intersections
int counter = 0;
//Loops through each pixel...
for(int y=0; y<imageHeight; y++)
{
for(int x=0; x<imageWidth; x++)
{
//Change ray direction for each pixel being processed
r.setDirection(glm::vec3(((x-imageWidth/2)/(float)imageWidth), ((imageHeight/2-y)/(float)imageHeight), -1.0f));
//If current ray intersects sphere...
if(s.intersection(r))
{
//Increase counter
counter++;
}
}
}
std::cout << counter << std::endl;
}
Ваше второе решение (t1
) к квадратному уравнению неверно в случае disc > 0
где вам нужно что-то вроде:
float sqrt_disc = glm::sqrt(disc);
t0 = (-b - sqrt_disc) / (2 * a);
t1 = (-b + sqrt_disc) / (2 * a);
Я думаю, что лучше написать уравнение в этой форме, а не превращать деление на 2 в умножение на 0,5, потому что чем больше код напоминает математику, тем легче его проверить.
Несколько других незначительных комментариев:
Казалось странным повторно использовать имя disc
за sqrt(disc)
, поэтому я использовал новое имя переменной выше.
Вам не нужно проверять на t0 > t1
, поскольку вы знаете, что оба a
а также sqrt_disc
положительны, и так t1
всегда больше чем t0
,
Если источник луча находится внутри сферы, это возможно для t0
быть отрицательным и t1
быть позитивным. Вы, кажется, не справитесь с этим делом.
Вам не нужен особый случай для disc == 0
, так как общий случай вычисляет те же значения, что и частный случай. (И чем меньше у вас особых случаев, тем проще проверить код.)
Если я правильно понимаю ваш код, вы можете попробовать:
r.setDirection(glm::vec3(((x-imageWidth/2)/(float)imageWidth),
((imageHeight/2-y)/(float)imageHeight),
-1.0f));
Прямо сейчас вы расположили камеру на расстоянии одной единицы от экрана, но лучи могут стрелять на целых 400 единиц вправо и вниз. Это очень широкое поле зрения. Кроме того, ваши лучи охватывают только один октет пространства. Вот почему вы получаете только несколько пикселей в верхнем левом углу экрана. Код, который я написал выше, должен исправить это.