ft (wip): textures
This commit is contained in:
34
scenes/texture.json
Normal file
34
scenes/texture.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"filename": "textured.png",
|
||||
"image_width": 1920,
|
||||
"image_height": 1080,
|
||||
"max_depth": 50,
|
||||
"camera": {
|
||||
"anti_alias_rate": 2,
|
||||
"fov": 50.0,
|
||||
"look_from": [-2, 4, 5],
|
||||
"look_at": [-2, 0.0, 0.0],
|
||||
"vup": [0.0, 1.0, 0.0], "defocus_angle": 0, "focus_dist": 15.68
|
||||
},
|
||||
"materials": [
|
||||
{ "type": "lambertian", "albedo": [0.2, 0.2, 0.2], "prob": 0.8 },
|
||||
{ "type": "lambertian", "albedo": [0.9, 0.9, 0.0], "prob": 1.0, "fuzz": 0.1 },
|
||||
{ "type": "dielectric", "refraction_index": 1.5},
|
||||
{ "type": "normal"},
|
||||
{"type": "texture", "source": "./textures/earthmap1k.png"},
|
||||
{"type": "texture", "source": "./textures/yellow.png"}
|
||||
],
|
||||
"objects": [
|
||||
{ "type": "sphere", "center": [0.0, 0.0, -1.2], "radius": 0.9, "material": 5},
|
||||
{ "type": "sphere", "center": [-2, 0, -1], "radius": 0.8, "material": 4},
|
||||
{ "type": "triangle", "p1": [0, 0, -4], "p2": [4, 0, -4], "p3": [2, 2, -4], "material": 5},
|
||||
{ "type": "triangle", "p1": [-2, 2, -4], "p2": [2, 2, -4], "p3": [0, 4, -4], "material": 4},
|
||||
{ "type": "quad", "p1": [-20, -1, -20], "p2": [20, -1, -20], "p3": [20, 20, -20], "p4": [-20, 20, -20], "material": 0},
|
||||
{ "type": "quad", "p1": [-20, -1, 20], "p2": [-20, -1, -20], "p3": [-20, 20, -20], "p4": [-20, 20, 20], "material": 0},
|
||||
{ "type": "quad", "p1": [-20, -1, 20], "p2": [20, -1, 20], "p3": [20, -1, -20], "p4": [-20, -1, -20], "material": 0},
|
||||
{ "type": "quad", "p1": [20, -1, 20], "p2": [20, -1, -20], "p3": [20, 20, -20], "p4": [20, 20, 20], "material": 0}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::{f32::consts::PI, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
objects::{materials::traits::Material, traits::Hittable},
|
||||
@@ -45,8 +45,27 @@ impl Hittable for Circle {
|
||||
|
||||
let p = r.at(t);
|
||||
if (p - self.center).length() < self.radius {
|
||||
return Some(Hit::new(t, p, self.normal, self.material.clone(), self.normal.dot(&r.dir()) < 0.));
|
||||
let uv = self.to_uv(&p);
|
||||
return Some(Hit::new(
|
||||
t,
|
||||
p,
|
||||
self.normal,
|
||||
self.material.clone(),
|
||||
self.normal.dot(&r.dir()) < 0.,
|
||||
*uv.x(),
|
||||
*uv.y(),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn to_uv(&self, point: &Vec3) -> Vec3 {
|
||||
let p = *point - self.center;
|
||||
// TODO: add rotated texture support
|
||||
return Vec3::new(
|
||||
0.5 + p.y().atan2(*p.x()) / (2. * PI),
|
||||
1. - (p.z() / self.radius).acos() / PI,
|
||||
0.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,4 +41,9 @@ impl Hittable for Cube {
|
||||
fn hit(&self, r: &Ray) -> Option<Hit> {
|
||||
Hit::hit_list(&self.faces, r)
|
||||
}
|
||||
|
||||
fn to_uv(&self, point: &Vec3) -> Vec3 {
|
||||
// TODO: map to [0.1] relative to specific face
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,8 @@ impl Hittable for Cylinder {
|
||||
fn hit(&self, r: &crate::ray::Ray) -> Option<super::hit::Hit> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn to_uv(&self, point: &Vec3) -> Vec3 {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,20 @@ pub struct Hit {
|
||||
n: Vec3,
|
||||
mat: Arc<dyn Material>,
|
||||
front_face: bool,
|
||||
u: f32,
|
||||
v: f32,
|
||||
}
|
||||
|
||||
impl Hit {
|
||||
pub fn new(t: f32, p: Vec3, n: Vec3, mat: Arc<dyn Material>, front_face: bool) -> Self {
|
||||
pub fn new(t: f32, p: Vec3, n: Vec3, mat: Arc<dyn Material>, front_face: bool, u: f32, v: f32) -> Self {
|
||||
Self {
|
||||
t,
|
||||
p,
|
||||
n: if front_face { n } else { -n },
|
||||
mat,
|
||||
front_face,
|
||||
u,
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +49,10 @@ impl Hit {
|
||||
self.front_face
|
||||
}
|
||||
|
||||
pub fn uv(&self) -> (f32, f32) {
|
||||
(self.u, self.v)
|
||||
}
|
||||
|
||||
// TODO: use front_face to discard back-hits for culling
|
||||
pub fn hit_list(hittables: &Vec<Arc<dyn Hittable>>, r: &Ray) -> Option<Hit> {
|
||||
let mut closest: Option<Hit> = None;
|
||||
|
||||
@@ -2,3 +2,4 @@ pub mod dielectric;
|
||||
pub mod lambertian;
|
||||
pub mod normal;
|
||||
pub mod traits;
|
||||
pub mod texture;
|
||||
|
||||
47
src/objects/materials/texture.rs
Normal file
47
src/objects/materials/texture.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use image::{DynamicImage, ImageReader};
|
||||
|
||||
use crate::{
|
||||
objects::{hit::Hit, materials::traits::Material},
|
||||
ray::Ray,
|
||||
vec3::Vec3,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Texture {
|
||||
source: DynamicImage,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub fn new(texture: &str) -> Self {
|
||||
let img = ImageReader::open(texture).unwrap().decode().unwrap(); // FIXME: unwraps
|
||||
Self {
|
||||
width: img.width(),
|
||||
height: img.height(),
|
||||
source: img,
|
||||
}
|
||||
}
|
||||
|
||||
fn _idx(&self, u: f32, v: f32) -> usize {
|
||||
let x: usize = (u * ((self.width - 1) as f32)) as usize; // TODO: check these calcs work
|
||||
let y: usize = (v * ((self.height - 1) as f32)) as usize;
|
||||
|
||||
y * self.width as usize + x
|
||||
}
|
||||
|
||||
fn _at(&self, u: f32, v: f32) -> Vec3 {
|
||||
let b = self.source.as_bytes(); // FIXME: i think im indexing colours incorrectly
|
||||
let idx = self._idx(u, v);
|
||||
|
||||
Vec3::from_u8(b[3 * idx], b[3 * idx + 1], b[3 * idx + 2])
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for Texture {
|
||||
fn scatter(&self, hit: &Hit, _ray: &Ray) -> Option<(Option<Ray>, Vec3)> {
|
||||
let (u, v) = hit.uv();
|
||||
let col = self._at(u, 1. - v);
|
||||
return Some((None, col));
|
||||
}
|
||||
}
|
||||
@@ -69,4 +69,10 @@ impl Hittable for Quad {
|
||||
r,
|
||||
)
|
||||
}
|
||||
|
||||
fn to_uv(&self, point: &Vec3) -> Vec3 {
|
||||
let u = (*point - self.p1).dot(&(self.p2 - self.p1)) / (self.p2 - self.p1).length_squared();
|
||||
let v = (*point - self.p1).dot(&(self.p4 - self.p1)) / (self.p4 - self.p1).length_squared();
|
||||
Vec3::new(u, v, 0.)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use core::f32::math::sqrt;
|
||||
use std::f32::consts::PI;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -14,7 +15,6 @@ pub struct Sphere {
|
||||
material: Arc<dyn Material>,
|
||||
}
|
||||
|
||||
|
||||
impl Debug for Sphere {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Sphere")
|
||||
@@ -59,13 +59,26 @@ impl Hittable for Sphere {
|
||||
let t = if tl > 0.001 { tl } else { tr };
|
||||
let p = r.at(t);
|
||||
let out_n = (p - self.center) / self.radius;
|
||||
let uv = self.to_uv(&p);
|
||||
Some(Hit::new(
|
||||
t,
|
||||
p,
|
||||
(p - self.center).get_unit(),
|
||||
self.material.clone(),
|
||||
out_n.dot(r.dir()) < 0.,
|
||||
*uv.x(),
|
||||
*uv.y(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn to_uv(&self, point: &Vec3) -> Vec3 {
|
||||
let p = *point - self.center;
|
||||
// TODO: add rotated texture support
|
||||
return Vec3::new(
|
||||
0.5 + p.y().atan2(*p.x()) / (2. * PI),
|
||||
1. - (p.z() / self.radius).acos() / PI,
|
||||
0.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ use std::fmt::Debug;
|
||||
|
||||
use crate::Ray;
|
||||
use crate::objects::hit::Hit;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub trait Hittable: Debug + Send + Sync {
|
||||
fn hit(&self, r: &Ray) -> Option<Hit>;
|
||||
fn to_uv(&self, point: &Vec3) -> Vec3; // TODO: overhaul; remove u,v in Hit and change Mat ref to
|
||||
// Hittable ref. Then call this function to calculate u,v coords as necessary.
|
||||
}
|
||||
|
||||
@@ -11,19 +11,60 @@ pub struct Triangle {
|
||||
p3: Vec3,
|
||||
material: Arc<dyn Material>,
|
||||
normal: Vec3,
|
||||
|
||||
// barycentric helpers
|
||||
v0: Vec3,
|
||||
v1: Vec3,
|
||||
d00: f32,
|
||||
d01: f32,
|
||||
d11: f32,
|
||||
denom: f32,
|
||||
}
|
||||
|
||||
impl Triangle {
|
||||
pub fn new(p1: Vec3, p2: Vec3, p3: Vec3, material: Arc<dyn Material>) -> Self {
|
||||
// barycentric helpers cached to save computations
|
||||
let v0 = p2 - p1;
|
||||
let v1 = p3 - p1;
|
||||
let d00 = v0.dot(&v0);
|
||||
let d01 = v0.dot(&v1);
|
||||
let d11 = v1.dot(&v1);
|
||||
|
||||
Self {
|
||||
p1,
|
||||
p2,
|
||||
p3,
|
||||
material,
|
||||
normal: (p2 - p1).cross(&(p3 - p1)).get_unit(),
|
||||
v0,
|
||||
v1,
|
||||
d00,
|
||||
d01,
|
||||
d11,
|
||||
denom: d00 * d11 - d01 * d01,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make the global Triangle::hit function not take precomped u,v somehow
|
||||
// FIXME/DEBT: this function recalculates values for each pass that can be cached on the
|
||||
// triangle struct itself. REFACTOR to not use this function
|
||||
pub fn to_uv(p1: &Vec3, p2: &Vec3, p3: &Vec3, point: &Vec3) -> Vec3 {
|
||||
let v0 = *p2 - p1;
|
||||
let v1 = *p3 - p1;
|
||||
let v2 = *point - p1;
|
||||
let d00 = v0.dot(&v0);
|
||||
let d01 = v0.dot(&v1);
|
||||
let d11 = v1.dot(&v1);
|
||||
let denom = d00 * d11 - d01 * d01;
|
||||
|
||||
let d20 = v2.dot(&v0);
|
||||
let d21 = v2.dot(&v1);
|
||||
|
||||
let v = (d11 * d20 - d01 * d21) / denom;
|
||||
let w = (d00 * d21 - d01 * d20) / denom;
|
||||
Vec3::new(1. - v - w, v, w)
|
||||
}
|
||||
|
||||
pub fn hit(
|
||||
p1: Vec3,
|
||||
p2: Vec3,
|
||||
@@ -45,6 +86,7 @@ impl Triangle {
|
||||
if t < 0. {
|
||||
return None;
|
||||
};
|
||||
let uvw = Triangle::to_uv(&p1, &p2, &p3, &r.at(t));
|
||||
|
||||
let p = r.at(t);
|
||||
let a4 = (p3 - p1).cross(&(p2 - p1)).length();
|
||||
@@ -54,7 +96,15 @@ impl Triangle {
|
||||
|
||||
let diff = (a4 - a1 - a2 - a3).abs();
|
||||
if diff < 0.001 {
|
||||
Some(Hit::new(t, p, normal, material, normal.dot(&r.dir()) < 0.))
|
||||
Some(Hit::new(
|
||||
t,
|
||||
p,
|
||||
normal,
|
||||
material,
|
||||
normal.dot(&r.dir()) < 0.,
|
||||
*uvw.x(),
|
||||
*uvw.y(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -83,4 +133,14 @@ impl Hittable for Triangle {
|
||||
r,
|
||||
)
|
||||
}
|
||||
|
||||
fn to_uv(&self, point: &Vec3) -> Vec3 {
|
||||
let v2 = *point - self.p1;
|
||||
let d20 = v2.dot(&self.v0);
|
||||
let d21 = v2.dot(&self.v1);
|
||||
|
||||
let v = (self.d11 * d20 - self.d01 * d21) / self.denom;
|
||||
let w = (self.d00 * d21 - self.d01 * d20) / self.denom;
|
||||
Vec3::new(1. - v - w, v, w)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::objects::materials::{dielectric::Dielectric, lambertian::{Lambertian, Metal}, normal::Normal, traits::Material};
|
||||
use crate::objects::materials::{dielectric::Dielectric, lambertian::{Lambertian, Metal}, normal::Normal, texture::Texture, traits::Material};
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -12,6 +12,7 @@ pub(crate) enum MaterialDef {
|
||||
Metal(Metal),
|
||||
Dielectric(Dielectric),
|
||||
Normal(Normal),
|
||||
Texture(RawTexture),
|
||||
}
|
||||
|
||||
impl MaterialDef {
|
||||
@@ -21,7 +22,13 @@ impl MaterialDef {
|
||||
MaterialDef::Metal(m) => Arc::new(m),
|
||||
MaterialDef::Dielectric(d) => Arc::new(d),
|
||||
MaterialDef::Normal(n) => Arc::new(n),
|
||||
MaterialDef::Texture(t) => Arc::new(Texture::new(&t.source)), // FIXME: error handling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct RawTexture {
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,14 @@ impl Vec3 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_u8(r: u8, b: u8, g: u8) -> Self {
|
||||
Self {
|
||||
x: r as f32 / 255.,
|
||||
y: g as f32 / 255.,
|
||||
z: b as f32/ 255.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random() -> Self {
|
||||
let mut rng = rand::rng();
|
||||
Self {
|
||||
@@ -492,3 +500,4 @@ impl Display for Vec3 {
|
||||
write!(f, "({}, {}, {})", self.x, self.y, self.z)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user