[Rust] レイトレーシング作る – 1

はじめに

お疲れ様です。
レンダリングの手法には多岐に渡りますが、
ひとまず一番基本的なRay tracingから実装していこうかと思います。
綺麗なコードは、探せばあると思いますが一旦自力で書いていこうと思います。

Rust初心者ということもあるので、
最初からゴリゴリにやるのではなくRust言語の雰囲気を見ながら実装していこうと思います。
改めてよろしくお願いいたします。

基本的に座標計算とベクトルの計算が主になるので、
utilフォルダーを作って適当にまとめていこうと思います。
Rust言語特有の概念があれば都度別のページでまとめる感じで進めていこうかと思います。

前準備

何をするにも、プロジェクトを作らないと始まらないので、作っていきます。
適当な作業ディレクトリに移動して下記のコマンドを実行します。(プロジェクト名は適当です。)

ひとまず、次の階層を目指して準備します。

my_rendere
└ src
  ┝ util
  │ └ vector
 └ main.rs

上記の構造を作るのは下記でいけます。

cargo new my_renderer
cd my_renderer
cd src
mkdir util; cd util
mkdir vector;

Vector3みたいなデータ構造を定義して、書いていく方針でも良かったんですが、3つの値を扱うデータの形って結構あります。色とか?なので、Vector3 の前にTuple3的なものを作って順次implで実施していく方針でいこうと思います。

(後書き)→という方針でしたが、同じ型で扱われているようで今後実装する予定のものはTuple3でも利用できるみたいです、、、不覚です。

モジュールの設定

モジュールとはなんぞって感じですが、プロジェクト配下のフォルダーまたはファイルという解釈で十分だと思います。ただ、モジュールとして扱うために前準備があるのでお気をつけください。

今日のケースですと下記のファイルを追加します。

my_rendere
└ src
  ┝ util
  │ ┝ vector.rs (今日は作らないけど、vectorをモジュールとみなすために必要)
  │ ┝ tuple3.rs (これを今日作る!)
  │ └ vector
 ┝ main.rs
  └ util.rs (utilをモジュールとみなすために必要)

util フォルダーはモジュールですよと知らせます。util.rsの中身は、フォルダー内のファイルをモジュールとみなすかどうかを指定します。次のような感じです。

// util.rs
pub mod vector;
pub mod tuple3;

main.rsにも追加すれば、rustの構文チェックとかも動作します。(まぁ、自分はVim + cocでやっているので、VisualStudiocodeとかで拡張機能入れたらチェックが入るかもしれないです。)

// main.rs
pub mod util;

fn main () {
    println!("Hello, Ray Tracing");
}

Tuple3を定義する

3つの値を同時に扱うデータ構造を作ります。内積とか外積とかを将来的に実装するので、Vector3で良いと思います。個人的には使い回せるところは使い回して、不要なものは実装したくないです。なので、最初にVector3を実装すると、実装が被るか、不要な実装をしてしまいそうなのでコイツを作ります。

今回のTuple3の目的は、+, -, *, / の演算子を実装して置いて後ほど楽をしたいというのが目的です。
ひとまず、データの構造を作ります。他に必要な実装はあるかもしれないですが、必要になったら順次追加していく方針です。

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Tuple3 {
   pub e: [f64; 3]
}

impl Tuple3 {
    pub fn new(e1:f64, e2:f64, e3:f64) -> Self {
        Tuple3{ e: [e1, e2,f e3]}
    }

    pub fn len() -> usize {
        3
    }
}

Tuple3に演算子(+, -, *, /)を定義する

演算子のオーバーライドしていきます。
演算子については、AddSubMulDivで提供されています。

以下AddSubの実装です。Tuple3のためにAddまたは、Subを実装するぞ、って書き方です。

use std::ops::{Add, Sub, Mul, Div};

// Tuple3 + Tuple3
impl Add for Tuple3 {
    type Output = Tuple3;
    fn add(self, other: Self) -> Self::Output {
        Tuple3::new(
            self.e[0]+other.e[0],
            self.e[1]+other.e[1],
            self.e[2]+other.e[2],
        )
    }
}

//Tuple3 - Tuple3
impl Sub for Tuple3 {
    type Output = Tuple3;
    fn sub(self, other: Self) -> Self::Output {
        Tuple3::new(
            self.e[0]-other.e[0],
            self.e[1]-other.e[1],
            self.e[2]-other.e[2],
        )
    }
}

次に、MulDivの実装をします。こちらについては、同じデータ構造同士の計算ではなく、f64との計算です。言い換えると異なる形同士の計算を実装します。

// f64 * Tuple3
impl Mul<Tuple3> for f64 {
    type Output = Tuple3;
    fn mul(self, other: Tuple3) -> Self::Output {
         Tuple3::new(
            self * other.e[0],
            self * other.e[1],
            self * other.e[2],
        )
    }
}
// Tuple3 * f64
impl Mul<f64> for Tuple3 {
    type Output = Tuple3;
    fn mul(self, t: f64) -> Self::Output {
        Tuple3::new(
            self.e[0] * t,
            self.e[1] * t,
            self.e[2] * t,
        )
    }
}
// Tuple3 / f64
impl Div<f64> for Tuple3 {
    type Output = Tuple3;
    fn div(self, t: f64) -> Self::Output {
        Tuple3::new(
            self.e[0] / t,
            self.e[1] / t,
            self.e[2] / t,
        )
    }
}

下記で、動作確認したら、おしまい!今日はここまで!

mod util;

use util::tuple3::Tuple3;

fn main() {
    let a = Tuple3::new(1.0, 2.0, 3.0);
    let b = Tuple3::new(3.0, 2.0, 1.0);

    println!("{:?}", a+b);
    println!("{:?}", a-b);
    println!("{:?}", 10.0 * a);
    println!("{:?}", a * 10.0);
    println!("{:?}", a / 10.0);
}

cargo runで下記が出力されれば、期待した出力となっています。

おわり

ここまで、目を通していただきありがとうございます。
今回は、AddSubMulDivを設定しました。
他にも+=-=*=/=-等の定義をする方法もあります。下のような感じで実装できるので、追加で実装するのもありかもしれないです。

use std::ops::AddAssign;

impl AddAssign for Tuple3 {
    fn add_assign (&mut self, v: Tuple3) {
        *self = *self + v; // Addを実装していないとエラーになります。
    }
}