RustでRayTracing (チャプター5、6)

9 min読了の目安(約8800字TECH技術記事

チャプター5

球を表示するプログラムでした。
cgmathのドキュメント見てたら、castってのがあったので、それを使ってto_colorの実装を書き直しました。
あとは記事通り実装するだけでしたね。

前の実装の差分だけ書いておきますね。

fn to_color(v: Vector3<f32>) -> Vector3<i32>
{
    const scale: f32 = 255.99;

    return vec3(scale * v.x, scale * v.y, scale * v.z).cast().unwrap();
}

fn ray_color(ray: &Ray) -> Vector3<i32>
{
    const white: Vector3<f32> = vec3(1.0, 1.0, 1.0);
    const blue: Vector3<f32> = vec3(0.5, 0.7, 1.0);
    const red: Vector3<f32> = vec3(1.0, 0.0, 0.0);

    const center: Vector3<f32> = vec3(0.0, 0.0, -1.0);

    if (hit_sphere(center, 0.5, &ray)) {
        return to_color(red);
    }

    let unit_direction = ray.direction.normalize();
    let t = 0.5 * (unit_direction.y + 1.0);
    let rgb = white.lerp(blue, t);

    return to_color(rgb);
}

fn hit_sphere(center: Vector3<f32>, radius: f32, ray:&Ray) -> bool {
    let oc = ray.origin - center;
    let a = dot(ray.direction, ray.direction);
    let b = 2.0 * dot(oc, ray.direction);
    let c = dot(oc, oc) - radius * radius;

    return  b * b - 4.0 * a * c > 0.0;
}

チャプター 6.1

法線ベクトルを表示するところでした。
blenderで法線ベクトル表示するところはこんな感じなのかなー。
vec3(1.0, 1.0, 1.0)じゃなくて、Vector3::Oneとかでやりたいな。

前の実装と変えたところだけ書いておきますね。

fn ray_color(ray: &Ray) -> Vector3<i32>
{
    const white: Vector3<f32> = vec3(1.0, 1.0, 1.0);
    const blue: Vector3<f32> = vec3(0.5, 0.7, 1.0);

    const center: Vector3<f32> = vec3(0.0, 0.0, -1.0);

    let t = hit_sphere(center, 0.5, &ray);

    if (t > 0.0) {
        let n = (ray.at(t) - center).normalize();
        return to_color(0.5 * (n + vec3(1.0, 1.0, 1.0)));
    }

    let unit_direction = ray.direction.normalize();
    let t = 0.5 * (unit_direction.y + 1f32);
    let rgb = white.lerp(blue, t);

    return to_color(rgb);
}

fn hit_sphere(center: Vector3<f32>, radius: f32, ray:&Ray) -> f32 {
    let oc = ray.origin - center;
    let a = dot(ray.direction, ray.direction);
    let b = 2.0 * dot(oc, ray.direction);
    let c = dot(oc, oc) - radius * radius;

    let discriminant = b * b - 4.0 * a * c;

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

チャプター6.2

演算の強度を落としてるんですね。
計算量が増えたら、効果ありそう。

追加したコードを書いておきます。

fn length_squared(v: Vector3<f32>) -> f32 {
    v.x * v.x + v.y * v.y + v.z * v.z
}

fn hit_sphere(center: Vector3<f32>, radius: f32, ray:&Ray) -> f32 {
    let oc = ray.origin - center;
    let a = length_squared(ray.direction);
    let half_b = dot(oc, ray.direction);
    let c = length_squared(oc) - radius * radius;

    let discriminant = half_b * half_b - a * c;

    if discriminant < 0.0 {
        return -1.0;
    }
    else {
        return (- half_b - discriminant.sqrt()) / a;
    }
}

チャプター6.3 - 6.7

  • set_face_normalをやろうとするとmutにしなきゃいけないのが嫌だったので、Sphereでは直接計算してます。
  • Vector3でi32はColorって名前で扱えるように変更。コードがかっこよくなるかなと。

チャプター6はけっこうなボリュームでした。

コードを全部書いておきます。

use std::fmt;
use cgmath::prelude::*;
use cgmath::{Vector3, vec3, dot};

type Color = Vector3<i32>;

pub struct HitRecord {
    pub p: Vector3<f32>,
    pub normal: Vector3<f32>,
    pub t: f32,
    pub front_face: bool
}

impl HitRecord {
    pub fn set_face_normal(&mut self, ray: &Ray, outward_normal: Vector3<f32>) {
        self.front_face = dot(ray.direction, outward_normal) < 0.0;
        self.normal = if self.front_face {
            outward_normal
        } else {
            -outward_normal
        }
    }
}

pub trait HitTable {
    fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord>;
}

pub struct HitTableList {
    list: Vec<Box<dyn HitTable>>
}

impl HitTableList {
    pub fn new() -> Self {
        HitTableList {
            list: vec![],
        }
    }

    pub fn push(&mut self, hitable: impl HitTable + 'static) {
        self.list.push(Box::new(hitable))
    }

    pub fn clear(&mut self) {
        self.list.clear()
    }
}

impl HitTable for HitTableList {
    fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
        let mut closest_so_far = t_max;
        let mut hit_anything: Option<HitRecord> = None;
        for h in self.list.iter() {
            if let Some(hit) = h.hit(ray, t_min, closest_so_far) {
                closest_so_far = hit.t;
                hit_anything = Some(hit);
            }
        }
        hit_anything
    }
}

pub struct Sphere {
    center: Vector3<f32>,
    radius: f32,
}

impl Sphere {
    pub fn new(center: Vector3<f32>, radius: f32) -> Self {
        Sphere {center, radius}
    }
}

impl HitTable for Sphere {
    fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
        let oc = ray.origin - self.center;
        let a = length_squared(ray.direction);
        let half_b = dot(oc, ray.direction);
        let c = length_squared(oc) - self.radius * self.radius;
    
        let discriminant = half_b.powi(2) - a * c;

        if discriminant > 0.0 {
            let sqrtd = discriminant.sqrt();

            // Find the nearest root that lies in the acceptable range.
            let mut root = (- half_b - sqrtd) / a;
            if root < t_min || t_max < root {
                root = (- half_b + sqrtd) / a;
                if root < t_min || t_max < root {
                    return None
                }
            }

            let p = ray.at(root);
            let outward_normal = (p - self.center) / self.radius;

            // set face normal 
            let front_face = dot(ray.direction, outward_normal) < 0.0;
            let normal = if front_face {outward_normal} else {-outward_normal};

            return Some(HitRecord{t: root, p: p, normal: normal, front_face: front_face});
        }

        None
    }
}

