Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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, UV runs 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, FRACT keeps 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.