Skip to content

feat(rendering): rectangular clipping/masking for sprites #583

Description

@stormmuller

Summary

Forge has no way to clip a sprite to a rectangle. Anything that needs to render "only the part of this inside that region" — scrolling lists, minimap viewports, fog-of-war reveals, wipe transitions, health bars that fill by revealing rather than scaling, cropped portraits — currently has no mechanism.

Raised by @stormmuller while reviewing #580, on the grounds that clipping (like text rendering) is generally useful and shouldn't be buried inside a UI module. Explicitly out of scope for the UI work; filed here so it can be designed on its own merits.

Why not gl.scissor

CameraEcsComponent.scissorRect already exists as a per-camera concept, but scissor is the wrong tool for per-sprite clipping: it is GL state, so changing it forces a draw-call break. Applied to a scrolling list, that means one draw call per clipped element — destroying batching for precisely the case with the most elements.

Suggested approach

Pass the clip rectangle as per-instance data and discard in the fragment shader, the way Unity's RectMask2D works:

  • +4 floats per instance (clipMin.xy, clipMax.xy) on the instance data segment.
  • Fragment shader discards fragments outside the rect.
  • Batching is preserved — a whole scrolling list stays in one draw call.
  • Nested clips intersect their rectangles, so nesting costs nothing extra.
  • Unclipped sprites pass an infinite rect, or the segment is opt-in via a separate Renderable.

Worth considering during design:

  • Soft edges. A one- or two-pixel feather is nearly free in the shader and looks markedly better than a hard discard on scrolling text.
  • Rotation. An axis-aligned clip rect in world space is the simple case. Clipping a rotated element correctly means either transforming the fragment into the clipper's local space, or accepting axis-aligned-only and documenting it.
  • Arbitrary shapes. A stencil-buffer or alpha-cutoff mask (Unity's Mask) is strictly more general but adds stencil state management to a renderer that currently has none. Probably a separate, later mechanism rather than part of this.

Relationship to the UI design (#580)

design/ui-system.md DL-09 covers the same ground and reached the same per-instance-clip conclusion. That design now defers to this issue rather than owning the work.

Note the dependency: ScrollRectEcsComponent (UI backlog 3.4) cannot ship before this lands. Scroll views are unusable without clipping — content spills past the viewport. Anything else in the UI backlog is unaffected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions