Shader: Vertex-Nodes (unterteiltes Gitter, zweiter Compiler-Rahmen) #79

Closed
opened 2026-09-13 15:43:22 +02:00 by meleki · 3 comments
Owner

Abgespalten aus #78 (Entscheidung 2026-09-13): die UV-Nodes kommen auf die Fragment-Seite (UV, Sample texture, Texture size, Time, Sin/Floor/Fract/Step). Echte Vertex-Nodes bleiben ein eigener Schritt, der erst nach vorn rückt, wenn ein konkreter Effekt Pixel außerhalb des Sprite-Rechtecks braucht.

Was der Vertex-Stage heute ist

viewport/symbols.ts: ein Unit-Quad mit vier Ecken (UNIT_QUAD, positions = uvs = 0..1, Pivot 0,5, Größe über Scale), geteilt von allen Sprites. Der Vertex-Stage (SPRITE_VERTEX in viewport/shaders.ts) macht Matrix-Transformation und reicht vTextureCoord/vColor durch.

Was Vertex-Nodes brächten — und was nicht

  • Mit dem Quad: vier Ecken bewegen — Scheren, Strecken, Versetzen. Das kann ein Behavior heute schon über width/height/Position.
  • Mit unterteiltem Gitter (eigene Geometrie pro Sprite statt des geteilten Quads): Welle, Jelly-Bounce, Fahne — Verformung, die über das Rechteck hinausgeht. Das ist der einzige echte Gewinn.
  • Nicht: Pixelgenaues (Pixelate, Scanlines, Verzerrung feiner als das Gitter) — bleibt Fragment-Sache. Die beiden Wege sind additiv.

Was zu bauen wäre

  1. Zweiter Compiler-Rahmen: Vertex-Graph mit eigenen Eingängen (Position, UV, Time, Properties), eigenem Vertex output (Position-Versatz, UV-Versatz), Varyings zwischen den Stufen.
  2. Gitter-Geometrie mit Unterteilungs-Parameter (pro Shader-Asset oder pro Sprite), Batching-Folgen messen (tools/perf-shaders.mjs).
  3. Die Fragment-Nodes aus #78 bleiben unverändert gültig: UV liest vTextureCoord, was ein Vertex-Graph dort hineinschreibt, kommt automatisch an.

Beweis beim Schließen

Ein Sprite mit einem Wellen-Vertex-Shader zeigt Pixel außerhalb seines Rechtecks (Pixelmessung im Play), ein Sprite ohne bleibt byte-gleich; Performance-Tabelle mit Gitter gegen das Quad.

