下面的公式有点多,只是把原教程中的公式详细展开了,一步一步来,很简单,相信我。
给定一个点,判断一个点是否在一个半径为 的圆或者圆外、圆内,我们可以使用下面的公式来判断。这里假设圆心在 点,半径为
如果一个点 在圆上,则
如果一个点 在圆内,则
如果一个点 在圆外,则
假设圆心在点 ,半径为 r,则根据上面的第一个公式,可以得到
我们可以将点 使用之前的 Vec3 向量表示,例如设 ,则可以将上面的公式,换一种表达形式,也就是
还记得我们的光线公式吗?,A是光线的起点,b是光线的方向,t是一个缩放参数,而得到的 则是光线打到的点。我们想知道一条光线所打到的点,是否在圆上或者圆内。是不是只要将光线公式,代入上面的公式,就可以了?也就是将原来的点P,换成了光线函数 P(t),就会得到下面的公式
由于 ,所以我们展开上面的公式,可以得到
而上面的公式,又可以换成另一种表达形式
这个公式就是我们以前很熟悉的 的形式,展开形式就是
那么,我们将公式 用同样的方式展开,则可以得到
整理一下,我们可以看出,上面的第1项、第3项、第6项,可以组合到一起组成一个公式,也就是 ,修改上面的式子,可以得到
上面的式子中 和 都有一个 ,可以组合一下,得到 ,用这种形式修改上面的公式,得到
而 可以换成一种形式,表示成 ,再换一种形式可以为 ,用这种形式修改上面的公式,可以得到最终的公式如下
将 移到公式左边,可以得到
这就是最终的公式了,为什么要绕这么多换成这样一个形式呢,我猜作者是为了计算方便,写代码方便。
对于上面的公式,我们已经知道 A 是光线的起点,C 是圆心,而 b 是光线的方向,r 是圆的半径,唯一不知道的变量就是 t,而上面的方程,是一个一元二次方程,根据上学时学的知识,我们知道一元二次方程的解,可能有0个,1个,或2个。对于上面公式,也就是我们渲染圆的目标而已,只要有 > 0 个解,则说明光线打到了圆。
接下来还没完,我们如何知道上面的公式解的个数呢?根据一元二次方程的形式
它的根可以表示为
对于上面公式中的 ,如果结果大于 0,则方程有两个不相等的实数根,等于 0,则方程有两个相等的实数根,小于 0,则方程无实数根。
所以,我们只需要将之前的公式
表示成
的形式,则可以很方便的知道方程有几个解。
对于我们而言,上面的公式,除了 之外,其他的变量都是已知道的,我们直接在代码中去转换。这次的代码沿用上一小节的,然后在 main.rs 中添加一个函数 hit_sphere
fn hit_sphere(center: Vec3, radius: f64, r: Ray) -> bool {
// 公式中的 (A - C)
let oc = r.origin - center;
// 公式中第1项的 b*b
let a = Vec3::dot(r.direction, r.direction);
// 公式中第2项的内容,忽略 t
let b = 2.0 * Vec3::dot(oc, r.direction);
// 公式中的 (A - C) * (A - C) - r^2
let c = Vec3::dot(oc, oc) - radius * radius;
// 计算出了 a, b, c,判断 b^2 - 4ac 解的个数
let result = b * b - 4.0 * a * c;
// 解的个数 >= 0,则打到了圆
return result >= 0.0;
}
然后在 ray_color 函数中添加是否打到圆的判断,如果打到了圆,我们就返回当前像素为红色
fn ray_color(r: Ray) -> Color {
if hit_sphere(Vec3::new(0.0, 0.0, -1.0), 0.5, r) {
return Color::new(1.0, 0.0, 0.0);
}
// 将光线的方向标准化,保证其值在 -1 到 1 之间
let unit_direction = Vec3::unit_vector(r.direction);
// 为了计算方便,我们将方向的 y 值,从 [-1,1] 映射到 [0, 1]
let t = 0.5 * (unit_direction.y + 1.0);
// 做一个蓝白渐变,当 t 为 0 时,就是白色,将 t 为 1 时,就是蓝色
return (1.0 - t) * Color::one() + t * Color::new(0.5, 0.7, 1.0);
}
cargo run > sphere.ppm 最终效果图如下
任何技术问题,欢迎加微信 ifloop 互相交流,或者关注我的微信公众号 萌一小栈
### 另外 另外,也欢迎体验我的2个 独立游戏 ,直接点击下载连接,可试玩