法线的定义是垂直于面的向量。对于一个球体来说,法线的向量,就是球上某个点,减去球心坐标,所得到的向量。

虽然法线向量看不到,但是我们可以将它的值,使用 RGB 颜色值的形式,显示出来,这样我们可以有一个大概的视觉上的了解。最终的效果图如下

在前一小节,我们只知道光线是否打到了球了,而现在要求法向量,我们就需要知道光线打到球的哪个位置。而一条光线打到球,可能有两个点,这里,我们先只取最近那个点的坐标。代码如下,在上一节代码的修改了 hit_sphere 函数和 ray_color 函数

//src main.rs
fn hit_sphere(center: Vec3, radius: f64, r: Ray) -> f64 {
    // 公式中的 (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;

    // -----------------------------------
    if result < 0.0 {
        return -1.0;
    } else {
        return (-b - result.sqrt()) / (2.0 * a);
    }
    // -----------------------------------
}

fn ray_color(r: Ray) -> Color {
    // -----------------------------------
    let t = hit_sphere(Vec3::new(0.0, 0.0, -1.0), 0.5, r);
    if t > 0.0 {
        let unit_normal = Vec3::unit_vector(r.at(t) - Vec3::new(0.0, 0.0, -1.0));
        return 0.5
            * Color::new(
                unit_normal.x + 1.0,
                unit_normal.y + 1.0,
                unit_normal.z + 1.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);
}

ray_color 函数中拿到 hit_sphere 函数返回的结果 t,然后通过 t 和光线的原点及方向,来计算出打到的位置,通过光线打到的位置,减去球心的坐标,就可以得出法向量,然后将法向量进行标准化,得到值 (x, y, z) 值在 -1 到 1 之间。

乘 0.5 和 加 1 的意思是将 -1 到 1 的值,映射到 0 到 1 之间,然后就可以将这个值作为颜色值来显示(着色)了。

这一小节完整代码:https://github.com/moeif/rtiow-rs/tree/04