Skip to main content

nightshade/ecs/camera/components/
viewport_shading.rs

1//! Per-camera viewport shading mode and overlay toggle, plus the
2//! per-frame effective shading the renderer derives from it.
3//!
4//! [`ViewportShading`] is a component the editor (or any caller) attaches
5//! to a camera entity to choose the shading mode and overlay visibility
6//! for that camera's tile. The renderer reads it together with the global
7//! [`RenderSettings`](crate::render::config::RenderSettings) defaults to
8//! produce an [`EffectiveShading`] for the camera, then writes it into
9//! `world.resources.renderer_state.active_view` while the camera's render
10//! graph executes. Passes read from `renderer_state.active_view`, never from
11//! the user-set defaults on `RenderSettings` directly, so each camera renders
12//! with its own view-local shading without the renderer having to
13//! mutate or restore unrelated global state between cameras.
14//!
15//! [`ShadingMode`] selects the post-processing stack:
16//!
17//! - `Rendered`: full pipeline (bloom, SSAO, SSGI, SSR, lit shading).
18//! - `Solid`: unlit base color with all post-processing disabled.
19//! - `Flat`: lit shading with every material's surface color, metallic,
20//!   roughness, and emissive replaced with CAD-style neutral values.
21//! - `Wireframe`: solid base + GPU-driven edge overlay through the
22//!   existing lines pass.
23//!
24//! `show_overlays` gates editor-visible decorations (gizmos, debug lines,
25//! the selection outline) for that camera only. The world ground grid is
26//! a separate global toggle.
27
28use crate::ecs::primitives::CameraCullingMask;
29use crate::render::config::{
30    Atmosphere, ColorGrading, DebugDraw, DepthOfField, Fog, RenderSettings,
31};
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
34pub enum ShadingMode {
35    Wireframe,
36    Solid,
37    Flat,
38    #[default]
39    Rendered,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
43pub struct ViewportShading {
44    pub mode: ShadingMode,
45    pub show_overlays: bool,
46}
47
48impl Default for ViewportShading {
49    fn default() -> Self {
50        Self {
51            mode: ShadingMode::Rendered,
52            show_overlays: true,
53        }
54    }
55}
56
57use crate::render::config::{EffectiveShading, FLAT_SHADING_COLOR, WIREFRAME_SENTINEL_COLOR};
58
59pub fn effective_shading_from_graphics(
60    render: &RenderSettings,
61    debug: &DebugDraw,
62) -> EffectiveShading {
63    EffectiveShading {
64        unlit_mode: render.unlit_mode,
65        bloom_enabled: render.bloom_enabled,
66        ssao_enabled: render.ssao_enabled,
67        ssgi_enabled: render.ssgi_enabled,
68        ssr_enabled: render.ssr_enabled,
69        show_normals: debug.show_normals,
70        show_bounding_volumes: debug.show_bounding_volumes,
71        show_wireframe: false,
72        selection_outline_enabled: debug.selection_outline_enabled,
73        flat_shading_color: None,
74        shadow_depth_enabled: true,
75        lines_enabled: debug.show_normals || debug.show_bounding_volumes,
76        color_grading: render.color_grading,
77        depth_of_field: render.depth_of_field,
78        bloom_intensity: render.bloom_intensity,
79        bloom_threshold: render.bloom_threshold,
80        bloom_knee: render.bloom_knee,
81        bloom_filter_radius: render.bloom_filter_radius,
82        ssao_radius: render.ssao_radius,
83        ssao_bias: render.ssao_bias,
84        ssao_intensity: render.ssao_intensity,
85        ssao_sample_count: render.ssao_sample_count,
86        ssao_visualization: debug.ssao_visualization,
87        ssao_blur_depth_threshold: render.ssao_blur_depth_threshold,
88        ssao_blur_normal_power: render.ssao_blur_normal_power,
89        ssgi_radius: render.ssgi_radius,
90        ssgi_intensity: render.ssgi_intensity,
91        ssgi_max_steps: render.ssgi_max_steps,
92        ssr_max_steps: render.ssr_max_steps,
93        ssr_thickness: render.ssr_thickness,
94        ssr_max_distance: render.ssr_max_distance,
95        ssr_stride: render.ssr_stride,
96        ssr_fade_start: render.ssr_fade_start,
97        ssr_fade_end: render.ssr_fade_end,
98        ssr_intensity: render.ssr_intensity,
99        ambient_light: render.ambient_light,
100        culling_mask: !0,
101        fog: render.fog,
102        atmosphere: render.atmosphere,
103    }
104}
105
106pub fn effective_shading_for_camera(
107    render: &RenderSettings,
108    debug: &DebugDraw,
109    shading: &ViewportShading,
110    post_process: Option<&CameraPostProcess>,
111    culling_mask: Option<&CameraCullingMask>,
112    environment: Option<&CameraEnvironment>,
113) -> EffectiveShading {
114    let mut effective = effective_shading_from_graphics(render, debug);
115    if let Some(mask) = culling_mask {
116        effective.culling_mask = mask.0;
117    }
118    if let Some(env) = environment {
119        if let Some(atmosphere) = env.atmosphere {
120            effective.atmosphere = atmosphere;
121        }
122        effective.fog = match env.fog {
123            FogOverride::Inherit => effective.fog,
124            FogOverride::Disabled => None,
125            FogOverride::Override(fog) => Some(fog),
126        };
127    }
128    if let Some(overrides) = post_process {
129        if let Some(cg) = overrides.color_grading {
130            effective.color_grading = cg;
131        }
132        if let Some(dof) = overrides.depth_of_field {
133            effective.depth_of_field = dof;
134        }
135        if let Some(value) = overrides.bloom_intensity {
136            effective.bloom_intensity = value;
137        }
138        if let Some(value) = overrides.bloom_threshold {
139            effective.bloom_threshold = value;
140        }
141        if let Some(value) = overrides.bloom_knee {
142            effective.bloom_knee = value;
143        }
144        if let Some(value) = overrides.bloom_filter_radius {
145            effective.bloom_filter_radius = value;
146        }
147        if let Some(value) = overrides.ssao_intensity {
148            effective.ssao_intensity = value;
149        }
150        if let Some(value) = overrides.ssao_radius {
151            effective.ssao_radius = value;
152        }
153        if let Some(value) = overrides.ssgi_intensity {
154            effective.ssgi_intensity = value;
155        }
156        if let Some(value) = overrides.ssr_intensity {
157            effective.ssr_intensity = value;
158        }
159        if let Some(value) = overrides.ambient_light {
160            effective.ambient_light = value;
161        }
162    }
163    match shading.mode {
164        ShadingMode::Rendered => {}
165        ShadingMode::Solid => {
166            effective.unlit_mode = true;
167            effective.bloom_enabled = false;
168            effective.ssao_enabled = false;
169            effective.ssgi_enabled = false;
170            effective.ssr_enabled = false;
171            effective.shadow_depth_enabled = false;
172        }
173        ShadingMode::Flat => {
174            effective.bloom_enabled = false;
175            effective.ssao_enabled = false;
176            effective.ssgi_enabled = false;
177            effective.ssr_enabled = false;
178            effective.flat_shading_color = Some(FLAT_SHADING_COLOR);
179        }
180        ShadingMode::Wireframe => {
181            effective.unlit_mode = true;
182            effective.bloom_enabled = false;
183            effective.ssao_enabled = false;
184            effective.ssgi_enabled = false;
185            effective.ssr_enabled = false;
186            effective.shadow_depth_enabled = false;
187            effective.flat_shading_color = Some(WIREFRAME_SENTINEL_COLOR);
188            effective.show_wireframe = true;
189        }
190    }
191    if !shading.show_overlays {
192        effective.show_normals = false;
193        effective.show_bounding_volumes = false;
194        effective.selection_outline_enabled = false;
195    }
196    effective.lines_enabled =
197        effective.show_normals || effective.show_bounding_volumes || effective.show_wireframe;
198    effective
199}
200
201/// Locks a camera's projection aspect to a fixed value regardless
202/// of the tile size it renders into. The renderer fits a
203/// constrained-aspect inner rect inside the tile and letterboxes /
204/// pillarboxes the remaining bars in the tile background. Without
205/// this component the camera's projection aspect matches the tile
206/// rect (the default behavior). With it, the aspect stays
207/// constant and the rendered region is centered inside the tile.
208#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
209pub struct ConstrainedAspect(pub f32);
210
211impl Default for ConstrainedAspect {
212    fn default() -> Self {
213        Self(16.0 / 9.0)
214    }
215}
216
217/// Per-camera environment overrides for atmosphere and fog. Each
218/// field opts into changing the resolved view state. `Inherit`
219/// for fog or `None` for atmosphere keeps the corresponding
220/// global `Graphics` value.
221#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize)]
222pub struct CameraEnvironment {
223    pub atmosphere: Option<Atmosphere>,
224    pub fog: FogOverride,
225}
226
227#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize)]
228pub enum FogOverride {
229    #[default]
230    Inherit,
231    Disabled,
232    Override(Fog),
233}
234
235/// Per-camera post-process parameter overrides. Anything left as
236/// `None` (or `Default`) inherits the corresponding global value
237/// from `Graphics`. The renderer resolves overrides into
238/// `graphics.active_view` per camera each frame so passes read a
239/// single set of resolved values.
240#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize)]
241pub struct CameraPostProcess {
242    pub color_grading: Option<ColorGrading>,
243    pub depth_of_field: Option<DepthOfField>,
244    pub bloom_intensity: Option<f32>,
245    pub bloom_threshold: Option<f32>,
246    pub bloom_knee: Option<f32>,
247    pub bloom_filter_radius: Option<f32>,
248    pub ssao_intensity: Option<f32>,
249    pub ssao_radius: Option<f32>,
250    pub ssgi_intensity: Option<f32>,
251    pub ssr_intensity: Option<f32>,
252    pub ambient_light: Option<[f32; 4]>,
253}