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

Choices without IF

A shader has no IF, and no <, >, AND or OR either. It makes its choices with sums instead, and once you’ve seen how, you’ll find it’s a neat way to think.

STEP: a choice as a number

STEP(edge, x) is 0 when x is under edge and 1 when it isn’t. That 0 or 1 is the choice, and multiplying by it is acting on it: something times 0 is gone, and something times 1 is all there.

SCREEN 320, 240
SHADER "half"
    WHEN DRAWING
        c = IMAGE
        right = STEP(0.5, UV.x)
        g = (c.r + c.g + c.b) / 3.0
        grey = RGBA(g, g, g, c.a)
        SET COLOR = MIX(c, grey, right)
    END WHEN
END SHADER

LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("half")
PAUSE

right is 1 on the right half of the layer and 0 on the left, and MIX(c, grey, right) is c where it’s 0 and grey where it’s 1. The city is in color on the left and grey on the right.

Here’s how the things IF would say come out as sums, with each choice a 0 or a 1:

What you meanAs a sum
1 if x is edge or moreSTEP(edge, x)
1 if x is under edge1.0 - STEP(edge, x)
a if choice, otherwise bMIX(b, a, choice)
choice1 and choice2choice1 * choice2
choice1 or choice2MAX(choice1, choice2)
not choice1.0 - choice
1 if x is between low and highSTEP(low, x) * (1.0 - STEP(high, x))

That last one draws a band. Here’s a box, as one band across and one down:

SCREEN 320, 240
SHADER "box"
    WHEN DRAWING
        c = IMAGE
        across = STEP(0.25, UV.x) * (1.0 - STEP(0.75, UV.x))
        down = STEP(0.25, UV.y) * (1.0 - STEP(0.75, UV.y))
        inside = across * down
        dim = RGBA(c.r * 0.3, c.g * 0.3, c.b * 0.3, c.a)
        SET COLOR = MIX(dim, c, inside)
    END WHEN
END SHADER

LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("box")
PAUSE

Everything outside the box is dimmed to 30%.

Crash-tan’s Tip: This isn’t slower than an IF would be. A graphics chip runs a whole group of pixels in step, so when some of them would take an IF one way and some the other, it does both for all of them and keeps the answer that’s wanted. Sums like these are what it was going to do anyway.

SMOOTHSTEP: a soft edge

STEP makes a sharp edge, and a sharp edge on a screen has jagged steps along it. SMOOTHSTEP(low, high, x) makes a soft one: 0 up to low, 1 from high on, and an easy curve between. The gap between low and high is how soft the edge is.

SCREEN 320, 240
SHADER "vignette"
    USE NUMBER soft DEFAULT 0.3
    WHEN DRAWING
        c = IMAGE
        far = LENGTH(UV - VEC2(0.5, 0.5))
        dark = SMOOTHSTEP(0.7 - soft, 0.7, far)
        SET COLOR = MIX(c, RGBA(0.0, 0.0, 0.0, c.a), dark)
    END WHEN
END SHADER

LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("vignette")
PAUSE

The corners fade to black, the way an old camera’s do. Try soft at 0.01 for a hard edge, and at 0.6 for a gentle one.

Choosing between colors by brightness

A choice doesn’t have to be about where the pixel is. Here’s one about what it is: every pixel becomes one of three colors, by how bright it is.

SCREEN 320, 240
SHADER "poster"
    WHEN DRAWING
        c = IMAGE
        g = c.r * 0.3 + c.g * 0.59 + c.b * 0.11
        mid = STEP(0.15, g)
        high = STEP(0.35, g)
        dark = RGB(0.1, 0.05, 0.2)
        middle = RGB(0.9, 0.3, 0.4)
        light = RGB(1.0, 0.9, 0.5)
        SET COLOR = MIX(MIX(dark, middle, mid), light, high)
    END WHEN
END SHADER

LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("poster")
PAUSE

The inner MIX picks dark or middle; the outer one replaces that with light when the pixel’s bright enough. Two choices, nested: the same as an IF … ELSEIF … ELSE.