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}