feat: Add bloom shader variant and enhance shader architecture in README

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-04-21 18:02:10 +02:00
parent b8917272f7
commit 6eb28ffcac
5 changed files with 494 additions and 154 deletions
+136 -11
View File
@@ -86,22 +86,147 @@ For full host wiring examples, see:
- `lib/renderer/` — renderer host widgets.
- `lib/managers/` — runtime/session/display/persistence managers.
- `lib/audio/` — platform-aware audio backends.
- `shaders/wolf_world.frag` — fragment shader included in package configuration.
## Development Commands
From this directory:
```bash
flutter analyze
flutter test
```
- `shaders/wolf_world.frag` base fragment shader included in package configuration.
- `shaders/wolf_world_bloom.frag` — bloom-enabled fragment shader variant.
## Integration Notes
- Keep UI/platform concerns in this package or app hosts, not in `wolf_3d_dart`.
- Use exported APIs from `lib/wolf_3d_flutter.dart` rather than importing private internals from `lib/src` in dependencies.
- Shader path is declared in this package `pubspec.yaml` and must stay synchronized with renderer usage.
- Shader paths are declared in this package `pubspec.yaml` and must stay synchronized with renderer usage.
## Shader Architecture And Performance Notes
This package ships two shader variants:
- `shaders/wolf_world.frag` (base pass, no bloom taps)
- `shaders/wolf_world_bloom.frag` (bloom-enabled variant)
The renderer selects one variant in Dart based on runtime settings. This is a
performance decision: when bloom is disabled, we do not run bloom sampling code
at all.
### No-Branch Shader Policy
For package-owned shader sources, do not use `if` statements. Use branchless
selection patterns (`mix`, `step`, `smoothstep`, mask algebra) instead.
Static check:
```bash
rg "\bif\s*\(" packages/wolf_3d_flutter/shaders
```
Expected result: no matches in source shader files.
### Why This Is Different From Dart
If you are primarily a Dart developer, this is the key mindset shift:
- Dart code runs on CPU cores with branch prediction and comparatively cheap
control flow.
- Fragment shaders run across many pixels in parallel on GPU SIMD/SIMT lanes.
- If neighboring pixels take different branches, the GPU can serialize branch
paths (divergence), reducing throughput.
- Texture reads are usually more expensive than scalar ALU math. Removing bloom
work entirely when disabled is often better than trying to gate it inside one
shader.
In short: in Dart, `if` can be good structure. In fragment shaders, branchless
math and pass selection are often better for frame time.
### Dart-Style Thinking vs Shader-Style Thinking
CPU/Dart style:
```dart
if (effectsEnabled) {
uv = warp(uv);
}
if (outsideScreen(uv)) {
return bezelColor;
}
```
Shader style used here:
```glsl
vec2 effectiveUv = mix(uv, warpedUv, effectsMask);
float bezelMask = (1.0 - insideMask) * effectsMask;
vec3 outColor = mix(screenColor, bezelColor, bezelMask);
```
Both produce feature-equivalent behavior, but the second keeps execution paths
uniform across fragments.
### Shader Block Guide (What / Why)
1. UV normalization:
Convert fragment coordinates to 0..1 UV so all sampling math is resolution
agnostic.
2. Barrel warp:
Simulates curved CRT glass by pushing UVs outward as radius increases.
3. Inside/outside mask:
Computes whether warped UV remains on the emissive screen rectangle. This
replaces branch-based bezel routing.
4. Edge-aware AA:
Computes local luma span from N/S/E/W neighbors and blends toward neighborhood
average only where contrast indicates potential aliasing.
5. CRT modulation:
Applies scanlines, moving sweep, center lift, and vignette to mimic phosphor
and lens behavior.
6. Bezel shading:
Uses overflow distance and edge bleed sampling to build depth, inner lip, and
scene-tinted glow on bezel regions.
7. Bloom variant only:
Adds three-ring cross taps, brightness gating, and tone mapping. This code is
in a separate shader so bloom-off mode avoids paying this cost.
### Constant Tuning Reference
- Warp factor: `0.045`
Higher = stronger curvature.
- AA blend ceiling: `0.45`
Higher = softer edges, more blur risk.
- Scanline band: `0.88 + 0.12 * sin(...)`
Lower floor or higher amplitude increases CRT stripe intensity.
- Sweep speed: `uTime * 0.08`
Higher = faster sweep line travel.
- Bloom ring radii: `3`, `7`, `13` texels
Larger radii spread glow farther but increase halo size.
- Bloom gain: `0.42`
Higher = brighter bloom before tone map.
- Tone map: `color / (color + 0.75) * 1.75`
Controls highlight rolloff and midtone lift.
### Glossary
- UV: normalized texture coordinates in [0, 1].
- Luma: perceived brightness estimate from RGB.
- Mask: scalar 0..1 value used to blend between alternatives.
- Vignette: edge darkening effect.
- Tone map: compresses highlights into displayable range.
- Tap: one texture sample read.
- Divergence: parallel shader lanes taking different branches.
### Profiling Expectations
- Bloom disabled: base shader variant runs, no bloom taps.
- Bloom enabled: bloom shader variant runs, additional texture sampling cost.
- Effects disabled: both shaders still remain branchless; effect contribution is
blended out by mask values.
## Troubleshooting