Where the pixel is
A shader runs for one pixel at a time, and the first thing it usually wants to know is which one.
UV
UV is where the pixel is on what the shader is drawing: (0, 0) at its top
left, (1, 1) at its bottom right, and (0.5, 0.5) in the middle. UV.x is
how far across, UV.y how far down. On a layer that’s across the layer; on a
sprite it’s across the sprite, however big it is on screen.
SCREEN 320, 240
SHADER "stripes"
WHEN DRAWING
c = IMAGE
band = FLOOR(UV.y * 8.0) / 8.0
SET COLOR = RGBA(c.r + band * 0.5, c.g, c.b, c.a)
END WHEN
END SHADER
LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("stripes")
PAUSE
UV.y * 8.0 runs from 0 at the top to 8 at the bottom, and FLOOR drops what
comes after the point, so band steps from 0 to 7/8 in eight bands, getting
redder on the way down.
The letters UV are just what graphics people call these, after the U and
V axes of a picture, to tell them apart from x and y on the screen.
Crash-tan’s Tip: On an animated sprite,
UVruns from 0 to 1 across the frame it’s showing, not across the whole sheet. A shader means the same on frame 7 as on frame 0.
Reading the picture somewhere else
IMAGE is the color right under the pixel. OWNTEXTURE(uv) is the color
anywhere else on the same picture: give it a place, as a UV, and it reads
the picture there. That’s how a shader moves things around, because a pixel
that shows what’s to its left makes the picture look moved to the right.
SCREEN 320, 240
SHADER "mirror"
WHEN DRAWING
SET COLOR = OWNTEXTURE(VEC2(1.0 - UV.x, UV.y))
END WHEN
END SHADER
LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("mirror")
PAUSE
VEC2(x, y) makes a place out of two numbers, and 1.0 - UV.x is the same
distance from the right as the pixel is from the left, so the layer comes out
back to front.
A place doesn’t have to be made out of parts. UV is one already, and sums
work on a whole place at once: UV * 2.0 doubles both its parts, and
UV + VEC2(0.1, 0.0) is a little to the right.
SCREEN 320, 240
SHADER "zoom"
USE NUMBER zoom DEFAULT 2.0
WHEN DRAWING
middle = VEC2(0.5, 0.5)
SET COLOR = OWNTEXTURE((UV - middle) / zoom + middle)
END WHEN
END SHADER
LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("zoom")
PAUSE
Each pixel reads from half as far from the middle as it is, so the middle of the layer is blown up to twice the size.
Crash-tan’s Tip: Reading outside the picture, below 0 or above 1, gets you the nearest pixel on its edge, stretched out. If you’d rather it wrapped around,
FRACTkeeps only what comes after the point: `OWNTEXTURE(FRACT(UV
- 3.0))` tiles the picture three times each way.
Pixels
UV is in fractions of the picture, and sometimes you want pixels: one pixel
to the left, say, however big the picture is. PX(x, y) is how far x
pixels across and y pixels down is, as a UV:
SCREEN 320, 240
SHADER "emboss"
WHEN DRAWING
c = IMAGE
before = OWNTEXTURE(UV - PX(1.0, 1.0))
d = (c.r + c.g + c.b) - (before.r + before.g + before.b)
g = 0.5 + d
SET COLOR = RGBA(g, g, g, 1.0)
END WHEN
END SHADER
LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("emboss")
PAUSE
Each pixel compares itself with the one up and to its left. Where they’re the same, it’s grey; where it’s brighter than its neighbour, lighter; where it’s darker, darker. The city comes out pressed into grey metal.
The pixels PX counts are the picture’s own: the layer’s on a layer, and the
sprite’s frame on a sprite, before it’s scaled. So a one-pixel outline is one
of the sprite’s pixels thick, even on a sprite drawn four times its size.
The screen
SCREEN is where the pixel is on the screen, in the game’s pixels, from
(0, 0) at the top left. It’s for effects that belong to the screen rather
than to what’s drawn: lines that stay where they are while a sprite moves
through them.
SCREEN 320, 240
SHADER "lines"
WHEN DRAWING
c = IMAGE
dark = STEP(0.5, FRACT(SCREEN.y / 4.0)) * 0.4
SET COLOR = RGBA(c.r * (1.0 - dark), c.g * (1.0 - dark), c.b * (1.0 - dark), c.a)
END WHEN
END SHADER
LOADIMG "hero.png", "hero"
ENABLE SPRITE 0
SET SPRITE 0 !! image("hero") x(140) y(100) scale((4, 4)) shader("lines")
!! velocity((0.5, 0))
SHOW SPRITE 0
PAUSE
The hero walks through stripes that stay put on the screen, two of the game’s pixels light and two dark.
SCREEN_SIZE is the size SCREEN gave the game: 320 by 240 here. SCREEN
counts the same pixels, so SCREEN / SCREEN_SIZE goes from 0 at the top left
to 1 at the bottom right of the game, however big the window is, and the
black bars of a letterbox aren’t part of it.
SCREEN 320, 240
SHADER "dusk"
WHEN DRAWING
c = IMAGE
down = SCREEN.y / SCREEN_SIZE.y
SET COLOR = MIX(c, RGB(0.9, 0.4, 0.1), down * 0.6)
END WHEN
END SHADER
LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("dusk")
PAUSE
The city goes orange towards the bottom of the screen, the same in a small window as in a big one.