Abgespalten aus #78 (Entscheidung 2026-09-13): die UV-Nodes kommen auf die **Fragment-Seite** (`UV`, `Sample texture`, `Texture size`, `Time`, `Sin`/`Floor`/`Fract`/`Step`). Echte Vertex-Nodes bleiben ein eigener Schritt, der erst nach vorn rückt, wenn ein konkreter Effekt **Pixel außerhalb des Sprite-Rechtecks** braucht. ## Was der Vertex-Stage heute ist `viewport/symbols.ts`: ein **Unit-Quad mit vier Ecken** (`UNIT_QUAD`, positions = uvs = 0..1, Pivot 0,5, Größe über Scale), geteilt von allen Sprites. Der Vertex-Stage (`SPRITE_VERTEX` in `viewport/shaders.ts`) macht Matrix-Transformation und reicht `vTextureCoord`/`vColor` durch. ## Was Vertex-Nodes brächten — und was nicht - Mit dem Quad: vier Ecken bewegen — Scheren, Strecken, Versetzen. Das kann ein Behavior heute schon über `width`/`height`/Position. - Mit **unterteiltem Gitter** (eigene Geometrie pro Sprite statt des geteilten Quads): Welle, Jelly-Bounce, Fahne — Verformung, die über das Rechteck hinausgeht. Das ist der einzige echte Gewinn. - Nicht: Pixelgenaues (Pixelate, Scanlines, Verzerrung feiner als das Gitter) — bleibt Fragment-Sache. Die beiden Wege sind additiv. ## Was zu bauen wäre 1. Zweiter Compiler-Rahmen: Vertex-Graph mit eigenen Eingängen (`Position`, `UV`, `Time`, Properties), eigenem `Vertex output` (Position-Versatz, UV-Versatz), Varyings zwischen den Stufen. 2. Gitter-Geometrie mit Unterteilungs-Parameter (pro Shader-Asset oder pro Sprite), Batching-Folgen messen (`tools/perf-shaders.mjs`). 3. Die Fragment-Nodes aus #78 bleiben unverändert gültig: `UV` liest `vTextureCoord`, was ein Vertex-Graph dort hineinschreibt, kommt automatisch an. ## Beweis beim Schließen Ein Sprite mit einem Wellen-Vertex-Shader zeigt Pixel **außerhalb** seines Rechtecks (Pixelmessung im Play), ein Sprite ohne bleibt byte-gleich; Performance-Tabelle mit Gitter gegen das Quad.
meleki added the low-priorityshader labels 2026-09-13 15:43:22 +02:00
admin added this to the Development project 2026-09-13 16:16:38 +02:00
admin moved this to Not planned for now in Development on 2026-09-16 08:20:21 +02:00
admin added held-back and removed low-priority labels 2026-09-16 14:41:04 +02:00
admin moved this to Next in Development on 2026-10-02 16:55:50 +02:00
admin removed the held-back label 2026-10-02 16:56:10 +02:00
admin added the low-priority label 2026-10-02 19:40:39 +02:00
admin moved this to Queue in Development on 2026-10-02 19:40:45 +02:00
admin moved this to Next in Development on 2026-10-02 20:16:59 +02:00

Wo wird definiert ob es ein Quad ist oder ein Gitter mit einer gewünschten Density.

Wo wird definiert ob es ein Quad ist oder ein Gitter mit einer gewünschten Density.
admin removed the low-priority label 2026-10-02 23:26:45 +02:00
Author
Owner

Antwort auf „Wo wird definiert, ob es ein Quad ist oder ein Gitter mit einer gewünschten Density“

Heute nirgends. Alle Sprites teilen sich ein Quad mit vier Ecken (UNIT_QUAD in src/viewport/symbols.ts). Seit #69 bekommt ein Sprite zwar ein eigenes Quad, sobald sein Shader ein Padding hat, es bleibt aber bei vier Ecken. Der Issue-Text ließ den Punkt offen („pro Shader-Asset oder pro Sprite“).

Entschieden (2026-10-02):

  • Es gibt keinen eigenen Schalter „Quad oder Gitter“. Es gibt eine Zahl Subdivisions, und 1 heißt Quad.
  • Die Zahl sitzt am neuen Vertex output-Node, so wie Edge und Padding am Output sitzen. Sie gilt für jedes Objekt, das den Shader trägt.
  • Ein Shader ohne Vertex output-Node bleibt beim geteilten Quad und kostet nichts extra.

Vorschlag, noch nicht bestätigt:

  • Die Zahl zählt Felder je Achse: 8 heißt 8 mal 8 Felder, also 81 Ecken. Deckel bei 64.

Warum am Shader und nicht am Sprite:

  • Wie fein das Gitter sein muss, hängt am Effekt, und den kennt der Shader.
  • Am Sprite wäre es eine Zeile mehr an jedem Objekt, die ohne Vertex-Shader nichts tut.
  • Nachteil: Ein sehr großes und ein sehr kleines Sprite mit demselben Shader bekommen gleich viele Ecken.

Gemessen ist dazu noch nichts. Was ein Gitter bei vielen Sprites kostet, zeigt erst die Performance-Tabelle aus „Beweis beim Schließen“.

