Draw first image

This commit is contained in:
hal8174 2024-09-28 20:15:05 +02:00
parent 4c872f9f91
commit 62b9fdcb56
19 changed files with 1566 additions and 76 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
.target .target
**.exr

1072
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
[workspace] [workspace]
members = [ "ray-tracing-core"] members = [ "ray-tracing-core", "ray-tracing-image", "ray-tracing-scene"]
resolver = "2" resolver = "2"

View file

@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
rand = "0.8.5"

View file

@ -0,0 +1,74 @@
use crate::prelude::*;
use rand::Rng;
pub trait Camera<R: Rng> {
fn forward(&self, x: u32, y: u32, rng: &mut R) -> Ray;
fn width(&self) -> u32;
fn height(&self) -> u32;
}
pub struct BasicCamera {
width: u32,
height: u32,
pos: Pos3,
dir: Dir3,
h: Dir3,
v: Dir3,
}
impl BasicCamera {
pub fn new(
width: u32,
height: u32,
pos: Pos3,
dir: Dir3,
up: Dir3,
horizontal_fov: Float,
) -> Self {
assert!(horizontal_fov < FloatConsts::PI);
let dir = dir.normalize();
let up = up.normalize();
let l = Float::sin(0.5 * horizontal_fov);
let h = l * Dir3::cross(dir, up);
let v = l * ((height as Float) / (width as Float)) * Dir3::cross(dir, h.normalize());
Self {
width,
height,
pos,
dir,
h,
v,
}
}
pub fn from_look_at(
width: u32,
height: u32,
pos: Pos3,
look_at: Pos3,
up: Dir3,
horizontal_fov: Float,
) -> Self {
Self::new(width, height, pos, look_at - pos, up, horizontal_fov)
}
}
impl<R: Rng> Camera<R> for BasicCamera {
fn forward(&self, x: u32, y: u32, rng: &mut R) -> Ray {
// normalize x and y to -0.5 to 0.5
let x = ((x as Float + rng.gen::<Float>()) / (self.width as Float)) - 0.5;
let y = ((y as Float + rng.gen::<Float>()) / (self.height as Float)) - 0.5;
let dir = self.dir + x * self.h + y * self.v;
Ray::new(self.pos, dir.normalize(), 0.0)
}
fn width(&self) -> u32 {
self.width
}
fn height(&self) -> u32 {
self.height
}
}

View file

@ -0,0 +1,67 @@
use crate::prelude::*;
use std::ops::{Add, AddAssign, Div};
#[derive(Debug, Clone, Copy)]
pub struct Color {
r: Float,
g: Float,
b: Float,
}
impl Color {
pub fn new(r: Float, g: Float, b: Float) -> Self {
Self { r, g, b }
}
pub fn r(self) -> Float {
self.r
}
pub fn g(self) -> Float {
self.g
}
pub fn b(self) -> Float {
self.b
}
pub fn gray(x: Float) -> Self {
Self::new(x, x, x)
}
pub fn black() -> Self {
Self::gray(0.0)
}
}
impl Add for Color {
type Output = Color;
fn add(self, rhs: Self) -> Self::Output {
Self {
r: self.r + rhs.r,
g: self.g + rhs.g,
b: self.b + rhs.b,
}
}
}
impl AddAssign for Color {
fn add_assign(&mut self, rhs: Self) {
self.r += rhs.r;
self.g += rhs.g;
self.b += rhs.b;
}
}
impl Div<Float> for Color {
type Output = Color;
fn div(self, rhs: Float) -> Self::Output {
Self {
r: self.r / rhs,
g: self.g / rhs,
b: self.b / rhs,
}
}
}

View file

@ -1,11 +1,16 @@
pub mod camera;
pub mod color;
pub mod material; pub mod material;
pub mod math; pub mod math;
pub mod ray; pub mod ray;
pub mod renderer;
pub mod scene; pub mod scene;
pub mod prelude { pub mod prelude {
pub type Float = f64; pub type Float = f32;
pub use crate::color::Color;
pub use crate::material::Material; pub use crate::material::Material;
pub use crate::math::{Dir3, Pos3}; pub use crate::math::*;
pub use crate::ray::Ray; pub use crate::ray::Ray;
pub use std::f32::consts as FloatConsts;
} }

