render normals
This commit is contained in:
parent
38e28a2d57
commit
bfaf3c2cb6
6 changed files with 127 additions and 33 deletions
24
core/src/camera.rs
Normal file
24
core/src/camera.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use crate::geometry::{Point, Ray, Transform, Vector};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Camera {
|
||||||
|
/// camera to world transform
|
||||||
|
transform: Transform,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Camera {
|
||||||
|
pub fn new(eye: Point, center: Point, up: Vector, fov: f32) -> Self {
|
||||||
|
let f = (center - eye).length();
|
||||||
|
let tan = (fov / 2.0).to_radians().tan();
|
||||||
|
let transform =
|
||||||
|
Transform::look_at(eye, center, up).inverse() * Transform::scale(tan * f, tan * f, f);
|
||||||
|
|
||||||
|
Self { transform }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ray(&self, x: f32, y: f32) -> Ray {
|
||||||
|
let origin = self.transform * Point::ORIGIN;
|
||||||
|
let direction = self.transform * Vector::new(x, y, 1.0);
|
||||||
|
Ray::new(origin, direction)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
fmt,
|
||||||
ops::{Add, Div, Mul, Sub},
|
ops::{Add, Deref, Div, Mul, Sub},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod shapes;
|
pub mod shapes;
|
||||||
|
@ -31,6 +31,14 @@ impl From<(f32, f32, f32)> for Vector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for Vector {
|
||||||
|
type Target = glam::Vec3A;
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Vector {
|
impl Vector {
|
||||||
pub const ZERO: Self = Self(glam::Vec3A::ZERO);
|
pub const ZERO: Self = Self(glam::Vec3A::ZERO);
|
||||||
pub const I: Self = Self(glam::Vec3A::X);
|
pub const I: Self = Self(glam::Vec3A::X);
|
||||||
|
@ -49,6 +57,10 @@ impl Vector {
|
||||||
self.0.length_squared()
|
self.0.length_squared()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn normalize(self) -> Vector {
|
||||||
|
Self(self.0.normalize())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dot(self, other: Vector) -> f32 {
|
pub fn dot(self, other: Vector) -> f32 {
|
||||||
self.0.dot(other.0)
|
self.0.dot(other.0)
|
||||||
}
|
}
|
||||||
|
@ -199,6 +211,7 @@ impl Mul<Point> for Transform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
origin: Point,
|
origin: Point,
|
||||||
direction: Vector,
|
direction: Vector,
|
||||||
|
|
|
@ -6,24 +6,24 @@ use super::{Point, Ray, Vector};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Hit {
|
pub(crate) struct Hit {
|
||||||
point: Point,
|
pub point: Point,
|
||||||
normal: Vector,
|
pub normal: Vector,
|
||||||
t: f32,
|
pub t: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub(crate) trait Hittable {
|
pub(crate) trait Hittable {
|
||||||
fn intersect(&self, ray: Ray, t_max: f32) -> Option<Hit>;
|
fn intersect(&self, ray: &Ray, t_max: f32) -> Option<Hit>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
center: Point,
|
center: Point,
|
||||||
radius: f32,
|
radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hittable for Sphere {
|
impl Hittable for Sphere {
|
||||||
fn intersect(&self, ray: Ray, t_max: f32) -> Option<Hit> {
|
fn intersect(&self, ray: &Ray, t_max: f32) -> Option<Hit> {
|
||||||
let oc = self.center - ray.origin;
|
let oc = self.center - ray.origin;
|
||||||
let a = ray.direction.length_squared();
|
let a = ray.direction.length_squared();
|
||||||
let h = ray.direction.dot(oc);
|
let h = ray.direction.dot(oc);
|
||||||
|
@ -41,19 +41,19 @@ impl Hittable for Sphere {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Plane {
|
pub struct Plane {
|
||||||
normal: Vector,
|
normal: Vector,
|
||||||
d: f32,
|
d: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hittable for Plane {
|
impl Hittable for Plane {
|
||||||
fn intersect(&self, ray: Ray, t_max: f32) -> Option<Hit> {
|
fn intersect(&self, ray: &Ray, t_max: f32) -> Option<Hit> {
|
||||||
let n_dot_r = self.normal.dot(ray.direction);
|
let n_dot_r = self.normal.dot(ray.direction);
|
||||||
(n_dot_r.abs() > f32::EPSILON)
|
(n_dot_r.abs() > f32::EPSILON)
|
||||||
.then(|| {
|
.then(|| {
|
||||||
let n_dot_o = self.normal.dot(ray.origin.to_vector());
|
let n_dot_o = self.normal.dot(ray.origin.to_vector());
|
||||||
-(n_dot_o + self.d) / n_dot_r
|
-(n_dot_o - self.d) / n_dot_r
|
||||||
})
|
})
|
||||||
.filter(|t| *t < t_max && *t > 0.0)
|
.filter(|t| *t < t_max && *t > 0.0)
|
||||||
.map(|t| Hit {
|
.map(|t| Hit {
|
||||||
|
@ -65,6 +65,7 @@ impl Hittable for Plane {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch(Hittable)]
|
#[enum_dispatch(Hittable)]
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum Shape {
|
pub enum Shape {
|
||||||
Sphere,
|
Sphere,
|
||||||
Plane,
|
Plane,
|
||||||
|
|
|
@ -3,40 +3,62 @@ pub mod impl_bytemuck;
|
||||||
|
|
||||||
pub mod color;
|
pub mod color;
|
||||||
|
|
||||||
|
mod camera;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
|
|
||||||
|
use core::f32;
|
||||||
|
|
||||||
|
pub use camera::Camera;
|
||||||
pub use geometry::{Point, Transform, Vector, shapes::Shape};
|
pub use geometry::{Point, Transform, Vector, shapes::Shape};
|
||||||
|
|
||||||
use color::Rgba;
|
use color::Rgba;
|
||||||
|
use geometry::{
|
||||||
|
Ray,
|
||||||
|
shapes::{Hit, Hittable},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Scene;
|
pub struct Scene {
|
||||||
|
camera: Camera,
|
||||||
|
contents: Vec<Shape>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
pub fn new() -> Self {
|
pub fn new(camera: Camera, contents: Vec<Shape>) -> Self {
|
||||||
Self
|
Self { camera, contents }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, width: usize, height: usize) -> Box<[Rgba]> {
|
pub fn render(&self, width: usize, height: usize) -> Box<[Rgba]> {
|
||||||
|
let ray = self.camera.ray(0.0, 0.0);
|
||||||
|
dbg!(&ray);
|
||||||
|
dbg!(self.intersect(ray));
|
||||||
let mut data = Box::new_uninit_slice(width * height);
|
let mut data = Box::new_uninit_slice(width * height);
|
||||||
|
let w = width as f32;
|
||||||
|
let h = height as f32;
|
||||||
|
let aspect_ratio = h / w;
|
||||||
|
|
||||||
for (i, pixel) in data.iter_mut().enumerate() {
|
for (i, pixel) in data.iter_mut().enumerate() {
|
||||||
let x = (i % width) as f32;
|
let x = ((i % width) as f32 * 2.0 / w) - 1.0;
|
||||||
let y = (i / width) as f32;
|
let y = (1.0 - (i / width) as f32 * 2.0 / h) * aspect_ratio;
|
||||||
|
|
||||||
pixel.write(Rgba::new(
|
let rgb = self
|
||||||
x / (width - 1) as f32,
|
.intersect(self.camera.ray(x, y))
|
||||||
y / (height - 1) as f32,
|
.map(|h| h.normal.normalize() * 0.5 + Vector::new(0.5, 0.5, 0.5))
|
||||||
0.5,
|
.unwrap_or(Vector::ZERO);
|
||||||
1.0,
|
|
||||||
));
|
pixel.write(Rgba::new(rgb.x, rgb.y, rgb.z, 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe { data.assume_init() }
|
unsafe { data.assume_init() }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Scene {
|
fn intersect(&self, ray: Ray) -> Option<Hit> {
|
||||||
fn default() -> Self {
|
let mut hit: Option<Hit> = None;
|
||||||
Self::new()
|
for shape in &self.contents {
|
||||||
|
let t_max = hit.as_ref().map(|h| h.t).unwrap_or(f32::INFINITY);
|
||||||
|
if let Some(h) = shape.intersect(&ray, t_max) {
|
||||||
|
hit = Some(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
#!python
|
#!python
|
||||||
|
from PIL import Image
|
||||||
|
from mechthild import Scene, Shape, Camera
|
||||||
|
|
||||||
from mechthild import Shape
|
WIDTH = 800
|
||||||
|
HEIGHT = 600
|
||||||
s1 = Shape.sphere((0.0, 0.5, 0.0), 0.5)
|
|
||||||
s2 = Shape.sphere((1.5, 2.0, -1.0), 2.0)
|
|
||||||
|
|
||||||
|
s1 = Shape.sphere((0.75, 1.0, 1.0), 1.0)
|
||||||
|
s2 = Shape.sphere((-1.5, 2.0, -2.0), 2.0)
|
||||||
ground = Shape.plane((0.0, 1.0, 0.0), 0.0)
|
ground = Shape.plane((0.0, 1.0, 0.0), 0.0)
|
||||||
|
|
||||||
print(f"{s1}\n{s2}\n{ground}")
|
camera = Camera((0.0, 1.5, 5.0), (0.0, 1.0, 0.0), (0.0, 1.0, 0.0), 80.0)
|
||||||
|
|
||||||
|
scene = Scene(camera, [s1, s2, ground])
|
||||||
|
|
||||||
|
render = scene.render(WIDTH, HEIGHT)
|
||||||
|
rgba = bytes([int(v * 255) for v in render])
|
||||||
|
|
||||||
|
image = Image.frombuffer('RGBA', (WIDTH, HEIGHT), rgba)
|
||||||
|
image.save("test.png")
|
||||||
|
|
|
@ -1,8 +1,30 @@
|
||||||
use bytemuck::allocation::cast_slice_box;
|
use bytemuck::allocation::cast_slice_box;
|
||||||
use mechthild_core::{Scene, Shape, color::Rgba};
|
use mechthild_core::{Camera, Scene, Shape, color::Rgba};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[pyclass(name = "Camera")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct PyCamera(Camera);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyCamera {
|
||||||
|
#[new]
|
||||||
|
pub fn new(
|
||||||
|
eye: (f32, f32, f32),
|
||||||
|
center: (f32, f32, f32),
|
||||||
|
up: (f32, f32, f32),
|
||||||
|
fov: f32,
|
||||||
|
) -> Self {
|
||||||
|
Self(Camera::new(eye.into(), center.into(), up.into(), fov))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __str__(&self) -> PyResult<String> {
|
||||||
|
Ok(format!("{:#?}", self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[pyclass(name = "Shape")]
|
#[pyclass(name = "Shape")]
|
||||||
|
#[derive(Clone)]
|
||||||
struct PyShape(Shape);
|
struct PyShape(Shape);
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
|
@ -28,8 +50,9 @@ struct PyScene(Scene);
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl PyScene {
|
impl PyScene {
|
||||||
#[new]
|
#[new]
|
||||||
pub fn new() -> Self {
|
pub fn new(camera: &PyCamera, contents: Vec<PyShape>) -> Self {
|
||||||
Self(Scene::new())
|
let contents = contents.into_iter().map(|s| s.0).collect();
|
||||||
|
Self(Scene::new(camera.0.clone(), contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, width: usize, height: usize) -> Vec<f32> {
|
pub fn render(&self, width: usize, height: usize) -> Vec<f32> {
|
||||||
|
@ -42,6 +65,7 @@ impl PyScene {
|
||||||
/// A Python module implemented in Rust.
|
/// A Python module implemented in Rust.
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
fn mechthild(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
fn mechthild(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
|
m.add_class::<PyCamera>()?;
|
||||||
m.add_class::<PyShape>()?;
|
m.add_class::<PyShape>()?;
|
||||||
m.add_class::<PyScene>()?;
|
m.add_class::<PyScene>()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Add table
Reference in a new issue