**Antwort auf „Wo wird definiert, ob es ein Quad ist oder ein Gitter mit einer gewünschten Density“** Heute nirgends. Alle Sprites teilen sich ein Quad mit vier Ecken (`UNIT_QUAD` in `src/viewport/symbols.ts`). Seit #69 bekommt ein Sprite zwar ein eigenes Quad, sobald sein Shader ein `Padding` hat, es bleibt aber bei vier Ecken. Der Issue-Text ließ den Punkt offen („pro Shader-Asset oder pro Sprite“). **Entschieden (2026-10-02):** - Es gibt keinen eigenen Schalter „Quad oder Gitter“. Es gibt eine Zahl `Subdivisions`, und 1 heißt Quad. - Die Zahl sitzt am neuen `Vertex output`-Node, so wie `Edge` und `Padding` am `Output` sitzen. Sie gilt für jedes Objekt, das den Shader trägt. - Ein Shader ohne `Vertex output`-Node bleibt beim geteilten Quad und kostet nichts extra. **Vorschlag, noch nicht bestätigt:** - Die Zahl zählt Felder je Achse: 8 heißt 8 mal 8 Felder, also 81 Ecken. Deckel bei 64. **Warum am Shader und nicht am Sprite:** - Wie fein das Gitter sein muss, hängt am Effekt, und den kennt der Shader. - Am Sprite wäre es eine Zeile mehr an jedem Objekt, die ohne Vertex-Shader nichts tut. - Nachteil: Ein sehr großes und ein sehr kleines Sprite mit demselben Shader bekommen gleich viele Ecken. Gemessen ist dazu noch nichts. Was ein Gitter bei vielen Sprites kostet, zeigt erst die Performance-Tabelle aus „Beweis beim Schließen“.
admin moved this to Done in Development on 2026-10-03 00:14:23 +02:00
Author
Owner

Gebaut. Die Frage aus dem Kommentar oben (wo wird Quad oder Gitter definiert) ist in Kommentar 2959 beantwortet: Es gibt keinen Schalter, nur die Zahl Subdivisions am neuen Node Vertex output.

Was sich ändert

  • Node Vertex output im Shader-Graph, höchstens einer je Graph. Die Eingänge x und y verschieben jede Ecke des Gitters, in Pixeln des Bildes (Antwort 1). Die UV bleiben an der Ecke, das Bild verformt sich also mit. Einen UV-Versatz gibt es nicht, dafür ist Sample texture im Fragment da.
  • Subdivisions am Node: Felder je Achse, 1 bis 64, Standard 8 (Antwort 4). 1 ist das Quad. Ein Shader ohne den Node bleibt beim geteilten Quad aller Sprites.
  • Speisen dürfen ihn UV, Time, Property, Number und die Rechen-Nodes (Antwort 2). Ein Node darf beide Outputs speisen.
  • Nicht erlaubt sind Texture color, Texture alpha, Sample texture und Color. Der Node meldet „Not available per corner: …“, und der Shader kompiliert nicht.
  • Träger (Antwort 3): Sprites, Texte und die Vorschau im Shader-Tab. 9-Slice und Partikel zeichnen so, als gäbe es den Node nicht.
  • Anklicken, Auswahlrahmen, Größe und Collider bleiben beim Rechteck ohne Verformung.

