From 968c2323883a2b0096168bb339693599085794f9 Mon Sep 17 00:00:00 2001 From: Djairo Date: Sun, 13 Sep 2026 11:58:25 +0200 Subject: [PATCH] ft (wip): texture/material overhaul --- scenes/scene.json | 2 +- scenes/texture.json | 4 +- src/main.rs | 1 + src/objects/materials.rs | 1 + src/objects/materials/colour_source.rs | 57 ++++++++++++++++++++++++++ src/objects/materials/dielectric.rs | 2 + src/objects/materials/lambertian.rs | 24 +++++++---- src/objects/materials/texture.rs | 15 ++----- src/raytracer.rs | 2 +- src/scenes/material_def.rs | 8 ---- src/scenes/scene.rs | 6 ++- 11 files changed, 89 insertions(+), 33 deletions(-) create mode 100644 src/objects/materials/colour_source.rs diff --git a/scenes/scene.json b/scenes/scene.json index 39b0a36..feb28fc 100644 --- a/scenes/scene.json +++ b/scenes/scene.json @@ -4,7 +4,7 @@ "image_height": 1080, "max_depth": 50, "camera": { - "anti_alias_rate": 23, + "anti_alias_rate": 2, "fov": 70.0, "look_from": [-10, 4, 15], "look_at": [-11.0, 0.0, 0.0], diff --git a/scenes/texture.json b/scenes/texture.json index 8f631ab..2145886 100644 --- a/scenes/texture.json +++ b/scenes/texture.json @@ -14,8 +14,8 @@ }, "materials": [ { "type": "lambertian", "albedo": [0.2, 0.2, 0.2], "prob": 0.8 }, - {"type": "texture", "source": "./textures/earthmap1k.png"}, - {"type": "texture", "source": "./textures/bluegrid.png"} + { "type": "lambertian", "albedo": { "source": "./textures/earthmap1k.png" }, "prob": 1.0 }, + { "type": "lambertian", "albedo": { "source": "./textures/bluegrid.png" }, "prob": 0.5 } ], "objects": [ { "type": "sphere", "center": [0.0, 0.0, -1.2], "radius": 0.9, "material": 2}, diff --git a/src/main.rs b/src/main.rs index 94bd04a..38a142c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ 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(); diff --git a/src/objects/materials.rs b/src/objects/materials.rs index b4ae60d..cbba327 100644 --- a/src/objects/materials.rs +++ b/src/objects/materials.rs @@ -3,3 +3,4 @@ pub mod lambertian; pub mod normal; pub mod traits; pub mod texture; +pub mod colour_source; diff --git a/src/objects/materials/colour_source.rs b/src/objects/materials/colour_source.rs new file mode 100644 index 0000000..c6fdc26 --- /dev/null +++ b/src/objects/materials/colour_source.rs @@ -0,0 +1,57 @@ +use serde::Deserialize; + +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 :// +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 { + ColourSource::Colour(vec3) => *vec3, + ColourSource::Texture(texture) => texture.colour_at(hit), + } + } +} + +impl From for ColourSource { + fn from(value: RawColourSource) -> Self { + match value { + RawColourSource::Colour(c) => Self::Colour(c), + RawColourSource::Texture(t) => Self::Texture(Texture::new(&t.source)), + } + } +} + +impl<'de> Deserialize<'de> for ColourSource { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let raw = RawColourSource::deserialize(deserializer)?; + Ok(raw.into()) + } +} diff --git a/src/objects/materials/dielectric.rs b/src/objects/materials/dielectric.rs index bb1dad3..36d2108 100644 --- a/src/objects/materials/dielectric.rs +++ b/src/objects/materials/dielectric.rs @@ -9,6 +9,8 @@ use crate::{ vec3::Colour, }; +// TODO (low priority): add support for coloured dielectrics (add colour to the struct, return that +// instead of 1,1,1) #[derive(Debug, Deserialize)] pub struct Dielectric { refraction_index: f32, diff --git a/src/objects/materials/lambertian.rs b/src/objects/materials/lambertian.rs index bfbba05..4ec803b 100644 --- a/src/objects/materials/lambertian.rs +++ b/src/objects/materials/lambertian.rs @@ -1,31 +1,39 @@ use rand::RngExt; use serde::Deserialize; +use std::fmt::Debug; use crate::{ - objects::{hit::Hit, materials::traits::Material}, - ray::Ray, - vec3::{Colour, Vec3}, + objects::{hit::Hit, materials::{colour_source::ColourSource, traits::Material}}, ray::Ray, vec3::{Colour, Vec3}, }; -#[derive(Debug, Deserialize)] +#[derive(Deserialize)] pub struct Lambertian { - albedo: Colour, + albedo: ColourSource, + // albedo: Colour, prob: f32, } impl Lambertian { - pub fn new(albedo: Colour, prob: f32) -> Self { + pub fn new(albedo: ColourSource, prob: f32) -> Self { Self { albedo, prob } } pub fn rgb(r: f32, g: f32, b: f32, prob: f32) -> Self { Self { - albedo: Colour::new(r, g, b), + albedo: ColourSource::Colour(Colour::new(r, g, b)), prob, } } } +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 ^ + } +} + impl Material for Lambertian { fn scatter(&self, hit: &Hit, _ray: &Ray) -> Option<(Option, Colour)> { let mut rng = rand::rng(); @@ -35,7 +43,7 @@ impl Material for Lambertian { dir = *hit.n(); } let scattered = Ray::new(*hit.p(), dir); - return Some((Some(scattered), self.albedo)); + return Some((Some(scattered), self.albedo.get_colour(hit))); } None } diff --git a/src/objects/materials/texture.rs b/src/objects/materials/texture.rs index 87dc559..ed55130 100644 --- a/src/objects/materials/texture.rs +++ b/src/objects/materials/texture.rs @@ -2,16 +2,10 @@ use image::{DynamicImage, ImageReader}; use log::trace; use crate::{ - objects::{hit::Hit, materials::traits::Material}, - ray::Ray, + objects::hit::Hit, vec3::Vec3, }; -// TODO: overhaul texture implementation so we can support textures for arbitrary materials. -// Basically remove this struct, and instead make lambertian/metal/dielectric/take either a texture -// or a flat colour -// I think we can use an enum ColourSource with a function to fetch a specific colour depending on -// if we have a texture/flat colour. and yeah. #[derive(Debug)] pub struct Texture { source: DynamicImage, @@ -69,12 +63,9 @@ impl Texture { b[self.stride * idx + 1], ) } -} -impl Material for Texture { - fn scatter(&self, hit: &Hit, _ray: &Ray) -> Option<(Option, Vec3)> { + pub fn colour_at(&self, hit: &Hit) -> Vec3 { let (u, v) = hit.uv(); - let col = self._at(u, 1. - v); - Some((None, col)) + self._at(u, 1. - v) } } diff --git a/src/raytracer.rs b/src/raytracer.rs index 9d32b1c..90324ae 100644 --- a/src/raytracer.rs +++ b/src/raytracer.rs @@ -54,7 +54,7 @@ pub fn render(scene: &mut Scene) { pub fn render_chunk(chunk: &mut [u8], scene: &Scene, y: u32) { let camera = &scene.get_camera(); - let aa_rate = &scene.get_camera().get_anti_alias_rate(); + let aa_rate = camera.get_anti_alias_rate(); for i in 0..scene.get_image_width() { let pixel_tl = camera.get_pixel_tl(i, y); diff --git a/src/scenes/material_def.rs b/src/scenes/material_def.rs index 87a73f6..fb6522a 100644 --- a/src/scenes/material_def.rs +++ b/src/scenes/material_def.rs @@ -12,7 +12,6 @@ pub(crate) enum MaterialDef { Metal(Metal), Dielectric(Dielectric), Normal(Normal), - Texture(RawTexture), } impl MaterialDef { @@ -22,13 +21,6 @@ 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)), } } } - -#[derive(Deserialize)] -pub(crate) struct RawTexture { - pub source: String, -} - diff --git a/src/scenes/scene.rs b/src/scenes/scene.rs index 5303e31..9984ecb 100644 --- a/src/scenes/scene.rs +++ b/src/scenes/scene.rs @@ -1,3 +1,4 @@ +use crate::objects::materials::colour_source::ColourSource; use std::sync::Arc; use rand::RngExt; @@ -78,7 +79,7 @@ impl Scene { world.push(Arc::new(Sphere::new( center, 0.2, - Arc::new(Lambertian::new(Vec3::random() * Vec3::random(), 1.)), + Arc::new(Lambertian::new(ColourSource::Colour(Vec3::random() * Vec3::random()), 1.)), ))); } else if mat < 0.95 { // metal @@ -186,6 +187,9 @@ struct SceneDef { pub max_depth: u32, } +// TODO (low priority): rn textures are tightly coupled to materials; what if we wnat 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 { fn deserialize(deserializer: D) -> Result where