pub struct Ray {
    pub origin: Vector3<f32>,
    pub direction: Vector3<f32>
}

impl Ray {
    pub fn new(o:Vector3<f32>, d:Vector3<f32>) -> Self {
        Ray{origin: o, direction: d}
    }

    pub fn at(&self, t: f32) -> Vector3<f32> {
        self.origin + self.direction * t
    }
}

impl fmt::Debug for Ray {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({:?}, {:?})", self.origin, self.direction)
    }
}

fn to_color(v: Vector3<f32>) -> Color
{
    const scale: f32 = 255.99;

    return vec3(scale * v.x, scale * v.y, scale * v.z).cast().unwrap();
}

fn ray_color(ray: &Ray, world: &HitTable) -> Color
{
    const white: Vector3<f32> = vec3(1.0, 1.0, 1.0);
    const blue: Vector3<f32> = vec3(0.5, 0.7, 1.0);

    if let Some(hit) = world.hit(ray, 0.0, f32::INFINITY) {
        return to_color(0.5 * (hit.normal + white));
    }

    let unit_direction = ray.direction.normalize();
    let t = 0.5 * (unit_direction.y + 1f32);
    let rgb = white.lerp(blue, t);

    return to_color(rgb);
}

fn length_squared(v: Vector3<f32>) -> f32 {
    v.x * v.x + v.y * v.y + v.z * v.z
}

fn hit_sphere(center: Vector3<f32>, radius: f32, ray:&Ray) -> f32 {
    let oc = ray.origin - center;
    let a = length_squared(ray.direction);
    let half_b = dot(oc, ray.direction);
    let c = length_squared(oc) - radius * radius;

    let discriminant = half_b * half_b - a * c;

    if discriminant < 0.0 {
        return -1.0;
    }
    else {
        return (- half_b - discriminant.sqrt()) / a;
    }
}


fn main() {

    // Image

    const aspect_ratio: f32 = 16.0 / 9.0;
    const image_width: i32 = 400;
    const image_height: i32 = (image_width as f32 / aspect_ratio) as i32;

    // World

    let mut world = HitTableList::new();
    world.push(Sphere::new(vec3(0.0, 0.0, -1.0), 0.5));
    world.push(Sphere::new(vec3(0.0, -100.5, -1.0), 100.0));

    // Camera

    let viewport_height: f32 = 2.0;
    let viewport_width = aspect_ratio * viewport_height;
    let focal_length = 1.0;

    let origin: Vector3<f32> = Vector3::zero();
    let horizontal: Vector3<f32> = vec3(viewport_width, 0.0, 0.0);
    let vertical: Vector3<f32> = vec3(0.0, viewport_height, 0.0);
    let lower_left_corner = origin - horizontal / 2.0 - vertical / 2.0 - vec3(0.0, 0.0, focal_length);

    // Render

    println!("P3\n {} {} \n255", image_width, image_height);

    for j in (0..image_height).rev() {
        for i in 0..image_width {
            let u = i as f32/(image_width - 1) as f32;
            let v = j as f32/(image_height - 1) as f32;
            let r = Ray::new(origin, lower_left_corner + u * horizontal + v * vertical - origin);
            let pixel_color = ray_color(&r, &world);
            println!("{} {} {}", pixel_color.x, pixel_color.y, pixel_color.z);

        }
    }
}