[Rust] レイトレーシング 10

Rust

はじめに

お疲れ様です。今日は、簡単にSamplerの部分を作ろうかなと思っています。
現状だと、球など曲線が含んでいるときギザギザになってしまっています。
それをなるべく滑らかにする処理を加えようかなと思っています。

Samplerについて

現状、一つのピクセルに対して一つのRayを作成しシーンとの交差判定を行っています。
これにより、一つのピクセルの中に境界などがあるとRayが当たる交差点の色によってギザギザが現れてしまいます。これを防ぐために一つのピクセルに対して複数のRayを生成し平均化することでこれを防ぐことができます。

Samplerの実装

ひとまず、traitを作る。./src/sampler/sampler.rsに作ります。

pub trait Sampler: Send + Sync {
    fn generate_samples(&self, samples_per_pixels: i32) -> Vec<(f64, f64)>;
}

SendSyncの境界トレイトは、今回不要ですが並列処理させるときに必要になります。のでそのうちこの下にリンクを載せます。

RegularSampler

おそらく一番基本的なサンプラーとなります。ピクセルに対して規則的にRayを作成するためのものとなります。イメージとしては、下記の黒枠をピクセルとみなすと、ピクセル内で等間隔でサンプル点を決定していく方法となります。

pub struct RegularSampler;

impl RegularSampler {
    pub fn new() -> Self {
        Self
    }
}

impl Sampler for RegularSampler {
    fn generate_samples(&self, samples_per_pixels: i32) -> Vec<(f64, f64)> {
        let sqrt_samples = (samples_per_pixels as f64).sqrt() as i32;
        let mut samples = Vec::with_capacity((sqrt_samples * sqrt_samples) as usize);

        for i in 0..sqrt_samples {
            for j in 0..sqrt_samples {
                let u = (i as f64 + 0.5) / sqrt_samples as f64;
                let v = (j as f64 + 0.5) / sqrt_samples as f64;

                samples.push((u, v));
            }
        }

        samples
    }
}

RandomSampler

今度は、1ピクセル内でRandomな点を生成して、それを元にRayを作成するためのものとなります。

use rand::Rng;

use crate::sampler::sampler::Sampler;

pub struct RandomSampler;

impl RandomSampler {
    pub fn new() -> Self {
        Self
    }
}

impl Sampler for RandomSampler {
    fn generate_samples(&self, samples_per_pixels: i32) -> Vec<(f64, f64)> {
        let mut rng = rand::rng();
        let mut samples = Vec::with_capacity(samples_per_pixels as usize);

        for _ in 0..samples_per_pixels {
            samples.push((rng.random::<f64>(), rng.random::<f64>()));
        }

        samples
    }
}

JitteredSampler

RegularSampler + RandomSamplerのような方法となります。ランダムな点を生成するとき、規則的な点を中心に一点ランダムな点を生成する方法となります。緑の点が規則点を元にランダムな赤い点を生成するイメージです。

use rand::Rng;

use crate::sampler::sampler::Sampler;

pub struct JitteredSampler;

impl JitteredSampler {
    pub fn new() -> Self {
        Self
    }
}

impl Sampler for JitteredSampler {
    fn generate_samples(&self, samples_per_pixels: i32) -> Vec<(f64, f64)> {
        let sqrt_samples = (samples_per_pixels as f64).sqrt() as i32;
        let mut samples = Vec::with_capacity((sqrt_samples * sqrt_samples) as usize);
        let mut rng = rand::rng();

        for i in 0..sqrt_samples {
            for j in 0..sqrt_samples {
                let u = (i as f64 + rng.random::<f64>()) / sqrt_samples as f64;
                let v = (j as f64 + rng.random::<f64>()) / sqrt_samples as f64;

                samples.push((u, v));
            }
        }

        samples
    }
}

管理方法

Samplerの選択を管理するために、簡単にSamplerFactoryを作っておく

use crate::sampler::sampler::Sampler;
use crate::sampler::{
    jittered_sampler::JitteredSampler,
    random_sampler::RandomSampler,
    regular_sampler::RegularSampler,
};

pub enum SamplerType {
    Random,
    Regular,
    Jittered,
}

pub struct SamplerFactory;

impl SamplerFactory {
    pub fn create(sampler_type: SamplerType) -> Box<dyn Sampler> {
        match sampler_type {
            SamplerType::Regular => Box::new(RegularSampler::new()),
            SamplerType::Random => Box::new(RandomSampler::new()),
            SamplerType::Jittered => Box::new(JitteredSampler::new()),
        }
    }
}

Renderer側の変更点

Samplerを実装したので、実際に組み込んでいきます。

pub struct Renderer {
    pub sampler: Box<dyn Sampler>, // 追加
    pub samples_per_pixel: i32,
    pub max_depth: usize,
}
impl Renderer {
    pub fn new(sampler_type: SamplerType, samples_per_pixel: i32, max_depth: usize) -> Self {
        let sampler = SamplerFactory::create(sampler_type); //追加
        Self {
            sampler, //追加
            samples_per_pixel,
            max_depth,
        }
    }

    pub fn render(&self, camera: &mut Camera, scene: &Scene) {
        // 省略
    }
    
    // 変更!
    fn render_pixel(
        &self,
        camera: &Camera,
        scene: &Scene,
        x: usize,
        y: usize,
        width: usize,
        height: usize,
    ) -> Color3 {
        let samples = self.sampler.generate_samples(self.samples_per_pixel);
        let mut color = Color3::zero();

        for (offset_u, offset_v) in samples {
            let u = (x as f64 + offset_u) / width as f64;
            let v = (y as f64 + offset_v) / height as f64;

            let ray = camera.generate_ray(u, v);
            color = color + self.trace_ray(&ray, scene, self.max_depth);
        }

        color / self.samples_per_pixel as f64
    }

    fn trace_ray(&self, ray: &Ray, scene: &Scene, depth: usize) -> Color3 {
        // 省略
    }
}

動作確認

動作の確認していきます。

コードと結果
pub fn main() -> Result<(), Box<dyn Error>> {
    // result imge name
    let img_path = "./build/plane_sphere.ppm";

    // prepare Render
    let renderer = Renderer::new(SamplerType::Jittered, 50, 10);

    // 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(())
}

Before

今回のやつ

さいごに

気がついたら、4月になっていてびっくりです。桜も満開なので近いうちに花見をすることを目標にかがて過ごそうと思います。

次のテーマは、当分マテリアル関連ですかね、頑張ります。