想看原书可以看本系列的第一篇《Ray Tracing in a Weekend》学习笔记01
5. Adding a Sphere
让我们向光线追踪器添加一个对象。 人们通常在光线跟踪器中使用球体,因为计算射线是否撞击球体非常简单。
5.1 Ray-Sphere Intersection
当逐行扫描屏幕的所有像素点,从摄像机位置发出对应的视线,视线与球体会发生三种碰撞,一是视线不与球体相交,那屏幕上看到的还是背景颜色,二是视线与球体相切,三是视线与球体相交。我们只需推算出直线与球是否相交的公式。简单的公式推导,得出的公式:
t
2
b
⋅
b
+
2
t
b
⋅
(
A
−
C
)
+
(
A
−
C
)
⋅
(
A
−
C
)
−
r
2
=
0
t^2b⋅b+2tb⋅(A−C)+(A−C)⋅(A−C)−r^2=0
t2b⋅b+2tb⋅(A−C)+(A−C)⋅(A−C)−r2=0
相当于求t的一元二次方程,b是光线的方向向量,A是光线的发射位置,C是球体的中心位置,r是球体半径。
5.2Creating Our First Raytraced Image
如果我们采用该数学并将其硬编码到我们的程序中,则可以通过将位于z轴上-1处的小球碰到的任何像素变为红色来对其进行测试:
//main.c
bool hit_sphere(const point3& center, double radius, const ray& r) {
vec3 oc = r.origin() - center;//oc=A-C(-号在vec3.h中重载了,所以返回的还是vec3)
auto a = dot(r.direction(), r.direction());
auto b = 2.0 * dot(oc, r.direction());//b·(A-C)
auto c = dot(oc, oc) - radius*radius;//(A−C)⋅(A−C)-r*r
auto discriminant = b*b - 4*a*c;
return (discriminant > 0);//>0表示一元二次方程有解,则光线与球体有交集
}
color ray_color(const ray& r) {
if (hit_sphere(point3(0,0,-1), 0.5, r))
return color(1, 0, 0);//光线击中的局域,都为(1,0,0),红色
vec3 unit_direction = unit_vector(r.direction());
auto t = 0.5*(unit_direction.y() + 1.0);
return (1.0-t)*color(1.0, 1.0, 1.0) + t*color(0.5, 0.7, 1.0);
}
小总结:vec3.h的功能就是定义了三维向量,包括三维向量之间的点乘,叉乘,加减乘除等操作。颜色、距离、位置等都用vec3来表示;color.h定义了RGB的输出,把范围固定在了[0,255]中。
6.Surface Normals and Multiple Objects
6.1Surface Normals and Multiple Objects
Surface normals(表面法线),可以帮助我们区分出背面。当视线穿过球体,穿过去交点为P,则法线向量是P-C。
再结合视线公式 P(t) = a + t ·b,和
t
2
b
⋅
b
+
2
t
b
⋅
(
A
−
C
)
+
(
A
−
C
)
⋅
(
A
−
C
)
−
r
2
=
0
t^2b⋅b+2tb⋅(A−C)+(A−C)⋅(A−C)−r^2=0
t2b⋅b+2tb⋅(A−C)+(A−C)⋅(A−C)−r2=0
①当视线与球体相交的时候,t可取两个值,分别为 t1=(-b - sqrt(b * b - 4ac))/ (2.0 * a), t2=(-b + sqrt(b * b - 4ac))/ (2.0 * a),t1是穿过去的交点P,t2是视线穿进球体的交点。当视线与球体相切时,t1=t2。
②得到t后,调用ray.h的at(),计算出该点的P(t),从而法线向量等于P(t)-C,对其进行单位化得到单位法线向量。
③返回一个color类的变量,把大小控制在(0,1)
所以需要更改main.c的代码,如下:
//main.c
double hit_sphere(const point3& center, double radius, const ray& r) {
vec3 oc = r.origin() - center;
auto a = dot(r.direction(), r.direction());
auto b = 2.0 * dot(oc, r.direction());
auto c = dot(oc, oc) - radius*radius;
auto discriminant = b*b - 4*a*c;
if (discriminant < 0) {
return -1.0;
} else {
return (-b - sqrt(discriminant) ) / (2.0*a);
}
}
color ray_color(const ray& r) {
auto t = hit_sphere(point3(0,0,-1), 0.5, r);
if (t > 0.0) {
vec3 N = unit_vector(r.at(t) - vec3(0,0,-1));
return 0.5*color(N.x()+1, N.y()+1, N.z()+1);
}
vec3 unit_direction = unit_vector(r.direction());
t = 0.5*(unit_direction.y() + 1.0);
return (1.0-t)*color(1.0, 1.0, 1.0) + t*color(0.5, 0.7, 1.0);
}
6.2 Simplifying the Ray-Sphere Intersection Code
对hit_sphere()进行优化:
①向量与自身的点乘,等于向量的长度的平方(在vec3.h中定义的length_squared()就是这个原理)
②假定b=2h,对二次方程的结果进行优化,
−
b
±
b
2
−
4
a
c
2
a
=
−
2
h
±
(
2
h
)
2
−
4
a
c
2
a
=
−
2
h
±
2
h
2
−
a
c
2
a
=
−
h
±
h
2
−
a
c
a
\frac{-b \pm \sqrt{b^2 - 4ac}}{2a} =\frac{-2h \pm \sqrt{(2h)^2 - 4ac}}{2a} =\frac{-2h \pm 2\sqrt{h^2 - ac}}{2a} =\frac{-h \pm \sqrt{h^2 - ac}}{a}
2a−b±b2−4ac=2a−2h±(2h)2−4ac=2a−2h±2h2−ac=a−h±h2−ac
//main.c
double hit_sphere(const point3& center, double radius, const ray& r) {
vec3 oc = r.origin() - center;
auto a = r.direction().length_squared();
auto half_b = dot(oc, r.direction());//b=2h,只取原来的一半
auto c = oc.length_squared() - radius*radius;
auto discriminant = half_b*half_b - a*c;
if (discriminant < 0) {
return -1.0;
} else {
return (-half_b - sqrt(discriminant) ) / a;
}
}