Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 5 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
[package]
name = "raytracer"
version = "0.1.0"
authors = ["Alex Chi <[email protected]>"]
edition = "2018"
[workspace]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
image = "0.23"
indicatif = "0.15"
threadpool = "1.8"
imageproc = "0.21"
rusttype = "0.9"
members = [
"raytracer",
"raytracer_codegen",
]
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,25 @@ TA Wenxin Zheng 会有更详细的说明。
* **Track 1: New Features** 完成 Rest of Your Life 的剩余部分,重构代码并渲染带玻璃球的 Cornell Box。
* **Track 2: More Features** 完成 Next Week 中除 Motion Blur 外的部分,渲染噪点较少的最终场景。
* **Track 3: Reduce Contention** 此项工作的前提条件是完成多线程渲染。在多线程环境中,clone / drop Arc 可能会导致性能下降。因此,我们要尽量减少 Arc 的使用。这项任务的目标是,仅在线程创建的时候 clone Arc;其他地方不出现 Arc,将 Arc 改为引用。
* **Track 4: Static Dispatch** 调用 `Box<dyn trait>` / `Arc<dyn trait>` / `&dyn trait` 中的函数时会产生额外的开销。我们可以通过泛型来解决这个问题。这个任务的目标是,定义新的泛型材质、变换和物体,仅在 `HitRecord`, `ScatterRecord` (这个在 Rest of Your Life 的剩余部分中出现), `HittableList` 和 `BVHNode` 中使用 `dyn`。
* **Track 5: Code Generation** 此项工作的前提条件是完成 BVH。目前,`BVHNode` 是在运行时构造的。这个过程其实可以在编译期完成。我们可以通过过程宏生成所有的物体,并构造静态的 `BVHNode`,从而提升渲染效率。
* **Track 4: Static Dispatch** 调用 `Box<dyn trait>` / `Arc<dyn trait>` / `&dyn trait` 中的函数时会产生额外的开销。我们可以通过泛型来解决这个问题。
* 这个任务的目标是,通过定义新的泛型材质、变换和物体,比如 `LambertianStatic<T>`,并在场景中使用他们,从而减少动态调用的开销。你也可以另开一个模块定义和之前的材质同名的 struct。
* 你可以在 `material.rs` 里找到泛型的相关用法。
* 仅在 `HitRecord`, `ScatterRecord` (这个在 Rest of Your Life 的剩余部分中出现), `HittableList` 和 `BVHNode` 中使用 `dyn`。
* 如果感兴趣,可以探索如何使用 `macro_rules` 来减少几乎相同的代码写两遍的冗余。
* **Track 5: Code Generation** 此项工作的前提条件是完成 BVH。
* 目前,`BVHNode` 是在运行时构造的。这个过程其实可以在编译期完成。我们可以通过过程宏生成所有的物体,并构造静态的 `BVHNode`,从而提升渲染效率。
* 为了使用过程宏,在这个工程中,我们已经重新组织了目录结构。请参考[这个 PR](https://github.com/skyzh/raytracer-tutorial/pull/14)进行修改。
* 你可以使用 `cargo expand` 来查看过程宏处理过后的代码。你也可以在编译过程中直接输出过程宏生成的代码。
* `codegen` 部分不需要通过 clippy。
* 如果感兴趣,你也可以探索给过程宏传参的方法。e.g. 通过 `make_spheres_impl! { 100 }` 生成可以产生 100 个球的函数。
* **Track 6: PDF Static Dispatch** 此项工作的前提条件是完成 Rest of your Life 的剩余部分。PDF 中需要处理的物体使用泛型完成,去除代码路径中的 `&dyn`。
* **Track 7: More Code Generation** 在过程宏中,读取文件,直接从 yaml 或 JSON 文件生成场景对应的程序。在 `data` 文件夹中给出了一些例子。
* **Track 7: More Code Generation** 在过程宏中,读取文件,直接从 yaml 或 JSON 文件生成场景对应的程序。
* 在 `data` 文件夹中给出了一些例子。
* 例子中 `BVHNode` 里的 `bounding_box` 是冗余数据。你可以不使用这个数据。
* **Track 8: Advanced Features** 增加对 Transform 的 PDF 支持。
* 完成 Track 3 前请备份代码 (比如记录 git 的 commit id)。完成 Track 4, 5, 6 时请保留原先的场景和程序,在此基础上添加新的内容。完成后编写 benchmark,对比修改前后效率的提升。你可以使用 `criterion` crate 做 benchmark。benchmark 的内容可以是往构造好的场景中随机打光线,记录打一条光线所需的时间。
* 如果你有多余的时间,你可以通过 benchmark 来测试实现功能前后的区别。
* 完成 Track 3 前请备份代码 (比如记录 git 的 commit id)。完成 Track 4, 5, 6 时请保留原先的场景和程序,在此基础上添加新的内容。
* 你可以使用 `criterion` crate 做 benchmark。benchmark 的内容可以是往构造好的场景中随机打光线,记录打一条光线所需的时间。

## More Information

Expand Down
15 changes: 15 additions & 0 deletions raytracer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "raytracer"
version = "0.1.0"
authors = ["Alex Chi <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
image = "0.23"
indicatif = "0.15"
threadpool = "1.8"
imageproc = "0.21"
rusttype = "0.9"
raytracer_codegen = { path = "../raytracer_codegen" }
12 changes: 9 additions & 3 deletions src/main.rs → raytracer/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
#[allow(clippy::float_cmp)]
#![allow(clippy::float_cmp)]
#![feature(box_syntax)]

mod material;
mod scene;
mod vec3;

use image::{ImageBuffer, Rgb, RgbImage};
use indicatif::ProgressBar;
use rusttype::Font;
use scene::example_scene;
use std::sync::mpsc::channel;
use std::sync::Arc;
use threadpool::ThreadPool;
pub use vec3::Vec3;

const AUTHOR: &str = "Alex Chi";

struct World {
pub struct World {
pub height: u32,
}

Expand Down Expand Up @@ -82,7 +88,7 @@ fn main() {
let bar = ProgressBar::new(n_jobs as u64);

// use Arc to pass one instance of World to multiple threads
let world = Arc::new(World { height });
let world = Arc::new(example_scene());

for i in 0..n_jobs {
let tx = tx.clone();
Expand Down
53 changes: 53 additions & 0 deletions raytracer/src/material.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#![allow(dead_code)]
#![allow(clippy::boxed_local)]
// You SHOULD remove above line in your code.

// This file shows necessary examples of how to complete Track 4 and 5.

pub trait Texture {}
pub trait Material {}

/// `Lambertian` now takes a generic parameter `T`.
/// This reduces the overhead of using `Box<dyn Texture>`
#[derive(Clone)]
pub struct Lambertian<T: Texture> {
pub albedo: T,
}

impl<T: Texture> Lambertian<T> {
pub fn new(albedo: T) -> Self {
Self { albedo }
}
}

impl<T: Texture> Material for Lambertian<T> {}

pub trait Hitable {}
pub struct AABB;

/// This BVHNode should be constructed statically.
/// You should use procedural macro to generate code like this:
/// ```
/// let bvh = BVHNode::construct(
/// box BVHNode::construct(
/// box Sphere { .. }
/// box Sphere { .. }
/// ),
/// box BVHNode::construct(
/// box Sphere { .. }
/// box Sphere { .. }
/// )
/// )
/// ```
/// And you can put that `bvh` into your `HittableList`.
pub struct BVHNode<L: Hitable, R: Hitable> {
left: Box<L>,
right: Box<R>,
bounding_box: AABB,
}

impl<L: Hitable, R: Hitable> BVHNode<L, R> {
pub fn construct(_left: Box<L>, _right: Box<R>) -> Self {
unimplemented!()
}
}
32 changes: 32 additions & 0 deletions raytracer/src/scene.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#![allow(dead_code)]
// You SHOULD remove above line in your code.

use crate::Vec3;
use crate::World;
use raytracer_codegen::make_spheres_impl;

// Call the procedural macro, which will become `make_spheres` function.
make_spheres_impl! {}

// These three structs are just written here to make it compile.
// You should `use` your own structs in this file.
// e.g. replace next two lines with
// `use crate::materials::{DiffuseLight, ConstantTexture}`
pub struct ConstantTexture(Vec3);
pub struct DiffuseLight(ConstantTexture);

pub struct Sphere {
center: Vec3,
radius: f64,
material: DiffuseLight,
}

pub fn example_scene() -> World {
let mut spheres: Vec<Box<Sphere>> = make_spheres(); // Now `spheres` stores two spheres.
let mut hittable_list = vec![];
// You can now add spheres to your own world
hittable_list.append(&mut spheres);

hittable_list.clear();
World { height: 512 }
}
File renamed without changes.
14 changes: 14 additions & 0 deletions raytracer_codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "raytracer_codegen"
version = "0.1.0"
authors = ["Alex Chi <[email protected]>"]
edition = "2018"

[lib]
proc-macro = true

[dependencies]
rand = { version = "0.7", features = ["small_rng"] }
quote = "1.0"
syn = "1.0"
proc-macro2 = "1.0"
84 changes: 84 additions & 0 deletions raytracer_codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#![allow(clippy::all)]

extern crate proc_macro;
use proc_macro2::TokenStream;
use quote::quote;

/// You can replace this `Vec` with your own version, e.g. the one
/// you wrote in raytracer, if you want.
#[derive(Copy, Clone, Debug)]
struct Vec3(f64, f64, f64);

/// You can replace this `Sphere` with your own version, e.g. the one
/// you wrote in raytracer, if you want.
#[derive(Copy, Clone, Debug)]
struct Sphere {
pub pos: Vec3,
pub size: f64,
pub color: Vec3,
}

/// This function generates code for one Sphere.
/// Enable `box` feature in `main.rs` with `#![feature(box_syntax)]`,
/// or replace `box` with `Box::new`
fn sphere_code(sphere: &Sphere) -> TokenStream {
let Vec3(x, y, z) = sphere.pos;
let Vec3(r, g, b) = sphere.color;
let size = sphere.size * 0.9;

// Create a code snippet of `Sphere`.
// Note that the `Sphere` in `quote` is the one in your ray tracer,
// not the one you defined in this module.
quote! {
box Sphere {
center: Vec3::new(#x, #y, #z),
radius: #size,
material: DiffuseLight(
ConstantTexture(
Vec3::new(#r, #g, #b)
)
)
}
}
}

/// This function generates the final `make_spheres` function.
#[proc_macro]
pub fn make_spheres_impl(_item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let spheres = vec![
&Sphere {
pos: Vec3(0.0, 0.0, 0.0),
size: 1.0,
color: Vec3(1.0, 1.0, 1.0),
},
&Sphere {
pos: Vec3(0.0, 0.0, 0.0),
size: 1.0,
color: Vec3(1.0, 1.0, 1.0),
},
];

let mut tokens = vec![];

for sphere in spheres {
let sc = sphere_code(sphere);
tokens.push(quote! {
spheres.push(#sc);
});
}

let x_final: TokenStream = tokens.into_iter().collect();

let result = proc_macro::TokenStream::from(quote! {
fn make_spheres() -> Vec<Box<Sphere>> {
let mut spheres = vec![];
#x_final
spheres
}
});

// uncomment this statement if you want to inspect result
// println!("{}", result);

result
}