我正在编写一个简单的光线跟踪器并且为了保持简单,我现在决定在我的场景中使用球体。我现在处于一个阶段,我只想确认我的光线正确地与场景中的球体相交,没有别的。我已经创建了一个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;
}
答案 0 :(得分:2)
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
为正。你似乎没有处理这种情况。
您不需要{{1}}的特殊情况,因为一般情况计算与特殊情况相同的值。 (而且您遇到的特殊情况越少,检查代码就越容易。)
答案 1 :(得分:1)
如果我正确理解您的代码,您可能想尝试:
r.setDirection(glm::vec3(((x-imageWidth/2)/(float)imageWidth),
((imageHeight/2-y)/(float)imageHeight),
-1.0f));
现在,您已经将相机放置在离屏幕一个单位的位置,但是光线可以向右和向下拍摄多达400个单位。这是一个非常广泛的视野。而且,你的光线只扫过一个空间。这就是为什么你只能在屏幕的左上角看到一些像素。我上面写的代码应该纠正。