Was ich selbst festgelegt habe

  • Texture size ist am Vertex output ebenfalls gesperrt. Der Node stand in der Auswahl weder bei den erlaubten noch bei den gesperrten.
  • Ein Objekt mit Vertex output wird nie ausgeblendet, auch wenn sein Rechteck außerhalb des Bildes liegt, wie bei Padding (#69).
  • Mit Padding deckt das Gitter auch den Rand ab.
  • Ein unverbundener Vertex output kostet trotzdem das Gitter, weil Subdivisions auf 8 steht.

Performance (stein2, Intel HD 630, Bilder je Sekunde)

Sprites 200 400 800 1600
ohne Shader 60 60 60 41
Shader ohne Vertex output 60 60 58 36
Subdivisions 8 59 58 46 27
Subdivisions 32 54 57 41 23
  • Bis 400 Sprites fällt ein Gitter kaum auf, bei 800 kostet es rund ein Viertel der Bildrate.
  • Das ist ein Lauf je Zeile, nicht drei. Der Wert 54 bei 200 Sprites liegt unter dem bei 400, einzelne Werte sind also nur auf ein paar Bilder genau.
  • Jedes Sprite hält dabei sein eigenes Gitter. Gleiche Gitter zu teilen wäre eine mögliche Verbesserung, gebaut ist sie nicht.
  • Nachmessen: tools/perf-shaders.mjs mit GM_PERF_SUBDIVISIONS=8.

Under the hood

  • ShaderVertexOutput, subdivisionsOf und das Merkmal perPixel in src/core/shader/nodes.ts. Der Compiler startet einen zweiten Durchlauf vom Vertex output und gibt vertex (Code und Subdivisions) mit zurück.
  • gridQuad in src/viewport/quadPadding.ts (ohne Pixi): dasselbe Rechteck in Felder geschnitten, Positionen und UV sind wie am Quad dieselben Zahlen.
  • src/viewport/shaders.ts setzt den Versatz in den Vertex-Stage. symbols.ts wählt je Sprite Quad, Rand-Quad oder Gitter. Ein 9-Slice-Sprite bekommt den Vertex-Teil nicht.
  • WorldViewport nimmt Träger eines Shaders mit Vertex output vom Ausblenden aus.

Tests (auf melektaus-atx)

Gate      → 220 Files, 3416 Tests, CHECK EXIT 0   (3401 + 15)
e2e 28,30 → ALL PASSED – 110 checks               (106 + 4)
  • 15 neue Unit-Tests für Compiler und Gitter.
  • Vier neue e2e-Checks:
    • Ein fester Versatz von 16 verschiebt das Bild um ein halbes Sprite.
    • Ein Schnitt auf v biegt mit 8 Feldern nur die untere Hälfte, mit 1 Feld schert das ganze Bild.
    • Texture alpha wird am Node abgelehnt.
    • Ein Text steht 6 Einheiten weiter rechts.
  • Die e2e-Summe 1699 ist gerechnet (1695 + 4). Volle Suite und Smokes liefen nicht.
  • Das Gate lief am Ende des Baus, vor dem Commit habe ich es nicht noch einmal gefahren.
  • Gegenproben habe ich keine gefahren, Screenshots keine angesehen.
  • Nicht geprüft:
    • die Vorschau im Shader-Tab
    • Time am Vertex output im Browser (nur Unit-Test)
    • gespiegelte Sprites
    • die Kombination mit Padding im Browser
    • alles nach dem Build

Nicht dabei

  • 9-Slice und Partikel (Antwort 3).
  • Ein UV-Versatz am Vertex output, wie ihn der Issue-Text nennt (Antwort 1).
  • Ein Node Position, und Bildlesen im Vertex-Teil (Antwort 2).

Commit: 4716726. Wiki: Shaders (neuer Abschnitt „Moving the corners“, Tabelle, Node-Zeile) und Development (Summe), 8a9c36f.

Gebaut. Die Frage aus dem Kommentar oben (wo wird Quad oder Gitter definiert) ist in Kommentar 2959 beantwortet: Es gibt keinen Schalter, nur die Zahl *Subdivisions* am neuen Node `Vertex output`. ## Was sich ändert - **Node `Vertex output`** im Shader-Graph, höchstens einer je Graph. Die Eingänge `x` und `y` verschieben jede Ecke des Gitters, in Pixeln des Bildes (Antwort 1). Die UV bleiben an der Ecke, das Bild verformt sich also mit. Einen UV-Versatz gibt es nicht, dafür ist `Sample texture` im Fragment da. - **Subdivisions** am Node: Felder je Achse, 1 bis 64, Standard 8 (Antwort 4). 1 ist das Quad. Ein Shader ohne den Node bleibt beim geteilten Quad aller Sprites. - **Speisen dürfen ihn** `UV`, `Time`, `Property`, `Number` und die Rechen-Nodes (Antwort 2). Ein Node darf beide Outputs speisen. - **Nicht erlaubt** sind `Texture color`, `Texture alpha`, `Sample texture` und `Color`. Der Node meldet „Not available per corner: …“, und der Shader kompiliert nicht. - **Träger** (Antwort 3): Sprites, Texte und die Vorschau im Shader-Tab. 9-Slice und Partikel zeichnen so, als gäbe es den Node nicht. - **Anklicken, Auswahlrahmen, Größe und Collider** bleiben beim Rechteck ohne Verformung. ## Was ich selbst festgelegt habe - **`Texture size` ist am `Vertex output` ebenfalls gesperrt.** Der Node stand in der Auswahl weder bei den erlaubten noch bei den gesperrten. - **Ein Objekt mit `Vertex output` wird nie ausgeblendet**, auch wenn sein Rechteck außerhalb des Bildes liegt, wie bei `Padding` (#69). - **Mit `Padding` deckt das Gitter auch den Rand ab.** - **Ein unverbundener `Vertex output` kostet trotzdem das Gitter**, weil *Subdivisions* auf 8 steht. ## Performance (stein2, Intel HD 630, Bilder je Sekunde) | Sprites | 200 | 400 | 800 | 1600 | | --- | --- | --- | --- | --- | | ohne Shader | 60 | 60 | 60 | 41 | | Shader ohne `Vertex output` | 60 | 60 | 58 | 36 | | Subdivisions 8 | 59 | 58 | 46 | 27 | | Subdivisions 32 | 54 | 57 | 41 | 23 | - Bis 400 Sprites fällt ein Gitter kaum auf, bei 800 kostet es rund ein Viertel der Bildrate. - **Das ist ein Lauf je Zeile, nicht drei.** Der Wert 54 bei 200 Sprites liegt unter dem bei 400, einzelne Werte sind also nur auf ein paar Bilder genau. - Jedes Sprite hält dabei sein eigenes Gitter. Gleiche Gitter zu teilen wäre eine mögliche Verbesserung, gebaut ist sie nicht. - Nachmessen: `tools/perf-shaders.mjs` mit `GM_PERF_SUBDIVISIONS=8`. ## Under the hood - `ShaderVertexOutput`, `subdivisionsOf` und das Merkmal `perPixel` in `src/core/shader/nodes.ts`. Der Compiler startet einen zweiten Durchlauf vom `Vertex output` und gibt `vertex` (Code und Subdivisions) mit zurück. - `gridQuad` in `src/viewport/quadPadding.ts` (ohne Pixi): dasselbe Rechteck in Felder geschnitten, Positionen und UV sind wie am Quad dieselben Zahlen. - `src/viewport/shaders.ts` setzt den Versatz in den Vertex-Stage. `symbols.ts` wählt je Sprite Quad, Rand-Quad oder Gitter. Ein 9-Slice-Sprite bekommt den Vertex-Teil nicht. - `WorldViewport` nimmt Träger eines Shaders mit `Vertex output` vom Ausblenden aus. ## Tests (auf `melektaus-atx`) ``` Gate → 220 Files, 3416 Tests, CHECK EXIT 0 (3401 + 15) e2e 28,30 → ALL PASSED – 110 checks (106 + 4) ``` - **15 neue Unit-Tests** für Compiler und Gitter. - **Vier neue e2e-Checks:** - Ein fester Versatz von 16 verschiebt das Bild um ein halbes Sprite. - Ein Schnitt auf `v` biegt mit 8 Feldern nur die untere Hälfte, mit 1 Feld schert das ganze Bild. - `Texture alpha` wird am Node abgelehnt. - Ein Text steht 6 Einheiten weiter rechts. - Die e2e-Summe **1699** ist gerechnet (1695 + 4). Volle Suite und Smokes liefen nicht. - Das Gate lief am Ende des Baus, vor dem Commit habe ich es nicht noch einmal gefahren. - Gegenproben habe ich keine gefahren, Screenshots keine angesehen. - **Nicht geprüft:** - die Vorschau im Shader-Tab - `Time` am `Vertex output` im Browser (nur Unit-Test) - gespiegelte Sprites - die Kombination mit `Padding` im Browser - alles nach dem Build ## Nicht dabei - 9-Slice und Partikel (Antwort 3). - Ein UV-Versatz am `Vertex output`, wie ihn der Issue-Text nennt (Antwort 1). - Ein Node `Position`, und Bildlesen im Vertex-Teil (Antwort 2). Commit: `4716726`. Wiki: *Shaders* (neuer Abschnitt „Moving the corners“, Tabelle, Node-Zeile) und *Development* (Summe), `8a9c36f`.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: meleki/feuerwerk#79