Skip to main content

kvas_engine/
fx.rs

1//! Per-scene shadow and post-processing settings. Plain data: the renderer reads them every
2//! frame, scripts change them through [`crate::script::Ctx`] (`ctx.post()`, `ctx.shadows()`).
3//!
4//! ```
5//! use kvas_engine::fx::PostSettings;
6//! let mut post = PostSettings::default();
7//! post.bloom = 0.6;
8//! assert!(post.any());
9//! ```
10
11use serde::{Deserialize, Serialize};
12
13crate::choice_enum!(
14    /// How a reduced render scale is brought back to the window size.
15    Upscaler { Bilinear, Fsr, Dlss } default Fsr
16);
17
18/// Sun shadow quality and look.
19#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
20#[serde(default)]
21pub struct ShadowSettings {
22    /// Master switch for sun shadows.
23    pub enabled: bool,
24    /// Edge softness: 0.5 crisp, 1 normal, 3+ very soft (more filter taps are used automatically).
25    pub softness: f32,
26    /// How dark shadows get, 0 = invisible .. 1 = fully dark.
27    pub strength: f32,
28    /// Depth bias; raise it if surfaces show striping ("shadow acne").
29    pub bias: f32,
30    /// Half-width in metres of the area around the camera that receives shadows.
31    pub range: f32,
32}
33
34impl Default for ShadowSettings {
35    fn default() -> Self {
36        Self { enabled: true, softness: 1.0, strength: 1.0, bias: 0.0004, range: 40.0 }
37    }
38}
39
40/// A custom full-screen shader applied to the camera image (see `shaders/fx` in a project).
41///
42/// The WGSL file defines `fn effect(uv: vec2<f32>, color: vec3<f32>, params: vec4<f32>, time: f32) -> vec3<f32>`
43/// and may call `scene_color(uv)` to sample the picture elsewhere. `params` carries the four numbers below.
44#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
45#[serde(default)]
46pub struct PostEffect {
47    /// Asset path of the WGSL file.
48    pub shader: String,
49    /// Switch an effect off without removing it.
50    pub enabled: bool,
51    /// Free numbers handed to the shader as `params`.
52    pub params: [f32; 4],
53}
54
55impl Default for PostEffect {
56    fn default() -> Self {
57        Self { shader: String::new(), enabled: true, params: [1.0, 0.0, 0.0, 0.0] }
58    }
59}
60
61/// Screen-space effects applied after the scene is drawn. 0 turns an effect off.
62#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
63#[serde(default)]
64pub struct PostSettings {
65    /// Glow around bright areas.
66    pub bloom: f32,
67    /// Brightness above which bloom starts (0..2).
68    pub bloom_threshold: f32,
69    /// Size of the bloom halo, 0.5..4.
70    pub bloom_radius: f32,
71    /// Lens flare: ghost images and streaks from very bright lights and the sun.
72    pub lens_flare: f32,
73    /// Full-screen gaussian-style blur strength (menus, underwater, damage).
74    pub blur: f32,
75    /// Blur along camera motion; follows how fast the camera turns and moves.
76    pub motion_blur: f32,
77    /// Darkening towards the screen corners.
78    pub vignette: f32,
79    /// Colour fringing towards the corners (cheap lens look).
80    pub chromatic_aberration: f32,
81    /// Animated film grain.
82    pub film_grain: f32,
83    /// Sharpening of fine detail.
84    pub sharpen: f32,
85    /// Edge smoothing (FXAA) on top of MSAA: removes stair steps on thin geometry, cut-out leaves
86    /// and sharp procedural patterns. 0 = off, 1 = full.
87    pub antialias: f32,
88    /// Tint multiplied over the final picture (linear RGB), white = none.
89    pub tint: [f32; 3],
90    /// Custom shader effects, applied in order after the built-in ones.
91    pub effects: Vec<PostEffect>,
92}
93
94impl Default for PostSettings {
95    fn default() -> Self {
96        Self {
97            bloom: 0.0,
98            bloom_threshold: 0.8,
99            bloom_radius: 1.5,
100            lens_flare: 0.0,
101            blur: 0.0,
102            motion_blur: 0.0,
103            vignette: 0.0,
104            chromatic_aberration: 0.0,
105            film_grain: 0.0,
106            sharpen: 0.0,
107            antialias: 1.0,
108            tint: [1.0; 3],
109            effects: Vec::new(),
110        }
111    }
112}
113
114impl PostSettings {
115    /// Whether any custom shader effect is switched on.
116    pub fn any_custom(&self) -> bool {
117        self.effects.iter().any(|e| e.enabled && !e.shader.is_empty())
118    }
119
120    /// Whether any effect needs the extra render pass.
121    pub fn any(&self) -> bool {
122        self.any_builtin() || self.any_custom()
123    }
124
125    /// Whether any built-in effect (bloom, blur, tint...) is on.
126    pub fn any_builtin(&self) -> bool {
127        self.bloom > 0.0
128            || self.lens_flare > 0.0
129            || self.blur > 0.0
130            || self.motion_blur > 0.0
131            || self.vignette > 0.0
132            || self.chromatic_aberration > 0.0
133            || self.film_grain > 0.0
134            || self.sharpen > 0.0
135            || self.antialias > 0.0
136            || self.tint != [1.0; 3]
137    }
138
139    /// Ready-made looks for the editor's preset buttons and for scripts.
140    pub fn preset(name: &str) -> Self {
141        match name {
142            "cinematic" => Self { bloom: 0.45, bloom_threshold: 0.75, lens_flare: 0.35, vignette: 0.35, chromatic_aberration: 0.25, film_grain: 0.12, motion_blur: 0.4, ..Default::default() },
143            "dreamy" => Self { bloom: 1.0, bloom_threshold: 0.55, bloom_radius: 2.6, blur: 0.12, vignette: 0.2, ..Default::default() },
144            "crisp" => Self { sharpen: 0.6, bloom: 0.15, vignette: 0.1, ..Default::default() },
145            "retro" => Self { chromatic_aberration: 0.6, film_grain: 0.3, vignette: 0.5, tint: [1.0, 0.95, 0.82], ..Default::default() },
146            _ => Self::default(),
147        }
148    }
149}