はじめに
お疲れ様です。前回の記事で長くとも1月以上はあけないと言いつつあけてしまいました、、、。
言い訳をすると、Ratatuiっていうライブラリをいじっていました。レイトレ関係ないから関係ないなとか思ってサボりました。。。そのうちRatatuiを題材にして書いても良いかなと迷走しているので気が向いたら書くかもです。。。。
流石に、レイトレの続きを実装したくて3連休に実装してたらローカルの進捗と記事の進捗に結構開きが出てしまいました。本当は、円錐の内容をまとめようかと思ったのですが、ちょっと体力がないので、元気な時にまとめます。当分のメインは、rendererの箇所を整理などをまとめようと思います。
今の手元がこんな感じ、、、

改めて何するか、、、
改めて当分何をするか、まとめておきます。大きく分けると下記です。
- Rendererの実装の分離
- Samplerの実装
- Materialの実装
それぞれの意図としては下記です。
Rendererは、今mainに直書きしているので分離させます。Samplerは、ピクセルに対して1つの光線しか投げていないので複数本投げて曲線とかをなめらかに表示できるようにします。Materialは、今法線を可視化しているだけなので、色をつけたり、反射を制御して金属っぽく表現したり、あとは光源みたいなのを表現したりするためのものです。
まぁ、ゆっくりまとめていこうと思います。
今回は、Rendererに分離するぞ、、、
Rendererですが、処理の流れを改めて整理すると下記のような感じになります。

実装していく
前節をもとに実装していくぞ!!
とりあえずデータ構造
今回は、これでやります。
次回以降、sample系を追加していきます。
pub struct Renderer {
pub samples_per_pixel: i32, // ←今回使わない,,,
pub max_depth: usize,
}
impl Debug for Renderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Renderer")
.field("samples_per_pixel", &self.samples_per_pixel)
.field("max_depth", &self.max_depth)
.finish()
}
}
実装するぞー
実装していきます。。。
impl Renderer {
pub fn new(samples_per_pixel: i32, max_depth: usize) -> Self {
Self {
samples_per_pixel,
max_depth,
}
}
pub fn render(&self, camera: &mut Camera, scene: &Scene) {
let height = camera.get_film().get_height();
let width = camera.get_film().get_width();
for y in 0..height {
for x in 0..width {
let color = self.render_pixel(camera, scene, x, y, width, height);
camera.record_pixel(x, y, color);
}
}
}
fn render_pixel(
&self,
camera: &Camera,
scene: &Scene,
x: usize,
y: usize,
width: usize,
height: usize,
) -> Color3 {
let mut color = Color3::zero();
let u = x as f64 / width as f64;
let v = y as f64 / height as f64;
let ray = camera.generate_ray(u, v);
let color = self.trace_ray(&ray, scene, self.max_depth);
color
}
fn trace_ray(&self, ray: &Ray, scene: &Scene, depth: usize) -> Color3 {
if depth <= 0 {
return Color3::zero();
}
if let Some((_object_index, intersection)) =
scene.intersect(ray, f64::EPSILON, f64::INFINITY)
{
let normal = intersection.normal;
Color3::new(
0.5 * (normal.get_x() + 1.0),
0.5 * (normal.get_y() + 1.0),
0.5 * (normal.get_z() + 1.0),
)
} else {
let t = 0.5 * (ray.get_direction().unit().get_element(1) + 1.0);
let white = Color3::new(1.0, 1.0, 1.0);
let blue = Color3::new(0.5, 0.7, 1.0);
white * (1.0 - t) + blue * t
}
}
}
動作確認
動作確認のための、main部分置いておきます。
動作確認用
pub fn main() -> Result<(), Box<dyn Error>> {
// result imge name
let img_path = "./build/plane_sphere.ppm";
// image size
let width = 256;
let height = 256;
// prepare film
let film = Film::new(height, width);
// prepare camera
let pos = Point3::new(0.0, 3.0, 4.0);
let look_at = Point3::new(0.0, 1.0, -1.0);
let up = Vector3::new(0.0, 1.0, 0.0);
let fov = 90.0_f64.to_radians();
let mut camera = Camera::new(pos, look_at, up, fov, film);
// prepare scene
let mut scene = Scene::new();
scene.add(Box::new(Plane::new(
Point3::new(0.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0)
)));
scene.add(Box::new(Sphere::new(
Point3::new(0.0, 1.0, -1.0),
1.0
)));
// output image phase
let img = ImageOutputFactory::create(ImageType::PPM);
renderer.render(&mut camera, &scene);
img.save(&camera.film, img_path).unwrap();
Ok(())
}

さいごに
今回動作確認する上で、下記の場所ミスってました。申し訳ないです、、、、。
widthとheightの比率が1:1 出なくてもうまく表示されるはずです。
pub fn set_pixel(&mut self, x: usize, y: usize, color: Rgb) {
if x >= self.width || y >= self.height {
return;
}
let index = y*self.width + x; // ←self.heightになっておった、すまぬ(20260321)
self.pixels[index] = self.pixels[index] + color;
}

