Shaders: Padding and the Edge mode Transparent give room around the picture (#69)
1 parent
d554dfddbf
commit
6e7ecb0260
2 files changed
+53
-6
No files matched your search
+1
-1
@@ -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 # 1680 editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1687 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
|
||||
|
||||
+52
-5
@@ -79,7 +79,7 @@ plugged into a colour port, straight from a `Number` or a `Luminance` as well.
|
||||
| **Sample texture** | in `u`, `v` (number) · out `color`, `alpha` | The picture at another spot, tinted like Texture color. Unconnected, `u` and `v` are this pixel's own. Past the border the picture continues as the Output's *Edge* says. |
|
||||
| **Texture size** | out `width`, `height` | The picture in pixels: of the frame, when the sprite wears one. |
|
||||
| **Time** | out `value` | Seconds since the editor started; see *Coordinates and time* below. |
|
||||
| **Output** | in `color`, `alpha` (number) | What the sprite is drawn with: the colour, clamped to 0-1, and the opacity, clamped too. Each input left unconnected stays the texture's. One per graph. Its *Edge* setting says how the picture continues past its border when sampled outside it: *Clamp* (the default) holds the border pixel, *Mirror* turns the picture back on itself, *Repeat* starts it over for tiles. |
|
||||
| **Output** | in `color`, `alpha` (number) | What the sprite is drawn with: the colour, clamped to 0-1, and the opacity, clamped too. Each input left unconnected stays the texture's. One per graph. Its *Edge* setting says how the picture continues past its border when sampled outside it: *Clamp* (the default) holds the border pixel, *Mirror* turns the picture back on itself, *Repeat* starts it over for tiles, *Transparent* leaves everything outside empty. Its *Padding* is room around the picture to draw into, in pixels of the picture per side (see *Room around the picture*). |
|
||||
| **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, and not one of the rows a sprite has anyway — `enabled`, `scale`, `rotation`, `mirrorX`, `mirrorY`, `width`, `height`, `color`, `texture`, `shader`. Both rows would sit on the same key and the uniform would read the sprite's width instead of the declared default, so the node says *"width" is a sprite property* and nothing compiles. |
|
||||
| **Color** | out `color` | A constant colour, picked in the inspector. |
|
||||
| **Number** | out `value` | A constant number, typed on the node. Handy where the same value feeds more than one input, or where an input has nothing to type on (Luminance, the Output's alpha). |
|
||||
@@ -153,6 +153,45 @@ enough to separate the two remaining modes — with nothing set on the Output th
|
||||
0.2 s and 0.7 s a `Fract` into a `Step` gives red and then blue; released, the pixel of a
|
||||
`Fract(Time)` mix changes between two reads with nothing running.
|
||||
|
||||
## Room around the picture
|
||||
|
||||
A shader draws where its wearer's rectangle is and nowhere else, so an outline, a glow or a shadow
|
||||
is cut off at the border. *Padding* on the Output node gives it room: that many pixels of the
|
||||
picture on every side. The picture stays where it is and as big as it is; `UV` simply runs on past
|
||||
0 and 1 in the margin, 8 pixels of padding on a 32-pixel picture from -0.25 to 1.25.
|
||||
|
||||
What the margin reads is the Output's *Edge*, for `Texture color`, `Texture alpha` and unconnected
|
||||
inputs just as for a `Sample texture`. For anything that should stand free around the picture that
|
||||
is *Transparent*: outside the picture there is nothing, and only what the graph puts there shows.
|
||||
With *Clamp* the border pixel goes on through the margin, which is rarely meant, so the Output says
|
||||
"Padding with Clamp smears the border" while it is set that way. The shader still compiles.
|
||||
|
||||
The simplest case: a `Sample texture` reading at `UV.u` minus 0.25, its `color` and its `alpha`
|
||||
both into the Output, shows the picture a quarter further right. Without padding it ends at the
|
||||
sprite's right border; with Padding 8 on a 32-pixel picture and Edge *Transparent* all of it is
|
||||
there, and left of it the sprite is empty. Take the `alpha` from the sample as well: the pixel's
|
||||
own is 0 everywhere in the margin.
|
||||
|
||||
The pixel it counts in is the wearer's own:
|
||||
|
||||
- **Sprite with a picture:** a pixel of the frame, however big the sprite draws it.
|
||||
- **Sprite without one:** a unit of its size.
|
||||
- **Nine-sliced sprite:** a border pixel, drawn at the texture's nine-slice scale. The margin is a
|
||||
ring around the nine parts; the parts themselves do not move.
|
||||
- **Text:** a unit of the text, whatever resolution the zoom had it rastered at.
|
||||
- **Particles:** a pixel of the particle picture, around every particle; without a picture a
|
||||
particle's size counts as that many pixels.
|
||||
|
||||
The margin is drawn only. Clicking, the selection frame, the size rows and colliders keep to the
|
||||
rectangle without it. The preview in the Shader tab leaves room for the margin and shows it.
|
||||
|
||||
Measured in the checks (steps 28, 30, 31 and 37): with Padding 8 and *Transparent* on a 32-pixel
|
||||
picture the picture keeps its place and the margin stays the background; the picture sampled a
|
||||
quarter to the right ends at the rectangle without padding and is drawn past it with; with *Clamp*
|
||||
the border colour fills the margin and the Output carries the hint; a text's filled quad reaches 6
|
||||
units past its rectangle and no further; particles grow about their centres; a sliced sprite gets
|
||||
a ring of 4 border pixels and keeps its 8-pixel border.
|
||||
|
||||
## Wearing a shader
|
||||
|
||||
The sprite's `Shader` row is a picker over the project's shader assets (`None` is no shader: the
|
||||
@@ -205,14 +244,15 @@ filter pass per sprite (up to #77) the same counts measured 56 / 54 / 48 / 34 /
|
||||
texture itself, so pixel art stays sharp: measured in play at a fractional camera scale (1.43
|
||||
screen pixels per world unit), a *Nearest* texture keeps its hard border pixel for pixel with a
|
||||
neutral shader on, and brightness 0.5 darkens every one of those pixels — the shader draws there,
|
||||
and it does not resample. Only sprites wear shaders; text objects do not. The node set is still
|
||||
and it does not resample. The node set is still
|
||||
small — no channel split, no colour-typed property, no second picture; each is one node in
|
||||
`src/core/shader/nodes.ts` (a `ShaderNodeDef` with an `emit`) and one row in the table above. A
|
||||
picture sampled past its border shows what Edge says, never the sprite's neighbour on the sheet: a
|
||||
frame is a texture of its own. Without a shader the question never comes up — the quad's own
|
||||
coordinates run 0 to 1 and never leave the picture — which is why the default is the mode that
|
||||
matches what you see there: *Clamp*. What no shader can do is draw outside the sprite's rectangle: the
|
||||
quad has four corners and stays a quad (#79 is the vertex-side step that would change that).
|
||||
matches what you see there: *Clamp*. Outside the sprite's rectangle a shader draws only as far as
|
||||
its Padding reaches, and what it draws with is still a rectangle: the quad has four corners and
|
||||
stays a quad (#79 is the vertex-side step that would change that).
|
||||
|
||||
## In the code
|
||||
|
||||
@@ -241,7 +281,14 @@ GLSL ES 3.00 (`GlProgram` looks for it in the fragment); without it `texture()`
|
||||
that address mode (`TextureLibrary.get(ref, edge)`), because WebGL takes the sampler state from the
|
||||
texture source, not from a style the shader hands over. That cut is only made where it can show:
|
||||
a graph whose `Sample texture` reads nothing but its own pixel draws with the plain sheet, whatever
|
||||
the Edge says, so the common shader costs no second copy of the picture. The sheet itself carries
|
||||
the Edge says, so the common shader costs no second copy of the picture. *Transparent* needs none
|
||||
either: no sampler state makes the outside empty, so the fragment cuts it itself (every read of the
|
||||
picture goes through `texelAt`, which is the plain `texture()` in the other modes). Padding is
|
||||
geometry (`src/viewport/quadPadding.ts`): the quad's corners and its UVs grow by the same fraction,
|
||||
a sliced sprite gets a ring of spans around its parts (`paddedSpans` in `nineSlice.ts`), and for
|
||||
particles, whose quads Pixi builds, the vertex stage does the same with a `uPad` uniform. A graph
|
||||
with padding takes the Edge's cut of the picture even without a shifted sample, because every
|
||||
pixel of the margin reads past the border. The sheet itself carries
|
||||
`mirror-repeat` (`SHEET_EDGE`) and cannot carry `clamp-to-edge`: Pixi rewrites that to `repeat` on
|
||||
every texture a Graphics fill draws with. Time is `ShaderLibrary.now()`, held still
|
||||
by `__gm.shaderTime(seconds)` for the checks; a view that drew a Time shader redraws on every
|
||||
|
||||
Reference in new issue
Block a user