From c517a836eec04054400b7dab332fb777250f2eb7 Mon Sep 17 00:00:00 2001 From: hal8174 Date: Tue, 1 Oct 2024 14:37:50 +0200 Subject: [PATCH] Extract renderer into own packege --- Cargo.lock | 10 +- Cargo.toml | 2 +- ray-tracing-core/src/camera.rs | 1 - ray-tracing-core/src/lib.rs | 1 + ray-tracing-core/src/light.rs | 1 - ray-tracing-core/src/material.rs | 26 +++- ray-tracing-core/src/math/dir3.rs | 31 +++- ray-tracing-core/src/renderer.rs | 133 +----------------- ray-tracing-core/src/scene.rs | 1 - ray-tracing-image/Cargo.toml | 1 + ray-tracing-image/src/main.rs | 9 +- ray-tracing-material/Cargo.toml | 1 - ray-tracing-material/src/default.rs | 1 - ray-tracing-material/src/diffuse.rs | 1 - ray-tracing-material/src/lib.rs | 2 + ray-tracing-material/src/mirror.rs | 23 +++ ray-tracing-renderer/Cargo.toml | 7 + ray-tracing-renderer/src/depth_renderer.rs | 56 ++++++++ ray-tracing-renderer/src/lib.rs | 3 + ray-tracing-renderer/src/path_tracer.rs | 76 ++++++++++ .../src/path_tracer_importance.rs | 77 ++++++++++ ray-tracing-scene/Cargo.toml | 1 - ray-tracing-scene/src/basic_scene.rs | 7 +- ray-tracing-scene/src/triangle_bvh.rs | 18 ++- 24 files changed, 332 insertions(+), 157 deletions(-) create mode 100644 ray-tracing-material/src/mirror.rs create mode 100644 ray-tracing-renderer/Cargo.toml create mode 100644 ray-tracing-renderer/src/depth_renderer.rs create mode 100644 ray-tracing-renderer/src/lib.rs create mode 100644 ray-tracing-renderer/src/path_tracer.rs create mode 100644 ray-tracing-renderer/src/path_tracer_importance.rs diff --git a/Cargo.lock b/Cargo.lock index 801bfeb..d422d27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -727,6 +727,7 @@ dependencies = [ "image", "rand", "ray-tracing-core", + "ray-tracing-renderer", "ray-tracing-scene", "rayon", ] @@ -735,7 +736,13 @@ dependencies = [ name = "ray-tracing-material" version = "0.1.0" dependencies = [ - "rand", + "ray-tracing-core", +] + +[[package]] +name = "ray-tracing-renderer" +version = "0.1.0" +dependencies = [ "ray-tracing-core", ] @@ -743,7 +750,6 @@ dependencies = [ name = "ray-tracing-scene" version = "0.1.0" dependencies = [ - "rand", "ray-tracing-core", "ray-tracing-material", ] diff --git a/Cargo.toml b/Cargo.toml index 3b77f21..9d3fbfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] -members = [ "ray-tracing-core", "ray-tracing-image", "ray-tracing-material", "ray-tracing-scene"] +members = [ "ray-tracing-core", "ray-tracing-image", "ray-tracing-material", "ray-tracing-renderer", "ray-tracing-scene"] resolver = "2" diff --git a/ray-tracing-core/src/camera.rs b/ray-tracing-core/src/camera.rs index affdca4..994e3a0 100644 --- a/ray-tracing-core/src/camera.rs +++ b/ray-tracing-core/src/camera.rs @@ -1,5 +1,4 @@ use crate::prelude::*; -use rand::Rng; pub trait Camera { fn forward(&self, x: u32, y: u32, rng: &mut R) -> Ray; diff --git a/ray-tracing-core/src/lib.rs b/ray-tracing-core/src/lib.rs index cd512f1..ba5a18b 100644 --- a/ray-tracing-core/src/lib.rs +++ b/ray-tracing-core/src/lib.rs @@ -16,5 +16,6 @@ pub mod prelude { pub use crate::material::Material; pub use crate::math::*; pub use crate::ray::Ray; + pub use rand::Rng; pub use std::f32::consts as FloatConsts; } diff --git a/ray-tracing-core/src/light.rs b/ray-tracing-core/src/light.rs index 300ad7e..3829865 100644 --- a/ray-tracing-core/src/light.rs +++ b/ray-tracing-core/src/light.rs @@ -1,5 +1,4 @@ use crate::prelude::*; -use rand::Rng; pub trait Light: Sync { fn emit(&self, w_in: Dir3, rng: &mut R) -> Color; diff --git a/ray-tracing-core/src/material.rs b/ray-tracing-core/src/material.rs index cb55d74..cb9e60f 100644 --- a/ray-tracing-core/src/material.rs +++ b/ray-tracing-core/src/material.rs @@ -1,7 +1,31 @@ use crate::prelude::*; -use rand::Rng; /// All calculations for the material are done a tangent space of the intersection. pub trait Material: Sync { fn eval(&self, w_in: Dir3, w_out: Dir3, rng: &mut R) -> Color; + + fn sample(&self, w_in: Dir3, rng: &mut R) -> SampleResult { + let w_out = Dir3::sample_cosine_hemisphere(rng); + + SampleResult::new(w_out, self.eval(w_in, w_out, rng)) + } +} + +pub struct SampleResult { + w_out: Dir3, + color: Color, +} + +impl SampleResult { + pub fn new(w_out: Dir3, color: Color) -> Self { + Self { w_out, color } + } + + pub fn w_out(&self) -> Dir3 { + self.w_out + } + + pub fn color(&self) -> Color { + self.color + } } diff --git a/ray-tracing-core/src/math/dir3.rs b/ray-tracing-core/src/math/dir3.rs index 7091e9c..047ac42 100644 --- a/ray-tracing-core/src/math/dir3.rs +++ b/ray-tracing-core/src/math/dir3.rs @@ -1,5 +1,4 @@ use crate::prelude::*; -use rand::Rng; use std::ops::{Add, Div, Mul, Neg, Sub}; #[derive(Debug, Clone, Copy)] @@ -62,15 +61,39 @@ impl Dir3 { ) } - pub fn generate_uniform_hemisphere(rng: &mut R) -> Self { + pub fn sample_uniform_hemisphere(rng: &mut R) -> Self { let theta = Float::acos(1.0 - rng.gen::()); let phi = 2.0 * FloatConsts::PI * rng.gen::(); let r = Float::sin(theta); Dir3::new(Float::sin(phi) * r, Float::cos(theta), Float::cos(phi) * r) } - pub fn generate_cosine_hemisphere(rng: &mut R) -> Self { - todo!() + pub fn sample_cosine_hemisphere(rng: &mut R) -> Self { + let mut d = Self::sample_disc(rng); + d.y = Float::sqrt(Float::max(0.0, 1.0 - d.x * d.x - d.z * d.z)); + d + } + + /// https://www.pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations#ConcentricSampleDisk + pub fn sample_disc(rng: &mut R) -> Self { + let x = 2.0 * rng.gen::() - 1.0; + let y = 2.0 * rng.gen::() - 1.0; + + if x == 0.0 && y == 0.0 { + return Self::zero(); + } + + let r; + let theta; + if x.abs() > y.abs() { + r = x; + theta = FloatConsts::FRAC_PI_4 * (y / x); + } else { + r = y; + theta = FloatConsts::FRAC_PI_2 - FloatConsts::FRAC_PI_4 * (x / y); + } + + r * Dir3::new(Float::cos(theta), 0.0, Float::sin(theta)) } } diff --git a/ray-tracing-core/src/renderer.rs b/ray-tracing-core/src/renderer.rs index 3ba7a73..fba048f 100644 --- a/ray-tracing-core/src/renderer.rs +++ b/ray-tracing-core/src/renderer.rs @@ -1,138 +1,7 @@ -use std::marker::PhantomData; - -use crate::{camera::Camera, prelude::*, scene::Scene}; -use rand::Rng; +use crate::prelude::*; pub trait ClassicalRenderer { fn render_pixel(&self, x: u32, y: u32, rng: &mut R) -> Color; fn width(&self) -> u32; fn height(&self) -> u32; } - -pub struct PathTracer -where - S: Scene, - C: Camera, - R: Rng, -{ - scene: S, - camera: C, - rng: PhantomData, -} - -impl PathTracer -where - S: Scene, - C: Camera, - R: Rng, -{ - pub fn new(scene: S, camera: C) -> Self { - Self { - scene, - camera, - rng: PhantomData {}, - } - } -} - -impl ClassicalRenderer for PathTracer -where - S: Scene, - C: Camera, - R: Rng, -{ - fn render_pixel(&self, x: u32, y: u32, rng: &mut R) -> Color { - let mut sum = Color::black(); - let mut alpha = Color::white(); - - let mut r = self.camera.forward(x, y, rng); - - let mut count = 0; - - while let Some(i) = self.scene.intersect(r, 0.001, Float::INFINITY) { - let frame = i.tangent_frame(); - let w_in = frame.to_frame(-r.dir()); - let w_out = Dir3::generate_uniform_hemisphere(rng); - - if let Some(light) = i.light() { - sum += alpha * light.emit(w_in, rng) * w_out.y(); - } - - if let Some(material) = i.material() { - alpha *= material.eval(w_in, w_out, rng) * w_out.y(); - } else { - return sum; - } - - r = Ray::new(r.at(i.t()), frame.to_world(w_out), r.time()); - count += 1; - if count > 4 { - break; - } - } - - sum - } - - fn width(&self) -> u32 { - self.camera.width() - } - - fn height(&self) -> u32 { - self.camera.height() - } -} - -pub struct DepthRenderer -where - S: Scene, - C: Camera, - R: Rng, -{ - scene: S, - camera: C, - rng: PhantomData, -} - -impl DepthRenderer -where - S: Scene, - C: Camera, - R: Rng, -{ - pub fn new(scene: S, camera: C) -> Self { - Self { - scene, - camera, - rng: PhantomData {}, - } - } -} - -impl ClassicalRenderer for DepthRenderer -where - S: Scene, - C: Camera, - R: Rng, -{ - fn render_pixel(&self, x: u32, y: u32, rng: &mut R) -> Color { - let r = self.camera.forward(x, y, rng); - - if let Some(i) = self.scene.intersect(r, 0.0, Float::INFINITY) { - // Color::gray(1.0 / i.t()) - let c = 0.5 * (i.normal() + Dir3::new(1.0, 1.0, 1.0)); - // let c = i.normal(); - Color::new(c.x, c.y, c.z) - } else { - Color::black() - } - } - - fn width(&self) -> u32 { - self.camera.width() - } - - fn height(&self) -> u32 { - self.camera.height() - } -} diff --git a/ray-tracing-core/src/scene.rs b/ray-tracing-core/src/scene.rs index 8c07fb1..0297de8 100644 --- a/ray-tracing-core/src/scene.rs +++ b/ray-tracing-core/src/scene.rs @@ -1,5 +1,4 @@ use crate::prelude::*; -use rand::Rng; pub trait Scene { fn intersect(&self, ray: Ray, min: Float, max: Float) -> Option>; diff --git a/ray-tracing-image/Cargo.toml b/ray-tracing-image/Cargo.toml index 2c289fd..2543713 100644 --- a/ray-tracing-image/Cargo.toml +++ b/ray-tracing-image/Cargo.toml @@ -8,4 +8,5 @@ image = "0.25.2" rand = { version = "0.8.5", features = ["small_rng"] } ray-tracing-core = { path = "../ray-tracing-core" } ray-tracing-scene = { path = "../ray-tracing-scene" } +ray-tracing-renderer = { path = "../ray-tracing-renderer" } rayon = "1.10.0" diff --git a/ray-tracing-image/src/main.rs b/ray-tracing-image/src/main.rs index da9515e..e139532 100644 --- a/ray-tracing-image/src/main.rs +++ b/ray-tracing-image/src/main.rs @@ -1,10 +1,7 @@ use image::{ImageBuffer, ImageResult, Rgb}; use rand::{rngs::SmallRng, SeedableRng}; -use ray_tracing_core::{ - camera::BasicCamera, - prelude::*, - renderer::{ClassicalRenderer, DepthRenderer, PathTracer}, -}; +use ray_tracing_core::{camera::BasicCamera, prelude::*, renderer::ClassicalRenderer}; +use ray_tracing_renderer::{path_tracer::PathTracer, path_tracer_importance::PathTracerImportance}; use ray_tracing_scene::{basic_scene::BasicScene, triangle_bvh::examples::cornel}; use rayon::prelude::*; use std::path::Path; @@ -47,7 +44,7 @@ fn main() -> ImageResult<()> { Float::to_radians(90.0), ); - let r = PathTracer::new(s, c); + let r = PathTracerImportance::new(s, c); render_image(r, "test.exr", 1048) } diff --git a/ray-tracing-material/Cargo.toml b/ray-tracing-material/Cargo.toml index 0e4572f..b3bc7b8 100644 --- a/ray-tracing-material/Cargo.toml +++ b/ray-tracing-material/Cargo.toml @@ -4,5 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -rand = "0.8.5" ray-tracing-core = { path = "../ray-tracing-core" } diff --git a/ray-tracing-material/src/default.rs b/ray-tracing-material/src/default.rs index c8ae5a4..42d0f26 100644 --- a/ray-tracing-material/src/default.rs +++ b/ray-tracing-material/src/default.rs @@ -1,4 +1,3 @@ -use rand::Rng; use ray_tracing_core::prelude::*; pub struct DefaultMaterial {} diff --git a/ray-tracing-material/src/diffuse.rs b/ray-tracing-material/src/diffuse.rs index 9cdba40..1131b53 100644 --- a/ray-tracing-material/src/diffuse.rs +++ b/ray-tracing-material/src/diffuse.rs @@ -1,4 +1,3 @@ -use rand::Rng; use ray_tracing_core::prelude::*; pub struct DiffuseMaterial { diff --git a/ray-tracing-material/src/lib.rs b/ray-tracing-material/src/lib.rs index 991b6b1..fef511d 100644 --- a/ray-tracing-material/src/lib.rs +++ b/ray-tracing-material/src/lib.rs @@ -1,2 +1,4 @@ pub mod default; pub mod diffuse; + +pub mod mirror; diff --git a/ray-tracing-material/src/mirror.rs b/ray-tracing-material/src/mirror.rs new file mode 100644 index 0000000..f53df0c --- /dev/null +++ b/ray-tracing-material/src/mirror.rs @@ -0,0 +1,23 @@ +use ray_tracing_core::prelude::*; + +pub struct Mirror { + color: Color, +} + +impl Mirror { + pub fn new(color: Color) -> Self { + Self { color } + } +} + +impl Material for Mirror { + fn eval(&self, _w_in: Dir3, _w_out: Dir3, _rng: &mut R) -> Color { + Color::black() + } + + fn sample(&self, w_in: Dir3, _rng: &mut R) -> ray_tracing_core::material::SampleResult { + let w_out = (2.0 * Dir3::up() * w_in.y()) - w_in; + + ray_tracing_core::material::SampleResult::new(w_out, self.color) + } +} diff --git a/ray-tracing-renderer/Cargo.toml b/ray-tracing-renderer/Cargo.toml new file mode 100644 index 0000000..a7ceb87 --- /dev/null +++ b/ray-tracing-renderer/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "ray-tracing-renderer" +version = "0.1.0" +edition = "2021" + +[dependencies] +ray-tracing-core = { path = "../ray-tracing-core" } diff --git a/ray-tracing-renderer/src/depth_renderer.rs b/ray-tracing-renderer/src/depth_renderer.rs new file mode 100644 index 0000000..4562918 --- /dev/null +++ b/ray-tracing-renderer/src/depth_renderer.rs @@ -0,0 +1,56 @@ +use ray_tracing_core::{camera::Camera, prelude::*, renderer::ClassicalRenderer, scene::Scene}; +use std::marker::PhantomData; + +pub struct DepthRenderer +where + S: Scene, + C: Camera, + R: Rng, +{ + scene: S, + camera: C, + rng: PhantomData, +} + +impl DepthRenderer +where + S: Scene, + C: Camera, + R: Rng, +{ + pub fn new(scene: S, camera: C) -> Self { + Self { + scene, + camera, + rng: PhantomData {}, + } + } +} + +impl ClassicalRenderer for DepthRenderer +where + S: Scene, + C: Camera, + R: Rng, +{ + fn render_pixel(&self, x: u32, y: u32, rng: &mut R) -> Color { + let r = self.camera.forward(x, y, rng); + + if let Some(i) = self.scene.intersect(r, 0.0, Float::INFINITY) { + // Color::gray(1.0 / i.t()) + let c = 0.5 * (i.normal() + Dir3::new(1.0, 1.0, 1.0)); + // let c = i.normal(); + Color::new(c.x(), c.y(), c.z()) + } else { + Color::black() + } + } + + fn width(&self) -> u32 { + self.camera.width() + } + + fn height(&self) -> u32 { + self.camera.height() + } +} diff --git a/ray-tracing-renderer/src/lib.rs b/ray-tracing-renderer/src/lib.rs new file mode 100644 index 0000000..ab6adbd --- /dev/null +++ b/ray-tracing-renderer/src/lib.rs @@ -0,0 +1,3 @@ +pub mod depth_renderer; +pub mod path_tracer; +pub mod path_tracer_importance; diff --git a/ray-tracing-renderer/src/path_tracer.rs b/ray-tracing-renderer/src/path_tracer.rs new file mode 100644 index 0000000..2d89fdc --- /dev/null +++ b/ray-tracing-renderer/src/path_tracer.rs @@ -0,0 +1,76 @@ +use ray_tracing_core::{camera::Camera, prelude::*, renderer::ClassicalRenderer, scene::Scene}; +use std::marker::PhantomData; + +pub struct PathTracer +where + S: Scene, + C: Camera, + R: Rng, +{ + scene: S, + camera: C, + rng: PhantomData, +} + +impl PathTracer +where + S: Scene, + C: Camera, + R: Rng, +{ + pub fn new(scene: S, camera: C) -> Self { + Self { + scene, + camera, + rng: PhantomData {}, + } + } +} + +impl ClassicalRenderer for PathTracer +where + S: Scene, + C: Camera, + R: Rng, +{ + fn render_pixel(&self, x: u32, y: u32, rng: &mut R) -> Color { + let mut sum = Color::black(); + let mut alpha = Color::white(); + + let mut r = self.camera.forward(x, y, rng); + + let mut count = 0; + + while let Some(i) = self.scene.intersect(r, 0.001, Float::INFINITY) { + let frame = i.tangent_frame(); + let w_in = frame.to_frame(-r.dir()); + let w_out = Dir3::sample_uniform_hemisphere(rng); + + if let Some(light) = i.light() { + sum += alpha * light.emit(w_in, rng); + } + + if let Some(material) = i.material() { + alpha *= material.eval(w_in, w_out, rng) * w_out.y(); + } else { + return sum; + } + + r = Ray::new(r.at(i.t()), frame.to_world(w_out), r.time()); + count += 1; + if count > 4 { + break; + } + } + + sum + } + + fn width(&self) -> u32 { + self.camera.width() + } + + fn height(&self) -> u32 { + self.camera.height() + } +} diff --git a/ray-tracing-renderer/src/path_tracer_importance.rs b/ray-tracing-renderer/src/path_tracer_importance.rs new file mode 100644 index 0000000..6bc157e --- /dev/null +++ b/ray-tracing-renderer/src/path_tracer_importance.rs @@ -0,0 +1,77 @@ +use ray_tracing_core::{camera::Camera, prelude::*, renderer::ClassicalRenderer, scene::Scene}; +use std::marker::PhantomData; + +pub struct PathTracerImportance +where + S: Scene, + C: Camera, + R: Rng, +{ + scene: S, + camera: C, + rng: PhantomData, +} + +impl PathTracerImportance +where + S: Scene, + C: Camera, + R: Rng, +{ + pub fn new(scene: S, camera: C) -> Self { + Self { + scene, + camera, + rng: PhantomData {}, + } + } +} + +impl ClassicalRenderer for PathTracerImportance +where + S: Scene, + C: Camera, + R: Rng, +{ + fn render_pixel(&self, x: u32, y: u32, rng: &mut R) -> Color { + let mut sum = Color::black(); + let mut alpha = Color::white(); + + let mut r = self.camera.forward(x, y, rng); + + let mut count = 0; + + while let Some(i) = self.scene.intersect(r, 0.001, Float::INFINITY) { + let frame = i.tangent_frame(); + let w_in = frame.to_frame(-r.dir()); + + if let Some(light) = i.light() { + sum += alpha * light.emit(w_in, rng) * w_in.y(); + } + + let w_out = if let Some(material) = i.material() { + let sample_result = material.sample(w_in, rng); + alpha *= sample_result.color(); + sample_result.w_out() + } else { + return sum; + }; + + r = Ray::new(r.at(i.t()), frame.to_world(w_out), r.time()); + count += 1; + if count > 4 { + break; + } + } + + sum + } + + fn width(&self) -> u32 { + self.camera.width() + } + + fn height(&self) -> u32 { + self.camera.height() + } +} diff --git a/ray-tracing-scene/Cargo.toml b/ray-tracing-scene/Cargo.toml index ce8f09c..83e5b19 100644 --- a/ray-tracing-scene/Cargo.toml +++ b/ray-tracing-scene/Cargo.toml @@ -4,6 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -rand = "0.8.5" ray-tracing-core = { path = "../ray-tracing-core" } ray-tracing-material = { path = "../ray-tracing-material" } diff --git a/ray-tracing-scene/src/basic_scene.rs b/ray-tracing-scene/src/basic_scene.rs index 0e68498..899c7d4 100644 --- a/ray-tracing-scene/src/basic_scene.rs +++ b/ray-tracing-scene/src/basic_scene.rs @@ -1,4 +1,3 @@ -use rand::Rng; use ray_tracing_core::prelude::*; use ray_tracing_core::scene::{Intersection, Scene}; use ray_tracing_material::default::DefaultMaterial; @@ -15,6 +14,12 @@ impl BasicScene { } } +impl Default for BasicScene { + fn default() -> Self { + Self::new() + } +} + impl Scene for BasicScene { fn intersect(&self, ray: Ray, min: Float, max: Float) -> Option> { let mut intersection: Option> = None; diff --git a/ray-tracing-scene/src/triangle_bvh.rs b/ray-tracing-scene/src/triangle_bvh.rs index b6a3409..25034f7 100644 --- a/ray-tracing-scene/src/triangle_bvh.rs +++ b/ray-tracing-scene/src/triangle_bvh.rs @@ -1,4 +1,3 @@ -use rand::Rng; use ray_tracing_core::{ prelude::*, scene::{Intersection, Scene}, @@ -197,14 +196,14 @@ impl Scene for TriangleBVH { pub mod examples { use super::{BVHMaterial, Triangle, TriangleBVH}; - use rand::Rng; use ray_tracing_core::{light::AreaLight, prelude::*}; - use ray_tracing_material::diffuse::DiffuseMaterial; + use ray_tracing_material::{diffuse::DiffuseMaterial, mirror::Mirror}; pub fn cornel() -> TriangleBVH { let side_length = 1.5; let light_size = 0.5; let light_offset = 0.01; + let box_size = 0.3; TriangleBVH::new( vec![ Pos3::new(side_length, side_length, side_length), @@ -219,6 +218,11 @@ pub mod examples { Pos3::new(light_size, side_length - light_offset, -light_size), Pos3::new(-light_size, side_length - light_offset, light_size), Pos3::new(-light_size, side_length - light_offset, -light_size), + Pos3::new(box_size, -side_length, 0.0), + Pos3::new(0.0, -side_length, -box_size), + Pos3::new(-box_size, -side_length, 0.0), + Pos3::new(0.0, -side_length, box_size), + Pos3::new(0.0, box_size - side_length, 0.0), ], vec![ Triangle::new([0, 1, 2], 0), @@ -233,6 +237,10 @@ pub mod examples { Triangle::new([7, 3, 5], 2), Triangle::new([8, 10, 9], 3), Triangle::new([11, 9, 10], 3), + Triangle::new([12, 13, 16], 4), + Triangle::new([13, 14, 16], 4), + Triangle::new([14, 15, 16], 4), + Triangle::new([15, 12, 16], 4), ], vec![ BVHMaterial { @@ -251,6 +259,10 @@ pub mod examples { material: None, light: Some(Box::new(AreaLight::new(Color::white() * 30.0))), }, + BVHMaterial { + material: Some(Box::new(Mirror::new(Color::new(1.0, 1.0, 1.0)))), + light: None, + }, ], ) }