判断射线与是否球体相交, 并计算交点位置

在Ray-tracing中, 计算并判断射线与球体是否相交是不可少的
那么如何来判断一条已知的射线是否交于给定的球体呢?
要计算球体射线交点,我们首先要先给出球与直线的方程

首先是球面方程

$$|x-c|^2=r^2$$

  • x为球面上的点
  • c为球心
  • r为球的半径 然后是直线方程

$$x = o +dl$$

  • x为直线上的点
  • o为直线起点
  • d为交点到原点的距离(一个标量)
  • l为射线方向的单位向量

相信方程都很清晰明了,那下面我们要做的就是通过上述两个式子中共有的 X 将方程联立起来

联立,得

$$|o+dl-c|^2=r^2$$

展开,得

$$d^2l^2+2dl(o-c)+(o-c)^2=r^2$$

整理,得

$$l^2d^2+2l(o-c)d+(o-c)^2-r^2=0$$

这时我们便得到了一个关于d的二元一次方程, 不难看出判别式$$\Delta=B^2-4AC$$
其中:
$$A=l^2$$
$$B=2l*(0-c)$$
$$C=(o-c)^2-r^2$$

根据其解的个数, 我们便能判断射线与球体的相交情况了
交点个数与根的关系简图

根的判别式Δ 交点个数 相交情况
<0 0 直线与球无交点
=0 1 直线与球相切

0 | 2 | 直线传过球体

此时我们根据求根公式, 便能快速算出d的值, 再通过射线的方程$$x=o+dl$$便可求出其交点

一个c++风格的 判断直线与球是否相交的代码实现如下

1
2
3
4
5
6
7
8
9
10
11
12
//Line–sphere intersection
bool hit_sphere(const vec3& center, float radius, const ray& r) {
vec3 oc = r.Origin() - center;
float a = dot(r.Direction(), r.Direction());//2
float b = 2.0f * dot(r.Direction(), oc);
float c = dot(oc, oc) - radius*radius;
float discriminate = b * b - 4 * a * c;
if (discriminate < 0)
return false;
else
return ( - b - sqrtf(discriminate)) /( 2.0f * a)>0;//1
}

1 注意, 射线(ray)和直线(line)的区别, 射线是有其方向的, 直线方程的解不一定符合射线, 我们需要将负解舍去

2 r.Direction() 即上述公式中的 L , 其实并不需要一定是单位向量, 只要保证其正负与单位向量一致即可

如果在绘制球体的过程, 需要求出交点的具体位置, 比如根据球面法相来输出像素颜色(将三维向量直接作为颜色输出, 在调试你的程序时是十分常用的), 那么上面的代码就不太够看了, 我们对其略做修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Line–sphere intersection
float hit_sphere(const vec3& center, float radius, const ray& r) {
vec3 oc = r.Origin() - center;
float a = dot(r.Direction(), r.Direction());//3
float b = dot(r.Direction(), oc);//1
float c = dot(oc, oc) - radius*radius;
float discriminate = b * b - a * c;//1
if (discriminate < 0)
return -1.0f;
else
return ( - b - sqrtf(discriminate)) / a;//1,2
}

//some code that get normal
Ray r;
...
d = hit_sphere(center, radius, r);
vec3 normal = Normalize(r.Origin() + r.Direction() * d - center);

1 这里其实可以化简, 判别式上下同时消掉了一个2

2 根据求根公式, 我们会得到两个根, 求交点时, 我们选取较小的根d, 即离射线原点(往往是camera)较近的那个点

3 根据RayTracingInOneWeekend书中的代码, 这里的r.Direction()仍不需一定为单位向量, 只要在后续计算中保持公式 x = r + dl 的一致便仍能得出正确的交点, 读者可自行根据上述公式进行验算。但我个人认为如wikipedia中, 在一开始将Ray类中的方向向量初始化为单位向量会比较方便, 避免一些忘记手动Normalize而导致的潜在的bug

一个初始化的例子

1
2
3
4
Ray(const vec3& origin, const vec3& dir){
...
this->dir = dir.Normalize();
}

reference:

https://en.wikipedia.org/wiki/Line-sphere_intersection
https://raytracing.github.io/books/RayTracingInOneWeekend.html

判断射线与是否球体相交, 并计算交点位置

https://matrix4f.com/Math/Geometry/line-sphere-intersection/

Author

oxine

Posted on

2020-02-26

Updated on

2020-03-19

Licensed under

Comments

昵称处填入QQ号,自动同步QQ头像与ID