0200-材质和散射

环境

  • Time 2022-11-16
  • WSL-Ubuntu 22.04
  • Rust 1.65.0

前言

说明

参考:https://raytracing.github.io/books/RayTracingInOneWeekend.html

目标

不同的材质有不同的散射光线和衰减,这一节将不同的材质进行抽象。

向量的乘法

impl Mul for Vector3 {
    type Output = Self;

    fn mul(self, other: Vector3) -> Self {
        Vector3 {
            x: self.x * other.x,
            y: self.y * other.y,
            z: self.z * other.z,
        }
    }
}

material.rs

use crate::{
    hittable::HitRecord,
    ray::Ray,
    vector3::{Color, Vector3},
};

// 材质
pub trait Material {
    fn scatter(&self, ray: &Ray, record: &HitRecord) -> Option<(Color, Ray)>;
}

pub struct Lambert {
    // 衰减
    pub albedo: Color,
}

impl Material for Lambert {
    fn scatter(&self, _: &Ray, record: &HitRecord) -> Option<(Color, Ray)> {
        let mut direction = record.normal + Vector3::random_unit();

        if direction.near_zero() {
            direction = record.normal;
        }
        let scattered = Ray::new(record.point, direction);

        Some((self.albedo, scattered))
    }
}

HitRecord

pub struct HitRecord {
    pub point: Point3,
    pub normal: Vector3,
    pub t: f64,
    pub material: Rc<dyn Material>,
}

Sphere

pub struct Sphere {
    center: Point3,
    radius: f64,
    material: Rc<dyn Material>,
}

impl Sphere {
    pub fn new(center: Point3, radius: f64, material: Rc<dyn Material>) -> Sphere {
        Sphere {
            center,
            radius,
            material,
        }
    }

    fn nearest(&self, ray: &Ray, temp: Temp) -> Option<HitRecord> {
        //  找到最近的点
        let a = ray.direction().dot(ray.direction());

        let mut t = (-temp.b - temp.sqrt) / a;
        if t < temp.min || temp.max < t {
            t = (-temp.b + temp.sqrt) / a;
            if t < temp.min || temp.max < t {
                return None;
            }
        }

        let point = ray.at(t);
        let normal = (point - self.center) / self.radius;
        Some(HitRecord {
            t,
            point,
            normal,
            material: Rc::clone(&self.material),
        })
    }
}

main.rs

use std::rc::Rc;

use camera::Camera;
use hittable::{Hit, World};
use material::{Lambert, Material};
use rand::Rng;
use ray::Ray;
use sphere::Sphere;
use vector3::{Color, Point3};

mod camera;
mod hittable;
mod material;
mod ray;
mod sphere;
mod vector3;

fn main() {
    // 图片的比例,和宽高
    const WIDTH: u64 = 400;
    const HEIGHT: u64 = (WIDTH as f64 / camera::RATIO) as u64;
    const SAMPLES_PER_PIXEL: u64 = 100;
    const MAX_DEPTH: u64 = 5;

    // 相机
    let camera = Camera::new();

    // 输出图片,第一行输出 P3,表示像素图
    let mut content = String::from("P3");
    // 输出宽和高,和最大颜色值
    content.push_str(&format!("\n{WIDTH} {HEIGHT}\n255\n"));

    let lambert: Rc<dyn Material> = Rc::new(Lambert {
        albedo: Color::new(0.5, 0.5, 0.5),
    });
    let world: World = vec![
        Box::new(Sphere::new(
            Point3::new(0.0, 0.0, -1.0),
            0.5,
            Rc::clone(&lambert),
        )),
        Box::new(Sphere::new(
            Point3::new(0.0, -100.5, -1.0),
            100.0,
            Rc::clone(&lambert),
        )),
    ];

    let mut rng = rand::thread_rng();
    for j in (0..HEIGHT).rev() {
        // 进度
        eprintln!("Scan lines remaining: {j}");
        for i in 0..WIDTH {
            let mut color = Color::default();
            for _ in 0..SAMPLES_PER_PIXEL {
                let random_u: f64 = rng.gen();
                let random_v: f64 = rng.gen();

                let u = ((i as f64) + random_u) / ((WIDTH - 1) as f64);
                let v = ((j as f64) + random_v) / ((HEIGHT - 1) as f64);

                color += ray_color(&camera.get_ray(u, v), &world, MAX_DEPTH);
            }
            content.push_str(&color.format_str(SAMPLES_PER_PIXEL as f64));
        }
    }
    println!("{}", content);
    eprintln!("Done.");
}

// 光线的颜色计算
fn ray_color(ray: &Ray, hittable: &dyn Hit, depth: u64) -> Color {
    // 超过最大深度,直接变成黑色
    if depth == 0 {
        return Color::new(0.0, 0.0, 0.0);
    }

    // 射线命中物体
    if let Some(record) = hittable.hit(ray, 0.001, f64::INFINITY) {
        // 命中物体根据材料散射光线
        return match record.material.scatter(ray, &record) {
            Some((attenuation, scattered)) => {
                attenuation * ray_color(&scattered, hittable, depth - 1)
            }
            None => Color::new(0.0, 0.0, 0.0),
        };
    }

    // 射线未命中,射线的单位向量
    let unit = ray.direction().unit();
    // 因为需要得到上下渐变的背景图,所以需要对 y 进行插值。
    let t = 0.5 * (unit.y + 1.0);
    // 线性插值,根据不同的光线得到在下面这个范围里的不同的颜色,并且是渐变色。
    (1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0)
}

总结

抽象出了材质,下面可以基于材质的抽象,添加不同的材质。

附录

posted @   jiangbo4444  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2020-07-19 【JavaScript】对象原型
点击右上角即可分享
微信分享提示