はじめに
お疲れ様です。
レンダリングの手法には多岐に渡りますが、
ひとまず一番基本的な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に演算子(+, -, *, /)を定義する
演算子のオーバーライドしていきます。
演算子については、Add
、Sub
、Mul
、Div
で提供されています。
以下Add
とSub
の実装です。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],
)
}
}
次に、Mul
とDiv
の実装をします。こちらについては、同じデータ構造同士の計算ではなく、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
で下記が出力されれば、期待した出力となっています。

おわり
ここまで、目を通していただきありがとうございます。
今回は、Add
、Sub
、Mul
、Div
を設定しました。
他にも+=
、-=
、*=
、/=
、-
等の定義をする方法もあります。下のような感じで実装できるので、追加で実装するのもありかもしれないです。
use std::ops::AddAssign;
impl AddAssign for Tuple3 {
fn add_assign (&mut self, v: Tuple3) {
*self = *self + v; // Addを実装していないとエラーになります。
}
}