Skip to content

V3: 3D Model layer (GLB, HDR studios, PBR, shadows, exact depth, Blender-style transforms) - #189

Merged
git-chad merged 118 commits into
git-chad/shader-lab-v3-planfrom
git-chad/v3-model-layer
Oct 1, 2026
Merged

git-chad merged 118 commits into
git-chad/shader-lab-v3-planfrom
git-chad/v3-model-layer

Conversation

@git-chad

@git-chad git-chad commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #188. Part of the V3 stack (parent #150); nothing merges to main until the plan is complete.

What

Step 1 of roadmap 4.4: a 3D Model source layer.

  • Import: .glb, or .gltf with embedded buffers, from Add layer → 3D Model or dropped on the canvas. Draco, meshopt and KTX2 decode from public/three. .obj isn't accepted.
  • Framing: the model is fitted to the frame automatically. Focal length changes the perspective, not the size.
  • Materials: the file's own PBR materials, or Chrome, Brushed Metal, Glass, Clay, Rubber or Iridescent. Each preset sets its own color and roughness, both editable.
  • Light:
    • Five CC0 Poly Haven studios (Studio, Softbox, Warehouse, Sunset, Neon) or your own .hdr, all normalized to the same brightness.
    • A frame-relative key light with soft shadows.
    • An invisible floor with a cast shadow and a contact shadow.
    • Exposure and tone mapping (Neutral by default).
  • Depth: the render's exact depth feeds Blur, Glass, Relief, Input → Depth and depth masks, including inside groups.
  • Transform, after the Blender feedback:
    • Location, Rotation and Scale as X/Y/Z fields; drag the axis letter to scrub.
    • Move, Rotate and Scale gizmos on the canvas.
    • G/R/S with an X/Y/Z axis lock; Alt+G/R/S clears.
    • Middle-drag orbits the camera, Shift+middle-drag shifts it.

Limits

  • Glass refracts the model itself; the layers below show through without bending. Refracting them is a separate proposal.
  • The React runtime export doesn't include 3D layers yet. Shader export reports them as unsupported.
  • Custom .hdr files can't be published to Community yet: the upload allowlist has no Radiance type. Bundled studios work.
  • Animation clips, the three.js upgrade and the path-traced Render mode follow in the agreed order.

Workarounds for three r183 bugs

The tests found four bugs, each worked around in the pass:

  • Stale mesh pose: a mesh that returned to an exact earlier pose kept its old GPU matrix. Meshes sharing a material share one refresh observer, whose fast path skips its matrix cache. A node-valued property on file materials forces a refresh every render.
  • Glass format error: compileAsync creates the transmission backdrop copy in the canvas format. Transmissive models now compile on first draw.
  • Frozen shadows: shadow maps update once per node frame, and frames only advance on three's own rAF. The pass advances the frame before each draw, so hidden-tab exports keep turntable shadows moving.
  • MSAA limits: WebGPU only allows 1 or 4 samples. MSAA turns off above 4K so 8192 px exports fit in memory.

Tests

  • Lint (src, tests, packages), tsc and tsgo pass; the only lint warnings are the two that were already there. Locally, bun run check fails only on scratch scripts in the gitignored .context.
  • model-layer.mjs adds 81 GPU checks, all passing:
    • Fit and transform, camera shift, blend and opacity, exposure and tone mapping.
    • Material replacement and exact restore; the stale-pose regression, with a same-pose control.
    • Model depth for effects, depth masks and groups; custom .hdr lighting, normalization and studio swaps.
    • Preview/export parity, large targets with and without MSAA, hydration, history and export.
  • model-layer-ui.mjs passes against the dev server: picker import, gizmo drags, undo granularity, G with an axis lock, Esc cancel, Alt+G, gizmo modes, uniform scale, .hdr attach/remove and save.
  • The full bun run test:composition suite passes.
  • All of the above ran on SwiftShader (software WebGPU in headless Chromium). Look and frame rate on a hardware GPU haven't been checked yet.

Manual test: tests/composition/MODEL-LAYER-MANUAL-QA.md. References 31, 15 and 26 to 30 were reviewed.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
shader-lab Ready Ready Preview Oct 1, 2026 2:04pm UTC

@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 0/5

[Medium risk] Adds 3D model layer with GLB import, materials, and HDR lighting.

The PR is not ready to merge because model placement, cutout depth, and resource-load races can produce incorrect renders or exports.

Summary

The PR adds a 3D Model source layer with glTF import, HDR lighting, material presets, depth output, canvas transforms, persistence, and composition tests.

  • Model placement, cutout geometry, and asynchronous resource handling need fixes before the feature is reliable.
  • The new decoder-backed import paths also need direct test coverage.

Reviews (1) · Last reviewed commit: "Add a 3D Model layer: GLB import, HDR st..."

Comment thread src/renderer/model-pass.ts
Comment thread src/renderer/model-pass.ts Outdated
Comment thread src/renderer/model-materials.ts
Comment thread src/renderer/pipeline-manager.ts Outdated
Comment thread src/renderer/pipeline-manager.ts Outdated
Comment thread tests/composition/model-layer.mjs
…roup failures

Media load callbacks now ignore replaced or removed passes and pending loads are cleared on disposal. Strict-mode pass failures are reported once at the originating pass instead of at every group boundary.
Publishing now drops every descendant of a hidden group and clears the
parentId of any retained layer (the audio source) whose parent was removed,
so the published scene passes hierarchy validation on reopen.
Cap the neighbor row and column search to two cells each side so the
worst-case cellAt count per fragment is 25 instead of unbounded in the
packed minimum-size, maximum-stroke configuration. Mirrored in the
runtime package.
…e on buttons

