git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

src/environment.rs (5.3K)

  1 //! The scene's light: the Environment node (since 2026-10-02).
  2 //!
  3 //! One sun direction lights BOTH views. The raster pass's flat shading and
  4 //! the smooth bake light by it (`VkRenderer::set_scene_light`,
  5 //! `geometry::shade_factor`), and the path tracer's sky puts its sun there
  6 //! (`RtEnvironment`). Until this the two had a light each, hard-coded and
  7 //! pointing different ways — the raster one, read the right way round,
  8 //! from BELOW — so switching modes moved the lit side of a model.
  9 //!
 10 //! The `environment` node stands at the root, the object level
 11 //! (`context::placement`). The first one there that is not bypassed is the
 12 //! scene's environment; with none, [`Environment::default`] is, which is
 13 //! the template's defaults — so adding the node changes nothing until a
 14 //! row is moved, and bypassing it is how to compare. Its rows evaluate at
 15 //! the current frame, so a sun can move with `$F`.
 16 //!
 17 //! What each view takes from it: the raster pass takes the DIRECTION only —
 18 //! its shading is a fixed 0.55..1 wrap of the surface colour, not a light
 19 //! with a strength. The tracer takes all of it: the sun's colour times its
 20 //! intensity, and the sky's two colours times theirs. Colours are LINEAR,
 21 //! as the tracer reads them, and may be pushed past 1 by an intensity.
 22 
 23 use glam::Vec3;
 24 
 25 use crate::app::FsNode;
 26 use crate::geometry::{node_param_f32, node_param_vec3, resolve_param_refs};
 27 
 28 /// The node type.
 29 pub const ENVIRONMENT: &str = "environment";
 30 
 31 /// The template's defaults — `nodes/environment.json` must say the same,
 32 /// which `the_environment_node_lights_both_views` checks. The sun is the
 33 /// tracer's old one, (0.45, 0.75, 0.35), to the nearest degree.
 34 pub const DEFAULT_SUN_AZIMUTH: f32 = 52.0;
 35 pub const DEFAULT_SUN_ELEVATION: f32 = 53.0;
 36 pub const DEFAULT_SUN_INTENSITY: f32 = 8.0;
 37 pub const DEFAULT_SUN_COLOR: [f32; 3] = [1.0, 0.95, 0.85];
 38 pub const DEFAULT_SKY_COLOR: [f32; 3] = [0.72, 0.82, 0.98];
 39 pub const DEFAULT_GROUND_COLOR: [f32; 3] = [0.32, 0.31, 0.35];
 40 pub const DEFAULT_SKY_INTENSITY: f32 = 1.0;
 41 
 42 #[derive(Clone, Copy, Debug, PartialEq)]
 43 pub struct Environment {
 44     /// Toward the sun, unit length, world space.
 45     pub sun_direction: Vec3,
 46     /// The sun's colour times its intensity.
 47     pub sun_radiance: [f32; 3],
 48     /// The sky straight up and straight down, times the sky's intensity.
 49     pub sky_zenith: [f32; 3],
 50     pub sky_nadir: [f32; 3],
 51 }
 52 
 53 impl Default for Environment {
 54     fn default() -> Self {
 55         Self::from_rows(
 56             DEFAULT_SUN_AZIMUTH,
 57             DEFAULT_SUN_ELEVATION,
 58             DEFAULT_SUN_COLOR,
 59             DEFAULT_SUN_INTENSITY,
 60             DEFAULT_SKY_COLOR,
 61             DEFAULT_GROUND_COLOR,
 62             DEFAULT_SKY_INTENSITY,
 63         )
 64     }
 65 }
 66 
 67 /// The direction an azimuth and an elevation (degrees) name: azimuth turns
 68 /// from +Z toward +X about the up axis, elevation lifts from the horizon
 69 /// toward +Y.
 70 pub fn sun_direction(azimuth: f32, elevation: f32) -> Vec3 {
 71     let (az, el) = (azimuth.to_radians(), elevation.to_radians());
 72     Vec3::new(az.sin() * el.cos(), el.sin(), az.cos() * el.cos()).normalize()
 73 }
 74 
 75 impl Environment {
 76     fn from_rows(
 77         azimuth: f32,
 78         elevation: f32,
 79         sun_color: [f32; 3],
 80         sun_intensity: f32,
 81         sky: [f32; 3],
 82         ground: [f32; 3],
 83         sky_intensity: f32,
 84     ) -> Self {
 85         let scale = |c: [f32; 3], k: f32| c.map(|v| (v * k).max(0.0));
 86         Self {
 87             sun_direction: sun_direction(azimuth, elevation),
 88             sun_radiance: scale(sun_color, sun_intensity),
 89             sky_zenith: scale(sky, sky_intensity),
 90             sky_nadir: scale(ground, sky_intensity),
 91         }
 92     }
 93 
 94     /// An environment node's rows, read as they stand (no expressions).
 95     pub fn of_node(node: &FsNode) -> Self {
 96         let color = |name: &str, fallback: [f32; 3]| node_param_vec3(node, name, Vec3::from_array(fallback)).to_array();
 97         Self::from_rows(
 98             node_param_f32(node, "sun_azimuth", DEFAULT_SUN_AZIMUTH),
 99             node_param_f32(node, "sun_elevation", DEFAULT_SUN_ELEVATION),
100             color("sun_color", DEFAULT_SUN_COLOR),
101             node_param_f32(node, "sun_intensity", DEFAULT_SUN_INTENSITY),
102             color("sky_color", DEFAULT_SKY_COLOR),
103             color("ground_color", DEFAULT_GROUND_COLOR),
104             node_param_f32(node, "sky_intensity", DEFAULT_SKY_INTENSITY),
105         )
106     }
107 
108     /// The scene's environment at `frame`: the root's first environment
109     /// node that is not bypassed, its expressions evaluated; the default
110     /// when there is none.
111     pub fn of_scene(root: &FsNode, frame: i32) -> Self {
112         let Some(node) = root
113             .children
114             .iter()
115             .find(|c| c.node_type.eq_ignore_ascii_case(ENVIRONMENT) && !c.bypassed)
116         else {
117             return Self::default();
118         };
119         let mut error = None;
120         match resolve_param_refs(root, node, frame, &mut error) {
121             Some(resolved) => Self::of_node(&resolved),
122             None => Self::of_node(node),
123         }
124     }
125 
126     /// What the path tracer is handed.
127     pub fn to_rt(&self) -> cce_ui::vk::RtEnvironment {
128         cce_ui::vk::RtEnvironment {
129             sun_direction: self.sun_direction.to_array(),
130             sun_color: self.sun_radiance,
131             sky_zenith: self.sky_zenith,
132             sky_nadir: self.sky_nadir,
133         }
134     }
135 }