ft: refactor materials/texture decoupling

made it so all materials can use a texture for colour source. this
should be extendable if we want to do e.g. coded patterns in future
This commit is contained in:
2026-09-13 14:53:28 +02:00
parent 968c232388
commit eddde10023
12 changed files with 89 additions and 69 deletions

View File

@@ -27,10 +27,8 @@ fn main() {
if args.len() > 1 {
json_file = &args[1];
}
println!("we done serializingiaonosnl");
let json_str = fs::read_to_string(json_file).expect("Reading specified scene file failed!");
let mut scene: Scene = serde_json::from_str(&json_str).unwrap();
let mut scene: Scene = Scene::random();
render(&mut scene);
}

View File

@@ -1,33 +1,16 @@
use serde::Deserialize;
use crate::{objects::{hit::Hit, materials::texture::Texture}, vec3::Colour};
use crate::{
objects::{hit::Hit, materials::texture::Texture},
vec3::Colour,
};
// FIXME (high priority): we can't deserialize this enum as-is, because of the texture.
// To fix this we'll have to pull the same trick we did for materialDef/sceneDef and the texture
// pointer, but i'm not sure what that implies for all materials.. maybe theres a clever way to
// solve that somehow, but from where im looking at it we'll need to manually deserialize everything
// that uses coloursource ://
#[derive(Debug)]
pub enum ColourSource {
Colour(Colour),
Texture(Texture),
}
// REFACTOR: not entirely happy having this serialization code here. move that.
#[derive(Deserialize)]
#[serde(untagged)]
enum RawColourSource {
Colour(Colour),
Texture(RawTexture),
}
#[derive(Deserialize)]
pub(crate) struct RawTexture {
pub source: String,
}
impl ColourSource {
pub fn get_colour(&self, hit: &Hit) -> Colour {
match self {
@@ -35,13 +18,30 @@ impl ColourSource {
ColourSource::Texture(texture) => texture.colour_at(hit),
}
}
pub fn rgb(r: f32, g: f32, b: f32) -> Self {
Self::Colour(Colour::new(r, g, b))
}
}
impl Default for ColourSource {
fn default() -> Self {
Self::rgb(1., 1., 1.)
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum RawColourSource {
Colour(Colour),
Texture(String),
}
impl From<RawColourSource> for ColourSource {
fn from(value: RawColourSource) -> Self {
match value {
RawColourSource::Colour(c) => Self::Colour(c),
RawColourSource::Texture(t) => Self::Texture(Texture::new(&t.source)),
RawColourSource::Texture(t) => Self::Texture(Texture::new(&t)),
}
}
}

View File

@@ -4,21 +4,33 @@ use rand::RngExt;
use serde::Deserialize;
use crate::{
objects::{hit::Hit, materials::traits::Material},
objects::{
hit::Hit,
materials::{colour_source::ColourSource, traits::Material},
},
ray::Ray,
vec3::Colour,
};
// TODO (low priority): add support for coloured dielectrics (add colour to the struct, return that
// instead of 1,1,1)
// THOUGHT: not entirely happy with this implementation; i ideally want to specify transparent
// dielectrics, but that will require a more substantial refactor wrt compositing + vec4 colours.
#[derive(Debug, Deserialize)]
pub struct Dielectric {
#[serde(default)]
colour: ColourSource,
refraction_index: f32,
}
impl Dielectric {
pub fn new(refraction_index: f32) -> Self {
Self { refraction_index }
Self::new_coloured(refraction_index, ColourSource::rgb(1., 1., 1.))
}
pub fn new_coloured(refraction_index: f32, colour: ColourSource) -> Self {
Self {
refraction_index,
colour,
}
}
fn reflectance(cos: f32, refraction_index: f32) -> f32 {
@@ -52,6 +64,6 @@ impl Material for Dielectric {
} else {
unit.refract(hit.n(), ri)
};
Some((Some(Ray::new(*hit.p(), dir)), Colour::new(1., 1., 1.)))
Some((Some(Ray::new(*hit.p(), dir)), self.colour.get_colour(hit)))
}
}

View File

@@ -3,24 +3,28 @@ use serde::Deserialize;
use std::fmt::Debug;
use crate::{
objects::{hit::Hit, materials::{colour_source::ColourSource, traits::Material}}, ray::Ray, vec3::{Colour, Vec3},
objects::{
hit::Hit,
materials::{colour_source::ColourSource, traits::Material},
},
ray::Ray,
vec3::{Colour, Vec3},
};
#[derive(Deserialize)]
pub struct Lambertian {
albedo: ColourSource,
// albedo: Colour,
colour: ColourSource,
prob: f32,
}
impl Lambertian {
pub fn new(albedo: ColourSource, prob: f32) -> Self {
Self { albedo, prob }
pub fn new(colour: ColourSource, prob: f32) -> Self {
Self { colour, prob }
}
pub fn rgb(r: f32, g: f32, b: f32, prob: f32) -> Self {
Self {
albedo: ColourSource::Colour(Colour::new(r, g, b)),
colour: ColourSource::rgb(r, g, b),
prob,
}
}
@@ -28,9 +32,10 @@ impl Lambertian {
impl Debug for Lambertian {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Lambertian").field("prob", &self.prob).finish()
// .field("albedo", &self.albedo)
// FIXME: readd albedo field w proper syntax ^
f.debug_struct("Lambertian")
.field("prob", &self.prob)
.field("colour", &self.colour)
.finish()
}
}
@@ -43,7 +48,7 @@ impl Material for Lambertian {
dir = *hit.n();
}
let scattered = Ray::new(*hit.p(), dir);
return Some((Some(scattered), self.albedo.get_colour(hit)));
return Some((Some(scattered), self.colour.get_colour(hit)));
}
None
}
@@ -51,19 +56,19 @@ impl Material for Lambertian {
#[derive(Debug, Deserialize)]
pub struct Metal {
albedo: Colour,
colour: ColourSource,
prob: f32,
fuzz: f32,
}
impl Metal {
pub fn new(albedo: Colour, prob: f32, fuzz: f32) -> Self {
Self { albedo, prob, fuzz }
pub fn new(colour: ColourSource, prob: f32, fuzz: f32) -> Self {
Self { colour, prob, fuzz }
}
pub fn rgb(r: f32, g: f32, b: f32, prob: f32, fuzz: f32) -> Self {
Self {
albedo: Colour::new(r, g, b),
colour: ColourSource::rgb(r, g, b),
prob,
fuzz,
}
@@ -76,7 +81,7 @@ impl Material for Metal {
if self.prob >= rng.random::<f32>() {
let mut refl = ray.dir().reflect(hit.n());
refl = refl.get_unit() + self.fuzz * Vec3::random_unit();
return Some((Some(Ray::new(*hit.p(), refl)), self.albedo));
return Some((Some(Ray::new(*hit.p(), refl)), self.colour.get_colour(hit)));
}
None
}

View File

@@ -72,7 +72,7 @@ pub fn render_chunk(chunk: &mut [u8], scene: &Scene, y: u32) {
chunk[(i * 3) as usize + 1] = g;
chunk[(i * 3) as usize + 2] = b;
}
trace!("Line {}\tfinished.", y);
trace!("Line {:4} finished.", y);
}
pub fn ray_colour(hittables: &Vec<Arc<dyn Hittable>>, r: &Ray, depth: u32) -> Colour {

View File

@@ -29,7 +29,7 @@ pub struct Scene {
image_width: u32,
image_height: u32,
// raytracing
// raytracing
max_depth: u32,
}
@@ -79,7 +79,10 @@ impl Scene {
world.push(Arc::new(Sphere::new(
center,
0.2,
Arc::new(Lambertian::new(ColourSource::Colour(Vec3::random() * Vec3::random()), 1.)),
Arc::new(Lambertian::new(
ColourSource::Colour(Vec3::random() * Vec3::random()),
1.,
)),
)));
} else if mat < 0.95 {
// metal
@@ -187,7 +190,7 @@ struct SceneDef {
pub max_depth: u32,
}
// TODO (low priority): rn textures are tightly coupled to materials; what if we wnat different
// TODO (low priority): rn textures are tightly coupled to materials; what if we want different
// materials to share a texture? apply the same technique we used for decoupling materials/objects
// on textures/materials.
impl<'de> Deserialize<'de> for Scene {