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 }