From 986aad5bd7e637c3af51a1fa3a1852ba99336ddc Mon Sep 17 00:00:00 2001 From: meleki Date: Sat, 3 Oct 2026 16:03:25 +0200 Subject: [PATCH] Describe the camera shader (#299) Shaders gets the camera as a wearer, its measurement and In the code. Scene editing gets the Shader switch, Concepts a pointer, Development the e2e sum 1725. --- Concepts.md | 4 +++- Development.md | 2 +- Scene-editing.md | 6 ++++-- Shaders.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/Concepts.md b/Concepts.md index a3ed4c4..ea20a22 100644 --- a/Concepts.md +++ b/Concepts.md @@ -55,7 +55,9 @@ that is made the main one, with the game camera as an overlay. The main camera d of the picture and the zoom of the tab; an overlay camera is fitted into that picture without distortion, so one of the same size and zoom covers it exactly. The mouse is reported through the main camera (#304 is about asking another). Two cameras switched on without *Overlay* draw as -before: through the first one only. The Scene tab shows every layer. +before: through the first one only. The Scene tab shows every layer. A camera can also wear a +shader over its picture (#299, see *Wearing a shader* in [Shaders](Shaders)); it works on that +camera's picture alone, before the pictures are laid on top of each other. Measured in the checks (step 63): a green sprite of an overlay camera at `z` -10 covers a red one of the main camera at `z` 1000, a `UI` sprite in front of the main camera and a `Default` one in front of the overlay stay away, and the same holds in the running game. diff --git a/Development.md b/Development.md index 9d3c44c..2a504d2 100644 --- a/Development.md +++ b/Development.md @@ -18,7 +18,7 @@ php tests/php/accounts.test.php # the API's accoun # has and the web server's container has not (SKIP there) php tests/php/snapshots.test.php # the snapshot store (#231), the same way php tests/php/releases.test.php # the release store (#266), the same way -node tools/seed-e2e.mjs && node tools/e2e.mjs # 1720 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1725 editor checks, screenshots in /tmp/gm-e2e-*.png GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step ./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh status # live progress, or the last run's result diff --git a/Scene-editing.md b/Scene-editing.md index 74acd1a..3fe7261 100644 --- a/Scene-editing.md +++ b/Scene-editing.md @@ -78,9 +78,11 @@ side. Names off screen take their row as well, so none jumps while the view pans **The eye picks what the Scene tab shows.** Beside *Fit* in the tab's header, the eye opens a list of every type that can be placed, Behavior to Tilemap, and per type up to three switches: **Symbol** (its icon), **Name** (its name plate) and **Shape**: on a collider its outline, on a tilemap the -outline of its solid cells, while the map's tiles stay drawn. Sprite, +outline of its solid cells, while the map's tiles stay drawn. The Camera row has a **Shader** +switch instead, off by default: it lays the main camera's shader over its frame (#299, see +[Shaders](Shaders)). Sprite, Text and Tilemap have no icon, so their Symbol switch is greyed out; their Name switch puts a plate -centred on the object, and it starts off. Every other switch starts on, which is what the Scene +centred on the object, and it starts off. Every other switch but the Shader starts on, which is what the Scene tab always showed. A hidden icon takes no room on a ring and draws no owner link, and its name then stands centred on the object. A hidden icon or outline cannot be clicked either: the object is still selected in the hierarchy. The list stays open while you switch. The switches are saved with diff --git a/Shaders.md b/Shaders.md index 8245992..5f952b7 100644 --- a/Shaders.md +++ b/Shaders.md @@ -374,6 +374,35 @@ a text keeps a name it already has (`text`, `fontSize`, `align` …): a shader p gets no row. In a `Properties` node the parameters are offered under *Type: Text* as they are under *Type: Sprite*. A shaded text is one draw call, like a shaded sprite. +**A camera wears one over its whole picture** (#299): post-processing such as a vignette, a colour +grade, grey, scanlines, a red flash on a hit or a fade. The camera's *Shader* row sits under +*Overlay*, its parameters below it, overridable, animatable and in a `Properties` node under +*Type: Camera* like a sprite's. A camera keeps a name it already has (`zoom`, `sees` …). The world +the camera sees is drawn into a picture of its frame first, cleared to the scene's background, +and the shader draws that picture where the frame stands, so `u`/`v` run from 0 to 1 across the +camera's picture. Each camera wears its own, so a change of camera changes the effect, and an +overlay camera (#302) wears one over its own see-through picture before it is laid on the main +one: a red flash on the game camera leaves the HUD of an overlay camera alone. The row +**Shader resolution**, shown while a shader is picked, says how many pixels that picture has: + +- **Screen pixels** (default): as many as the camera's picture covers on screen, so it stays + sharp at any game scale. +- **Camera pixels**: *View width* x *View height*, scaled up to the game scale without smoothing, + for pixel art: a scanline or a blur step per game pixel. + +**Either way a pixel counts as a camera pixel.** `Texture size` is *View width* x *View height* +in both, and a `Blur` of 4 reaches 4 camera pixels at any game scale and any size of the window, +the same rule a field shader follows with its frame. *Screen pixels* only looks sharper. The frame +is the edge, so `Padding` and `Vertex output` do nothing there, as on a field. The Game tab, the +running game and the game link (`play.html`) all draw it. In the Scene tab the eye in the header +has a **Shader** switch on the Camera row, off by default: switched on, the main camera's shader +lies over its frame there, under the symbols, names and gizmos. That picture holds every render +layer, since the Scene tab shows all of them. Measured in the checks (step 63): a camera shader +that hands the picture through draws the game picture byte for byte at 100%, the right way up; a +flat red fills it to the corners while the overlay camera's square keeps its green; `Texture size` +reads 640 x 360 in both resolutions with the Game tab fitted to 670 x 377 pixels; and the Scene +tab shows the red only with the switch on. + ## What it costs, and where it stops A shader runs in the sprite's own draw: the mesh is drawn once, with the graph's fragment in place @@ -483,6 +512,23 @@ more than the samples save: the radius hardly matters there, at radius 4 one pas 400 sprites on, and radius 16, with its third draw, is as slow at 800 sprites as radius 8 in one pass. Passes are for wide blurs and large pictures, not for every soft edge. +A camera shader (#299) is one pass for the whole view. Measured with `tools/perf-camera-shader.mjs` +on the same HD 630 in a 1920x1080 window, the Game tab at Fit (1310x679, the picture 1207x679 at +*Screen pixels*), plain sprites of 48x48 in the 640x360 camera, in frames per second. At that size +the editor holds well under 60 without any shader, and the scene alone moved by up to 5 frames a +second from one run to the next, so the hand-through rows are three runs each: + +| sprites | 100 | 400 | +| --- | --- | --- | +| no camera shader | 35.5 - 41.0 | 29.2 - 33.3 | +| hands through, Screen pixels | 35.0 - 41.5 | 31.1 - 34.1 | +| hands through, Camera pixels | 33.9 - 35.9 | 30.0 - 31.7 | +| Blur 4, Screen pixels (one run, its own scene 41.7 / 37.9) | 33.5 | 33.3 | +| Blur 4, Camera pixels (same run) | 33.9 | 33.6 | + +A camera shader that hands the picture through costs less than the scene varies by itself. A Blur +of 4 over the whole view cost about 4 to 8 frames a second in its one run, at either resolution. + ## In the code `src/core/shader/nodes.ts` is the catalogue, `src/core/shader/compile.ts` turns a graph into the @@ -517,7 +563,14 @@ since two graphs often share a pass's program, every shader declares all 8 pass uniform group, even an empty one. Otherwise the second graph reads a placeholder and draws white. `Blur` and `Texture size` count the picture's size through the uniform `uPixelScale`: 1 on every wearer but a field, where it is the frame's units per pixel of its picture (`unitsPerPixel` in -`src/viewport/fieldFrame.ts`). +`src/viewport/fieldFrame.ts`), and a camera, where it is camera pixels per pixel of its picture +(`pixelScale` in `src/viewport/cameraPicture.ts`). +A camera's shader is `CameraShade` (`src/viewport/cameraShade.ts`): every view keeps its masks and +objects in one container, which is drawn into the camera's picture with Pixi's `render` and a +transform from world units to that picture. The Game tab and the game link then take that container +off the stage and let the picture stand in for it; the Scene tab leaves it drawn, since it shows +more than the frame, and lays the picture over it below the symbols (`shadingCamera` and +`keepsWorldAroundShader` in `WorldViewport`). `src/viewport/ShaderLibrary.ts` caches the compiled graph per asset, keyed by a fingerprint of what the fragment depends on — node ids, types, params, declared ports, and which port feeds which — so moving, collapsing or folding compiles nothing again; a changed param or link does, once