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::{
|
use crate::{
|
||||||
objects::{materials::traits::Material, traits::Hittable},
|
objects::{materials::traits::Material, traits::Hittable},
|
||||||
@@ -45,8 +45,27 @@ impl Hittable for Circle {
|
|||||||
|
|
||||||
let p = r.at(t);
|
let p = r.at(t);
|
||||||
if (p - self.center).length() < self.radius {
|
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
|
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> {
|
fn hit(&self, r: &Ray) -> Option<Hit> {
|
||||||
Hit::hit_list(&self.faces, r)
|
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> {
|
fn hit(&self, r: &crate::ray::Ray) -> Option<super::hit::Hit> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_uv(&self, point: &Vec3) -> Vec3 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,16 +12,20 @@ pub struct Hit {
|
|||||||
n: Vec3,
|
n: Vec3,
|
||||||
mat: Arc<dyn Material>,
|
mat: Arc<dyn Material>,
|
||||||
front_face: bool,
|
front_face: bool,
|
||||||
|
u: f32,
|
||||||
|
v: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hit {
|
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 {
|
Self {
|
||||||
t,
|
t,
|
||||||
p,
|
p,
|
||||||
n: if front_face { n } else { -n },
|
n: if front_face { n } else { -n },
|
||||||
mat,
|
mat,
|
||||||
front_face,
|
front_face,
|
||||||
|
u,
|
||||||
|
v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +49,10 @@ impl Hit {
|
|||||||
self.front_face
|
self.front_face
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn uv(&self) -> (f32, f32) {
|
||||||
|
(self.u, self.v)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: use front_face to discard back-hits for culling
|
// TODO: use front_face to discard back-hits for culling
|
||||||
pub fn hit_list(hittables: &Vec<Arc<dyn Hittable>>, r: &Ray) -> Option<Hit> {
|
pub fn hit_list(hittables: &Vec<Arc<dyn Hittable>>, r: &Ray) -> Option<Hit> {
|
||||||
let mut closest: Option<Hit> = None;
|
let mut closest: Option<Hit> = None;
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ pub mod dielectric;
|
|||||||
pub mod lambertian;
|
pub mod lambertian;
|
||||||
pub mod normal;
|
pub mod normal;
|
||||||
pub mod traits;
|
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,
|
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 core::f32::math::sqrt;
|
||||||
|
use std::f32::consts::PI;
|
||||||
use std::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -14,7 +15,6 @@ pub struct Sphere {
|
|||||||
material: Arc<dyn Material>,
|
material: Arc<dyn Material>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Debug for Sphere {
|
impl Debug for Sphere {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Sphere")
|
f.debug_struct("Sphere")
|
||||||
@@ -59,13 +59,26 @@ impl Hittable for Sphere {
|
|||||||
let t = if tl > 0.001 { tl } else { tr };
|
let t = if tl > 0.001 { tl } else { tr };
|
||||||
let p = r.at(t);
|
let p = r.at(t);
|
||||||
let out_n = (p - self.center) / self.radius;
|
let out_n = (p - self.center) / self.radius;
|
||||||
|
let uv = self.to_uv(&p);
|
||||||
Some(Hit::new(
|
Some(Hit::new(
|
||||||
t,
|
t,
|
||||||
p,
|
p,
|
||||||
(p - self.center).get_unit(),
|
(p - self.center).get_unit(),
|
||||||
self.material.clone(),
|
self.material.clone(),
|
||||||
out_n.dot(r.dir()) < 0.,
|
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::Ray;
|
||||||
use crate::objects::hit::Hit;
|
use crate::objects::hit::Hit;
|
||||||
|
use crate::vec3::Vec3;
|
||||||
|
|
||||||
pub trait Hittable: Debug + Send + Sync {
|
pub trait Hittable: Debug + Send + Sync {
|
||||||
fn hit(&self, r: &Ray) -> Option<Hit>;
|
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,
|
p3: Vec3,
|
||||||
material: Arc<dyn Material>,
|
material: Arc<dyn Material>,
|
||||||
normal: Vec3,
|
normal: Vec3,
|
||||||
|
|
||||||
|
// barycentric helpers
|
||||||
|
v0: Vec3,
|
||||||
|
v1: Vec3,
|
||||||
|
d00: f32,
|
||||||
|
d01: f32,
|
||||||
|
d11: f32,
|
||||||
|
denom: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Triangle {
|
impl Triangle {
|
||||||
pub fn new(p1: Vec3, p2: Vec3, p3: Vec3, material: Arc<dyn Material>) -> Self {
|
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 {
|
Self {
|
||||||
p1,
|
p1,
|
||||||
p2,
|
p2,
|
||||||
p3,
|
p3,
|
||||||
material,
|
material,
|
||||||
normal: (p2 - p1).cross(&(p3 - p1)).get_unit(),
|
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(
|
pub fn hit(
|
||||||
p1: Vec3,
|
p1: Vec3,
|
||||||
p2: Vec3,
|
p2: Vec3,
|
||||||
@@ -45,6 +86,7 @@ impl Triangle {
|
|||||||
if t < 0. {
|
if t < 0. {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
let uvw = Triangle::to_uv(&p1, &p2, &p3, &r.at(t));
|
||||||
|
|
||||||
let p = r.at(t);
|
let p = r.at(t);
|
||||||
let a4 = (p3 - p1).cross(&(p2 - p1)).length();
|
let a4 = (p3 - p1).cross(&(p2 - p1)).length();
|
||||||
@@ -54,7 +96,15 @@ impl Triangle {
|
|||||||
|
|
||||||
let diff = (a4 - a1 - a2 - a3).abs();
|
let diff = (a4 - a1 - a2 - a3).abs();
|
||||||
if diff < 0.001 {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -83,4 +133,14 @@ impl Hittable for Triangle {
|
|||||||
r,
|
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 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)]
|
#[derive(Deserialize)]
|
||||||
@@ -12,6 +12,7 @@ pub(crate) enum MaterialDef {
|
|||||||
Metal(Metal),
|
Metal(Metal),
|
||||||
Dielectric(Dielectric),
|
Dielectric(Dielectric),
|
||||||
Normal(Normal),
|
Normal(Normal),
|
||||||
|
Texture(RawTexture),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaterialDef {
|
impl MaterialDef {
|
||||||
@@ -21,7 +22,13 @@ impl MaterialDef {
|
|||||||
MaterialDef::Metal(m) => Arc::new(m),
|
MaterialDef::Metal(m) => Arc::new(m),
|
||||||
MaterialDef::Dielectric(d) => Arc::new(d),
|
MaterialDef::Dielectric(d) => Arc::new(d),
|
||||||
MaterialDef::Normal(n) => Arc::new(n),
|
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 {
|
pub fn random() -> Self {
|
||||||
let mut rng = rand::rng();
|
let mut rng = rand::rng();
|
||||||
Self {
|
Self {
|
||||||
@@ -492,3 +500,4 @@ impl Display for Vec3 {
|
|||||||
write!(f, "({}, {}, {})", self.x, self.y, self.z)
|
write!(f, "({}, {}, {})", self.x, self.y, self.z)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user