Skip to content

Improved 2D rendering effects#395

Merged
sebcrozet merged 15 commits into
masterfrom
render-2d
Jul 4, 2026
Merged

Improved 2D rendering effects#395
sebcrozet merged 15 commits into
masterfrom
render-2d

Conversation

@sebcrozet

@sebcrozet sebcrozet commented Jul 4, 2026

Copy link
Copy Markdown
Member

We have made a couple of PRs for improving 3D rendering effects. This PR focuses on 2D (see changelog for details).

This also removes the default behavior that closes the window when pressing the Escape button (it can be re-enabled with Window::rebind_close_key(Some(Key::Escape)) if needed).

  • Per-object blend modes: additive, multiply, screen, premultiplied/straight alpha
  • Dynamic 2D lighting: point/spot lights with normal-mapped sprites (diffuse + Blinn-Phong specular)
  • Screen-space 2D global illumination: soft shadows + colored light bleed, with optional radiance cascades
  • CRT post-processing: screen curvature, chromatic aberration, scanlines, vignette
  • Bloom + HDR tonemapping now applied to 2D too
image image image image

sebcrozet and others added 15 commits June 25, 2026 10:56
Add `Blend2d` (Alpha, PremultipliedAlpha, Additive, Multiply, Screen,
Opaque) selectable per object via `Object2d::set_blend` /
`SceneNode2d::set_blend[_recursive]`. `ObjectMaterial2d` now builds one
surface pipeline per blend mode, each lazily specialized by MSAA sample
count, and selects it from `data.blend()` at draw time. Wireframe/point
overlays keep straight alpha blending.

Adds the `blend_modes2d` example plus its run configs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `SceneNode2d::sprite`/`add_sprite` (textured quad), `SpriteSheet`
(regular atlas grid) with `set_sprite_frame` for flip-book animation,
`set_uv_rect` to map a sprite to a texture sub-rectangle (recomputed from
each vertex's normalized position so it is repeatable), and
`nine_slice`/`add_nine_slice` building a 16-vertex / 9-quad mesh with a
`Border` that keeps corners fixed while edges and center stretch.

Adds the `sprites2d` example plus its run configs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 2D scene renders into the HDR film, so HDR tonemapping and bloom
(`Window::set_bloom_enabled`) already affect 2D content — over-bright
(>1.0) colors bleed light. Add `Window::render_2d_with` to run a
post-processing effect over a 2D frame, and a new `Crt` stylization
effect (barrel curvature, chromatic aberration, scanlines, vignette)
with per-term toggles.

The CRT shader is GPU-validated in the shader-validity test.

Adds the `post_processing2d` example plus its run configs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `LitMaterial2d`, shading 2D objects with the dynamic 2D lights from
a new thread-local `Light2dManager` (ambient + up to 16 point/spot
`Light2d`s, stored in a uniform array so it stays within WebGPU's bind
group/storage limits). Lights sit above the plane, so a normal-mapped
sprite shades per-pixel (diffuse + Blinn-Phong specular); without a
normal map the surface is flat and still picks up each light's radial
falloff and spot cone.

New `ObjectData2d` fields `normal_map` and `lit_params` (+ accessors,
`Object2d`/`SceneNode2d` setters). Helpers: `SceneNode2d::lit_sprite`
/`add_lit_sprite`, `set_lit_params`, `set_normal_map_from_file`.

The lit shader (point + spot lights) is GPU-validated in the
shader-validity test. Adds the `lighting2d` example + run configs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `Tilemap`: a grid of tiles drawn from a single `SpriteSheet` atlas,
baked into one `SceneNode2d` mesh (one draw call, sharing the standard
2D material and blend modes). Tiles index atlas frames; `set_tile` /
`fill` rebuild the dynamic mesh, and `EMPTY` cells are skipped.

GPU-validated in the shader-validity test (including a dynamic rebuild).
Adds the `tilemap2d` example + run configs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `SkinnedMesh2d`: vertices bind to up to four bones of a `Bone2d`
skeleton with weights, and the vertex shader blends per-bone joint
matrices (bone world × inverse bind, as 2D affine 3x3) on the GPU —
Spine/DragonBones-style deformation. `update()` walks the hierarchy and
uploads the joint matrices; `set_bone_local`/`set_transform` pose it.
Exposes a `SceneNode2d` rendered by a dedicated skinning material.

GPU-validated in the shader-validity test (built + posed + rendered).
Adds the `skinning2d` example + run configs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply WESL @if conditional compilation to object2d.wgsl: a `textured`
feature gates the texture binding and sample. `ObjectMaterial2d` now
compiles specialized shader variants and pipelines lazily, cached by
(blend mode, features, sample count), all sharing one pipeline layout
(stripped bindings simply go unused).

Specialization is automatic: an object still using the global default
white texture gets the untextured variant (no texture sample, higher
occupancy), while textured objects get the textured one — no API change.

Both variants are GPU-validated in the shader-validity test (a textured
object added alongside the solid ones).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `Gi2d`, a post-processing effect that ray-marches incoming light per
pixel against analytic emitter/occluder discs and modulates the rendered
scene by the resulting irradiance — soft shadows and colored light bleed
with no light/shadow bookkeeping. Applied via `render_2d_with`; set the
camera and the emitters/occluders each frame.

This is the direct, brute-force form of the technique that radiance
cascades accelerate (cone-traced cascade probes can layer on top later);
implemented straight here for correctness and clarity. The march uses
analytic sphere tracing, so no precomputed SDF/JFA texture is needed for
the disc primitives.

GPU-validated in the shader-validity test. Adds the
`global_illumination2d` example + run configs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generalize post-processing from a single effect to an ordered chain, the
three.js EffectComposer model: the tonemapped scene feeds the first
effect, each effect's output feeds the next, and the last writes the
frame. Implemented by ping-ponging between two LDR targets
(`post_process_render_target` + a new `_b`); each effect reads one and
writes the other.

`Window::render_chain` / `render_2d_with_chain` / `render_3d_with_chain`
(and `OffscreenSurface::render_chain`) take `&mut [&mut dyn
PostProcessingEffect]`. The single-effect `render` / `render_2d_with`
are now thin wrappers (back-compatible), and an empty chain matches the
no-effect path.

GPU-validated: the shader-validity test now also renders a 2-effect
chain. Adds the `effect_chain2d` example (GI → CRT) + run configs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sebcrozet sebcrozet merged commit 4a55053 into master Jul 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant