はじめに
お疲れ様です。今日は、簡単に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)>;
}
SendとSyncの境界トレイトは、今回不要ですが並列処理させるときに必要になります。のでそのうちこの下にリンクを載せます。
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月になっていてびっくりです。桜も満開なので近いうちに花見をすることを目標にかがて過ごそうと思います。
次のテーマは、当分マテリアル関連ですかね、頑張ります。