Clear the active paint layer when the layer stops being paintable (selection,
mode, visibility or lock change) so Paint never resumes without Edit Paint.
Only preventDefault on Space for non-interactive targets so focused buttons
and links still activate.
Add Mask Feather parameter definition for brush masks so the timeline shows the user-facing label. Update the manual QA Limits paragraph to match the Feather and keyframe contract.
Derive output alpha from the red, green and blue samples so displaced
fringes survive at transparent edges. Skip the blur pyramid when distance
and frost are both zero, in the app and runtime copies.
…est, video benchmark

- Image background keeps the input alpha instead of forcing opaque (editor and runtime passes)
- Duplication check edits the real mode param and asserts both layers
- Add a --dots scenario set (graph, blobs, plexus) to the effect video benchmark
…trols

Mesh drift is scaled to keep each point inside its cell, and each quad
splits along whichever diagonal is interior. Dot and link controls that
Mesh ignores are hidden, via a new oneOf visibility condition.
… style paper, duplicate test

- Un-premultiply ink color in transparent-paper output so alpha coverage is not applied twice (app and SDK pass)
- Show Angle for hatch and squiggle (new oneOf visibility condition); show Cross Angle only in hatch
- Plotter styles also set paper to color
- Duplication test now edits the real mode param and checks both layers
- Scale transparent-paper toner by input alpha so empty pixels stay clear
- Mix Amount in premultiplied space so partial amounts do not darken toner
- Skip the blur pyramid when Amount is zero
- Update a real parameter in the duplicate-independence check
- Mirror the pass changes into the React runtime
…ate test

Seed small shapes at their top-left tip so Cut Line and Cloud style them.
Raise the flood reach cap to 4096 so distant rings survive.
Let Scalloped draw inside for negative offsets.
Make the duplicate-independence check update a real parameter.
…terials, shadows, exact depth and Blender-style transforms

A new 3D Model source layer renders a .glb (or self-contained .gltf) into
its own HalfFloat MSAA target and composites it as a source over the layers
below. Draco, meshopt and KTX2 decoders are served from public/three. The
model is fitted to a tight vertex sphere with a margin; focal length changes
perspective without changing size.

Lighting: five CC0 Poly Haven studios or a custom .hdr, each normalized to
the same mean irradiance so switching studios keeps the exposure. The
environment goes through one persistent pmremTexture node set as
scene.environmentNode, because r183 bakes the environment texture node into
compiled materials and swapping scene.environment left them sampling the
disposed PMREM. A camera-relative key light casts PCF soft shadows onto an
invisible floor, and a blurred height pass under the model draws the
contact shadow. Tone mapping runs in the composite (Neutral by default).

Materials: the file's PBR materials or Chrome, Brushed Metal, Glass, Clay,
Rubber and Iridescent overrides. Anisotropy was dropped (flat white on
derivative tangents); iridescence is a view-angle spectral tint because
thin-film interference was invisible on metals.

Depth: an override pass writes near=1 depth normalized to the model's
bounding sphere, composited over any incoming scene depth, so Blur, Glass,
Relief, Input -> Depth and depth masks read the model, also inside groups.

Transform UX (asked for Blender basics): Location/Rotation/Scale as XYZ
fields with scrubbable axis letters, Move/Rotate/Scale gizmos, G/R/S modal
transforms with X/Y/Z locks, Alt+G/R/S clear, middle-drag orbit and
Shift+middle-drag shift. Modal keys are captured before other handlers so a
focused toolbar tooltip cannot swallow Escape.

Workarounds for three r183, found by tests:
- NodeMaterialObserver is shared per node builder state and its renderId
  fast path skips the world-matrix cache, so a mesh returning to an exact
  earlier pose kept a stale GPU matrix. A node-valued property on file
  materials makes the observer refresh every render.
- compileAsync allocates the transmission backdrop copy with the canvas
  format, breaking glass in HalfFloat targets; transmissive models compile
  on first draw instead.
- ShadowNode updates once per node frame per camera and frames only advance
  on three's own rAF, so the pass advances the node frame before each draw
  (hidden-tab exports and several draws per tick froze shadows).
- WebGPU only allows 1 or 4 samples; MSAA turns off above 4K so 8192 px
  exports stay within memory.

Exports wait up to 30 s for model and HDR loads.
The picker already uses the cube for 3D Model; the layer row fell back to
the generic image placeholder like effect layers do.
…, load tracking

- Offset the floor, contact shadow and key light target by Location Y
- Render the depth pass with per-material alpha-tested variants so cutouts stay empty
- Carry alpha test, alpha map, map alpha, transparency and depth write into material presets
- Track model and environment loads per request so stale loads cannot clear readiness
- Keep model and environment errors in separate slots and flag a missing custom HDR
@git-chad
git-chad force-pushed the git-chad/v3-outline branch from 4150019 to f9738ee Compare October 1, 2026 14:03
@git-chad
git-chad force-pushed the git-chad/v3-model-layer branch from e73a83d to ecc6f02 Compare October 1, 2026 14:03
@git-chad
git-chad changed the base branch from git-chad/v3-outline to git-chad/shader-lab-v3-plan October 1, 2026 15:14
@git-chad
git-chad merged commit ecc6f02 into git-chad/shader-lab-v3-plan Oct 1, 2026
4 of 5 checks passed

This branch was successfully deployed

1 active deployment
Preview — ecc6f020 Deployed Oct 1, 2026 by vercel[bot]
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