blockの巣

Rustでコンパイル時レイトレーシング3

2022/02/04 00:20 公開
2022/02/05 12:52

背景色が間違っていたのを修正

2022/02/16 01:38

背景のグラデーションの向きの修正とリポジトリへのリンクを修正

Compile Time Ray Tracing in Rust Ray Tracing Rust

前回記事
リポジトリ
記事の内容はコミットIDa22c5917e25e505aaeefe75e34121e1817f9ae6cのものです。

今回はようやくレイを飛ばすところまで行けました!
といってもオブジェクトにヒットするとかはしておらずレイの向きによって色が変わっているだけなのでまだレイトレーシングっぽくないですね。

レンダリング結果
レンダリング結果

今回の変更点

ray.rs

まずRay型の内容からです。 レイの始点と方向を持つだけのstructです。特別注意が必要なことはないと思います。

use crate::{Point3, Vec3};

#[derive(Clone, Copy, Debug)]
pub struct Ray {
    pub orig: Point3,
    pub dir: Vec3,
}

impl Ray {
    pub const fn new(origin: &Point3, direction: &Vec3) -> Self {
        Ray {
            orig: origin.clone(),
            dir: direction.clone(),
        }
    }

    pub const fn origin(&self) -> Point3 {
        self.orig
    }

    pub const fn direction(&self) -> Vec3 {
        self.dir
    }

    pub const fn at(&self, t: f64) -> Vec3 {
        self.orig + self.dir * t
    }
}

lib.rs

lib.rsの内容はray moduleの宣言などの他const_eval_limitというfeatureの宣言が増えています。

#![feature(const_trait_impl)]
#![feature(const_fn_floating_point_arithmetic)]
#![feature(const_mut_refs)]
#![feature(const_eval_limit)]
#![const_eval_limit = "0"]
pub mod ray;
pub mod util;
pub mod vec3;

pub use ray::*;
pub use vec3::*;

pub type Point3 = Vec3;
pub type Color = Vec3;

const_eval_limit

const_eval_limitとはconst fnの中でのステップ数の制限をコントロールするためのfeatureです。

#![feature(const_eval_limit)]
#![const_eval_limit = "N"]

とすることでステップ数の制限をNに設定できます。N = 0のときは無制限になります。
これを使用しないとconst fnsqrtを呼び出しただけでステップ数の上限に引っかかってしまいまともにコードを書けそうになかったので追加しました。

main.rs

main.rsの内容を先に貼っておきます。 変更点がない箇所は省略しています。

main.rs
#![feature(const_trait_impl)]
#![feature(const_fn_floating_point_arithmetic)]
#![feature(const_mut_refs)]
#![feature(const_eval_limit)]
#![const_eval_limit = "0"]

use image::ImageFormat;
use ray_tracing_in_one_weekend_compile_time::*;

const ASPECT_RATIO: f64 = 16.0 / 9.0;
const IMAGE_WIDTH: usize = 400;
const IMAGE_HEIGHT: usize = (IMAGE_WIDTH as f64 / ASPECT_RATIO) as usize;
const PIXEL_COUNT: usize = IMAGE_WIDTH * IMAGE_HEIGHT;

const fn ray_color(ray: &Ray) -> Color {
    let unit_direction = unit_vector(&ray.direction());
    let t = 0.5 * unit_direction.y + 1.0;
    (1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0)
}

fn main() -> anyhow::Result<()> {
    // 省略
}

const fn ray_trace() -> [Color; PIXEL_COUNT] {
    let viewport_height = 2.0;
    let viewport_width = viewport_height * ASPECT_RATIO;
    let focal_length = 1.0;

    let origin = Point3::ZERO;
    let horizontal = Vec3::new(viewport_width, 0.0, 0.0);
    let vertical = Vec3::new(0.0, viewport_height, 0.0);
    let lower_left_corner =
        origin - horizontal / 2.0 - vertical / 2.0 - Vec3::new(0.0, 0.0, focal_length);

    let mut pixel_colors = [Color::ZERO; PIXEL_COUNT];
    let mut i = 0;
    let mut j = (IMAGE_HEIGHT - 1) as i32;
    let mut pixel_index = 0;
    // forが使えないのでwhileで代用
    while 0 <= j {
        while i < IMAGE_WIDTH {
            let u = (i as f64) / (IMAGE_WIDTH - 1) as f64;
            let v = (j as f64) / (IMAGE_HEIGHT - 1) as f64;
            let r = Ray::new(
                &origin,
                &(lower_left_corner + u * horizontal + v * vertical - origin),
            );
            pixel_colors[pixel_index] = ray_color(&r);
            i += 1;
            pixel_index += 1;
        }
        i = 0;
        j -= 1;
    }
    pixel_colors
}

// 省略

まずmain.rsでもconst_eval_limitが追加されています。 ループの中でステップ数の上限に引っかかるので上限なしにしてあります。

#![feature(const_eval_limit)]
#![const_eval_limit = "0"]

画像は横400を基準としてアスペクト比16:9になるように設定しています。

const ASPECT_RATIO: f64 = 16.0 / 9.0;
const IMAGE_WIDTH: usize = 400;
const IMAGE_HEIGHT: usize = (IMAGE_WIDTH as f64 / ASPECT_RATIO) as usize;

ray_color関数は引数のRayを飛ばしてヒットした箇所の色を返す関数です。
現時点ではヒットするオブジェクトがないのでレイの向きを元に返す色を決めています。

const fn ray_color(ray: &Ray) -> Color {
    let unit_direction = unit_vector(&ray.direction());
    let t = 0.5 * unit_direction.y + 1.0;
    (1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0)
}

ray_trace関数ではピクセルごとにレイを生成してray_color関数の返り値を配列に格納しています。

const fn ray_trace() -> [Color; PIXEL_COUNT] {
    let viewport_height = 2.0;
    let viewport_width = viewport_height * ASPECT_RATIO;
    let focal_length = 1.0;

    let origin = Point3::ZERO;
    let horizontal = Vec3::new(viewport_width, 0.0, 0.0);
    let vertical = Vec3::new(0.0, viewport_height, 0.0);
    let lower_left_corner =
        origin - horizontal / 2.0 - vertical / 2.0 - Vec3::new(0.0, 0.0, focal_length);

    let mut pixel_colors = [Color::ZERO; PIXEL_COUNT];
    let mut i = 0;
    let mut j = (IMAGE_HEIGHT - 1) as i32;
    let mut pixel_index = 0;
    // forが使えないのでwhileで代用
    while 0 <= j {
        while i < IMAGE_WIDTH {
            let u = (i as f64) / (IMAGE_WIDTH - 1) as f64;
            let v = (j as f64) / (IMAGE_HEIGHT - 1) as f64;
            let r = Ray::new(
                &origin,
                &(lower_left_corner + u * horizontal + v * vertical - origin),
            );
            pixel_colors[pixel_index] = ray_color(&r);
            i += 1;
            pixel_index += 1;
        }
        i = 0;
        j -= 1;
    }
    pixel_colors
}

まとめ

今回でようやくRayが登場しました。
かんたんに書くためにまた新しいconst_eval_limitというfeatureを使用しました。 どんどんnightlyでしか書けないコードになってきましたね。stableの制限の中でもやりたいと思ったことはあるのですがだいぶ難しそうです。
次は球とレイの交差判定を行います。