Shaders: the shader asset, its tab, the node set and the Adjust colors recipe (#62)
1 parent
e3e65b951a
commit
a0fad88eb2
5 files changed
+93
-4
No files matched your search
+3
-2
@@ -1,8 +1,9 @@
|
||||
| Term | Meaning |
|
||||
|---|---|
|
||||
| **Asset** | Named thing with a type (`scene`, `camera`, `sprite`, `text`, `behavior`, `collider`, `container`, `texture`, `animation`, `statemachine`) and typed properties. Assets have **no position**. |
|
||||
| **Texture** | An uploaded image. Scenes, textures, animations and state machines are the four types that cannot be placed: a texture has no position, it is *worn* by a sprite through the ordinary property `texture`. See [Textures](Textures). |
|
||||
| **Asset** | Named thing with a type (`scene`, `camera`, `sprite`, `text`, `behavior`, `collider`, `container`, `texture`, `animation`, `statemachine`, `shader`) and typed properties. Assets have **no position**. |
|
||||
| **Texture** | An uploaded image. Scenes, textures, animations, state machines and shaders are the five types that cannot be placed: a texture has no position, it is *worn* by a sprite through the ordinary property `texture`. See [Textures](Textures). |
|
||||
| **State machine** | A named set of **states**, edited as nodes in its own tab. A behavior puts it into a state with `Set state` and branches on it with `State branch`; each behavior instance keeps its own current state. See [State machines](State-machines). |
|
||||
| **Shader** | A node graph that becomes the GLSL a sprite is drawn with, edited in its own tab. A sprite wears it through `shader`; the numbers the graph declares are rows below that dropdown. See [Shaders](Shaders). |
|
||||
| **Text** | An asset that draws a line of text (`text`, `fontSize`, `color`, `align`) — titles, HUD readouts, button labels. Its content is an ordinary property, so a `Properties` node of type Text with `text` switched to Set changes it while playing. |
|
||||
| **Enabled** | Every placeable asset has the switch `enabled` (default on). A disabled object is not drawn, does not collide and its behavior does not run — **and neither does anything parented below it**. Switching one instance off is a normal override, which makes a container a *screen*: switch it on to show it, off to hide it. From a graph, `Properties` with `enabled` switched to Set does it, and `Is enabled` asks (including the parents); a behavior hears the switch as `On enable` / `On disable`. |
|
||||
| **Scene object** | An asset placed into a scene: a link to the asset + a **position** (`x`, `y`, `z`) + sparse **overrides** of the asset's properties + an optional **parent** (another object of the scene). Scenes cannot be placed. |
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
|
||||
# code is lost (tail's is reported), so a red run reads as green
|
||||
npm run build # writes dist/ and regenerates index.html
|
||||
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 671 editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 700 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
|
||||
@@ -150,4 +150,4 @@ widths to find click points. The **render device is not in this repo** — `/dev
|
||||
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
|
||||
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
|
||||
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
|
||||
WebGL 2.0, and the same 671 checks as here.
|
||||
WebGL 2.0, and the same 700 checks as here.
|
||||
+1
@@ -16,6 +16,7 @@ Live: <https://nockal.com/game-maker/> · Widget playground: <https://nockal.com
|
||||
| [Textures](Textures) | Getting images in, where they go, tinting, the filter, frames, upload limits |
|
||||
| [Animations](Animations) | Animation assets, the Animation tab, how they run |
|
||||
| [State machines](State-machines) | The state machine asset, `State branch` and `Set state` |
|
||||
| [Shaders](Shaders) | The shader asset, the Shader tab, the node set, the Adjust colors recipe, wearing a shader |
|
||||
| [Containers](Containers) | Editing a template, instances and their edits, screens without scene switching |
|
||||
| [Keyboard](Keyboard) | Every shortcut |
|
||||
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
A **shader** is an asset: a node graph that becomes the GLSL fragment a sprite is drawn with. Like a
|
||||
texture or a state machine it cannot be placed — a sprite *wears* it through its `Shader` property,
|
||||
and the numbers the shader declares appear as rows below that dropdown. There are no built-in
|
||||
shaders; the one effect the node set is sized for is the "Adjust colors" recipe below.
|
||||
|
||||
## The Shader tab
|
||||
|
||||
`+` in the **Assets** panel makes a shader; selecting one opens the **Shader** tab on it, with the
|
||||
same canvas the Behavior tab has: right-click places a node, drag moves, drag from a port links,
|
||||
`Snap` snaps to the 24 px grid, `Fit` frames the graph, undo takes every step back. The readout on
|
||||
the right says `N properties` while the graph compiles, otherwise why it does not (`No Output node`,
|
||||
a bad property name, ...). A node that keeps the graph from compiling shows the reason on its body
|
||||
and a red frame.
|
||||
|
||||
A shader graph is compiled on every change. A sprite wearing a graph that does not compile is drawn
|
||||
without a filter, as if it wore none.
|
||||
|
||||
## The nodes
|
||||
|
||||
Every value is either a **number** (a GLSL `float`) or a **colour** (a `vec3`, RGB in 0-1). Ports
|
||||
typed `any` take both: with a colour on either side the result is a colour, per channel; a number
|
||||
fed to a colour port is broadcast to all three channels. A colour cannot be fed to a number port.
|
||||
|
||||
| Node | Ports | What it does |
|
||||
|---|---|---|
|
||||
| **Texture color** | out `color` | The sprite's own pixel before the shader: its texture tinted by its colour, straight (unpremultiplied). |
|
||||
| **Output** | in `color` | The colour the sprite is drawn with, clamped to 0-1. Its alpha stays the texture's. Left unconnected, the pixel passes through. One per graph. |
|
||||
| **Property** | out `value` | Declares a number the sprite sets: `Name` is the row below the sprite's Shader dropdown and the uniform in the fragment; `Default`, `Min`, `Max` are the row's range. A name is one word (letters, digits, `_`), unique in the graph. |
|
||||
| **Color** | out `color` | A constant colour, picked in the inspector. |
|
||||
| **Add**, **Subtract**, **Multiply**, **Divide** | in `a`, `b` (any) · out `result` | Arithmetic, per channel for colours. An unconnected input is the number typed on the node (0, or 1 for the second input of Multiply and Divide). |
|
||||
| **Mix** | in `a`, `b` (any), `t` (number) · out `result` | Blends from `a` to `b`: `t` = 0 is `a`, 1 is `b`, beyond that it extrapolates. |
|
||||
| **Luminance** | in `color` · out `value` | How bright a colour is (Rec. 709 weights), as one number. |
|
||||
|
||||
A colour input with no field (Output, Luminance) that is left unconnected reads the texel's colour.
|
||||
|
||||
## Adjust colors
|
||||
|
||||
Brightness, contrast and saturation with **1 as neutral** on every one, on the straight colour so a
|
||||
transparent pixel never gains colour:
|
||||
|
||||
```
|
||||
rgb = rgb * brightness
|
||||
rgb = (rgb - 0.5) * contrast + 0.5
|
||||
rgb = mix(luminance(rgb), rgb, saturation)
|
||||
```
|
||||
|
||||
As nodes: `Texture color` → `Multiply` (b: Property *brightness*) → `Subtract` (b: 0.5) →
|
||||
`Multiply` (b: Property *contrast*) → `Add` (b: 0.5) → both into `Luminance` and into `Mix` as
|
||||
`b`, `Luminance` into `Mix` as `a`, Property *saturation* into `t`, `Mix` into `Output`. Three
|
||||
`Property` nodes with default 1, min 0, max 4. The test fixture and the three example games ship
|
||||
this graph as the asset "Adjust colors" (`tools/lib/shaders.mjs`).
|
||||
|
||||
Measured on a flat `#4080ff` sprite: brightness 0.5 gives `#204080`, contrast 0 gives `#808080`,
|
||||
saturation 0 gives `#7c7c7c` — all byte-exact against the formula.
|
||||
|
||||
## Wearing a shader
|
||||
|
||||
The sprite's `Shader` row is a picker over the project's shader assets (`None` is no shader and no
|
||||
extra render pass). The rows below it are the shader's `Property` nodes, in node order; they are
|
||||
ordinary sprite properties: overridable per placed object, reset (↺) and apply (↑) like any other,
|
||||
readable and writable by a `Properties` node at runtime — a behavior can fade a sprite by writing
|
||||
its `brightness` every frame. A value with no row (the shader was switched, the property renamed)
|
||||
stays stored but marks nothing; it comes back with the shader.
|
||||
|
||||
A placed object may override the shader itself; it then shows *that* shader's rows. Switching the
|
||||
shader keeps the values of the old one's rows in the bag — they are not visible and do not count as
|
||||
deviations while their shader is not worn.
|
||||
|
||||
## What it costs, and where it stops
|
||||
|
||||
A shader is a Pixi filter: the sprite is rendered into an intermediate texture and drawn back with
|
||||
the fragment, one pass per sprite wearing one. Fine for dozens, not for thousands. The pass runs at
|
||||
screen resolution, so pixel art stays sharp. Only sprites wear shaders; text objects do not. The
|
||||
node set is deliberately small — no channel split, no texture sampling at other coordinates, no
|
||||
time input; each is one node in `src/core/shader/nodes.ts` (a `ShaderNodeDef` with an `emit`) and
|
||||
one row in the table above.
|
||||
|
||||
## In the code
|
||||
|
||||
`src/core/shader/nodes.ts` is the catalogue, `src/core/shader/compile.ts` turns a graph into the
|
||||
fragment (every node reachable from the Output becomes one local per output, in dependency order;
|
||||
every Property a `uniform float u_<name>`), `src/viewport/shaders.ts` wraps the result in a filter
|
||||
and `src/viewport/ShaderLibrary.ts` caches it per asset. The rows come from
|
||||
`shaderParamDefs(graph)` in `src/core/properties/shaders.ts`; `schemaForAsset` and
|
||||
`schemaForObject` (`PropertySchema.ts`) append them to a sprite's schema, which is why both take
|
||||
the project.
|
||||
+1
@@ -7,6 +7,7 @@
|
||||
- [Textures](Textures)
|
||||
- [Animations](Animations)
|
||||
- [State machines](State-machines)
|
||||
- [Shaders](Shaders)
|
||||
- [Containers](Containers)
|
||||
- [Keyboard](Keyboard)
|
||||
|
||||
|
||||
Reference in new issue
Block a user