View file

@ -1 +1,15 @@
pub trait Material {} use crate::prelude::*;
use rand::Rng;
/// All calculations for the material are done a tangent space of the intersection.
pub trait Material<R: Rng> {
fn eval(&self, w_in: Dir3, w_out: Dir3, rng: &mut R) -> Color;
}
pub struct DefaultMaterial {}
impl<R: Rng> Material<R> for DefaultMaterial {
fn eval(&self, _w_in: Dir3, _w_out: Dir3, rng: &mut R) -> Color {
Color::black()
}
}

View file

@ -1,54 +1,26 @@
use crate::prelude::*; use crate::prelude::*;
use std::ops::{Add, Div, Mul, Neg, Sub}; use std::ops::{Add, Div, Mul, Neg, Sub};
#[derive(Debug, Clone, Copy)]
pub struct Pos3 {
x: Float,
y: Float,
z: Float,
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Dir3 { pub struct Dir3 {
x: Float, pub(crate) x: Float,
y: Float, pub(crate) y: Float,
z: Float, pub(crate) z: Float,
}
impl Pos3 {
pub fn new(x: Float, y: Float, z: Float) -> Self {
Self { x, y, z }
}
pub fn x(self) -> Float {
self.x
}
pub fn y(self) -> Float {
self.y
}
pub fn z(self) -> Float {
self.z
}
pub fn zero() -> Self {
Self {
x: 0.0,
y: 0.0,
z: 0.0,
}
}
} }
impl Dir3 { impl Dir3 {
pub fn new(x: Float, y: Float, z: Float) -> Self { pub fn new(x: Float, y: Float, z: Float) -> Self {
Self { x, y, z } Self { x, y, z }
} }
pub fn x(self) -> Float { pub fn x(self) -> Float {
self.x self.x
} }
pub fn y(self) -> Float { pub fn y(self) -> Float {
self.y self.y
} }
pub fn z(self) -> Float { pub fn z(self) -> Float {
self.z self.z
} }
@ -61,23 +33,32 @@ impl Dir3 {
} }
} }
pub fn up() -> Self {
Self::new(0.0, 1.0, 0.0)
}
pub fn normalize(self) -> Self { pub fn normalize(self) -> Self {
self / self.length() self / self.length()
} }
pub fn length(self) -> Float {
Float::sqrt(self.x * self.x + self.y * self.y + self.z * self.z) pub fn length_squared(self) -> Float {
self.x * self.x + self.y * self.y + self.z * self.z
} }
}
impl Sub for Pos3 { pub fn length(self) -> Float {
type Output = Dir3; Float::sqrt(self.length_squared())
}
fn sub(self, rhs: Self) -> Self::Output { pub fn dot(self, rhs: Self) -> Float {
Dir3 { self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
x: self.x - rhs.x, }
y: self.y - rhs.y,
z: self.z - rhs.z, pub fn cross(self, rhs: Self) -> Self {
} Self::new(
self.y * rhs.z - self.z * rhs.y,
self.z * rhs.x - self.x * rhs.z,
self.x * rhs.y - self.y * rhs.x,
)
} }
} }
@ -93,23 +74,11 @@ impl Add for Dir3 {
} }
} }
impl Add<Pos3> for Dir3 { impl Add<pos3::Pos3> for Dir3 {
type Output = Pos3; type Output = pos3::Pos3;
fn add(self, rhs: Pos3) -> Self::Output { fn add(self, rhs: pos3::Pos3) -> Self::Output {
Pos3 { pos3::Pos3 {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}
impl Add<Dir3> for Pos3 {
type Output = Pos3;
fn add(self, rhs: Dir3) -> Self::Output {
Pos3 {
x: self.x + rhs.x, x: self.x + rhs.x,
y: self.y + rhs.y, y: self.y + rhs.y,
z: self.z + rhs.z, z: self.z + rhs.z,
@ -141,6 +110,18 @@ impl Mul<Float> for Dir3 {
} }
} }
impl Mul<Dir3> for Float {
type Output = Dir3;
fn mul(self, rhs: Dir3) -> Self::Output {
Dir3 {
x: self * rhs.x,
y: self * rhs.y,
z: self * rhs.z,
}
}
}
impl Div<Float> for Dir3 { impl Div<Float> for Dir3 {
type Output = Dir3; type Output = Dir3;

View file

@ -0,0 +1,5 @@
pub mod dir3;
pub mod pos3;
pub use dir3::Dir3;
pub use pos3::Pos3;

View file

@ -0,0 +1,57 @@
use crate::prelude::*;
use std::ops::{Add, Sub};
#[derive(Debug, Clone, Copy)]
pub struct Pos3 {
pub(crate) x: Float,
pub(crate) y: Float,
pub(crate) z: Float,
}
impl Pos3 {
pub fn new(x: Float, y: Float, z: Float) -> Self {
Self { x, y, z }
}
pub fn x(self) -> Float {
self.x
}
pub fn y(self) -> Float {
self.y
}
pub fn z(self) -> Float {
self.z
}
pub fn zero() -> Self {
Self {
x: 0.0,
y: 0.0,
z: 0.0,
}
}
}
impl Sub for pos3::Pos3 {
type Output = Dir3;
fn sub(self, rhs: Self) -> Self::Output {
Dir3 {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
impl Add<Dir3> for pos3::Pos3 {
type Output = pos3::Pos3;
fn add(self, rhs: Dir3) -> Self::Output {
pos3::Pos3 {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}

View file

@ -2,7 +2,25 @@ use crate::prelude::*;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Ray { pub struct Ray {
pub start: Pos3, start: Pos3,
pub dir: Dir3, dir: Dir3,
pub time: Float, time: Float,
}
impl Ray {
pub fn new(start: Pos3, dir: Dir3, time: Float) -> Self {
Self { start, dir, time }
}
pub fn start(self) -> Pos3 {
self.start
}
pub fn dir(self) -> Dir3 {
self.dir
}
pub fn time(self) -> Float {
self.time
}
} }

View file

@ -0,0 +1,63 @@
use std::marker::PhantomData;
use crate::{camera::Camera, prelude::*, scene::Scene};
use rand::Rng;
pub trait ClassicalRenderer<R: Rng> {
fn render_pixel(&self, x: u32, y: u32, rng: &mut R) -> Color;
fn width(&self) -> u32;
fn height(&self) -> u32;
}
pub struct DepthRenderer<S, C, R>
where
S: Scene<R>,
C: Camera<R>,
R: Rng,
{
scene: S,
camera: C,
rng: PhantomData<R>,
}
impl<S, C, R> DepthRenderer<S, C, R>
where
S: Scene<R>,
C: Camera<R>,
R: Rng,
{
pub fn new(scene: S, camera: C) -> Self {
Self {
scene,
camera,
rng: PhantomData {},
}
}
}
impl<S, C, R> ClassicalRenderer<R> for DepthRenderer<S, C, R>
where
S: Scene<R>,
C: Camera<R>,
R: Rng,
{
fn render_pixel(&self, x: u32, y: u32, rng: &mut R) -> Color {
let r = self.camera.forward(x, y, rng);
if let Some(i) = self.scene.intersect(r, 0.0, Float::INFINITY) {
// Color::gray(1.0 / i.t())
let c = 0.5 * (i.normal() + Dir3::new(1.0, 1.0, 1.0));
Color::new(c.x, c.y, c.z)
} else {
Color::black()
}
}
fn width(&self) -> u32 {
self.camera.width()
}
fn height(&self) -> u32 {
self.camera.height()
}
}

View file

@ -1,24 +1,34 @@
use crate::prelude::*; use crate::prelude::*;
use rand::Rng;
pub trait Scene { pub trait Scene<R: Rng> {
fn intersect(&self, ray: Ray, min: Float, max: Float) -> Option<Intersection<'_>>; fn intersect(&self, ray: Ray, min: Float, max: Float) -> Option<Intersection<'_, R>>;
} }
pub struct Intersection<'sc> { pub struct Intersection<'sc, R: Rng> {
t: Float, t: Float,
material: &'sc dyn Material, normal: Dir3,
material: &'sc dyn Material<R>,
} }
impl<'sc> Intersection<'sc> { impl<'sc, R: Rng> Intersection<'sc, R> {
pub fn new(t: Float, material: &'sc dyn Material) -> Self { pub fn new(t: Float, normal: Dir3, material: &'sc dyn Material<R>) -> Self {
Self { t, material } Self {
t,
normal,
material,
}
} }
pub fn t(&self) -> Float { pub fn t(&self) -> Float {
self.t self.t
} }
pub fn material(&self) -> &'sc dyn Material { pub fn normal(&self) -> Dir3 {
self.normal
}
pub fn material(&self) -> &'sc dyn Material<R> {
self.material self.material
} }
} }

View file

@ -0,0 +1,11 @@
[package]
name = "ray-tracing-image"
version = "0.1.0"
edition = "2021"
[dependencies]
image = "0.25.2"
rand = { version = "0.8.5", features = ["small_rng"] }
ray-tracing-core = { path = "../ray-tracing-core" }
ray-tracing-scene = { path = "../ray-tracing-scene" }
rayon = "1.10.0"

View file

@ -0,0 +1,52 @@
use image::{ImageBuffer, ImageResult, Rgb};
use rand::{rngs::SmallRng, SeedableRng};
use ray_tracing_core::{
camera::BasicCamera,
prelude::*,
renderer::{ClassicalRenderer, DepthRenderer},
};
use ray_tracing_scene::BasicScene;
use rayon::prelude::*;
use std::path::Path;
fn render_image<C: ClassicalRenderer<SmallRng> + Sync>(
renderer: C,
outputfilename: impl AsRef<Path>,
samples_per_pixel: usize,
) -> ImageResult<()> {
let mut data = vec![Color::black(); (renderer.width() * renderer.height()) as usize];
data.par_iter_mut().enumerate().for_each(|(i, c)| {
let x = (i % renderer.width() as usize) as u32;
let y = (i / renderer.width() as usize) as u32;
let mut rng = SmallRng::seed_from_u64((x + y * renderer.width()) as u64);
for _ in 0..samples_per_pixel {
*c += renderer.render_pixel(x, y, &mut rng) / (samples_per_pixel as Float);
}
});
let img = ImageBuffer::from_fn(renderer.width(), renderer.height(), |x, y| {
let c = data[(x + y * renderer.width()) as usize];
Rgb::<Float>([c.r(), c.g(), c.b()])
});
img.save(outputfilename)
}
fn main() -> ImageResult<()> {
let s = BasicScene::new();
let c = BasicCamera::new(
640,
400,
Pos3::new(-10.0, 0.0, 0.0),
Dir3::new(1.0, 0.0, 0.0),
Dir3::up(),
Float::to_radians(90.0),
);
let r = DepthRenderer::new(s, c);
render_image(r, "test.exr", 16)
}

View file

@ -0,0 +1,8 @@
[package]
name = "ray-tracing-scene"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5"
ray-tracing-core = { path = "../ray-tracing-core" }

View file

@ -0,0 +1,51 @@
use rand::Rng;
use ray_tracing_core::material::DefaultMaterial;
use ray_tracing_core::prelude::*;
use ray_tracing_core::scene::{Intersection, Scene};
pub mod sphere;
pub struct BasicScene {
spheres: Vec<(Pos3, Float)>,
}
impl BasicScene {
pub fn new() -> Self {
Self {
spheres: vec![(Pos3::zero(), 1.0), (Pos3::new(0.0, 0.0, 2.5), 2.0)],
}
}
}
impl<R: Rng> Scene<R> for BasicScene {
fn intersect(&self, ray: Ray, min: Float, max: Float) -> Option<Intersection<'_, R>> {
let mut intersection: Option<Intersection<'_, R>> = None;
for &(c, r) in &self.spheres {
let offset = ray.start() - c;
let p = Dir3::dot(ray.dir(), offset);
let delta = p * p - (offset.length_squared() - r * r);
if delta >= 0.0 {
let d = -p - Float::sqrt(delta);
let int = Intersection::new(
d,
((ray.start() + d * ray.dir()) - c).normalize(),
&DefaultMaterial {},
);
if d >= min && d <= max {
if let Some(i) = intersection.as_ref() {
if i.t() > d {
intersection.replace(int);
}
} else {
intersection = Some(int)
}
}
}
}
intersection
}
}

View file