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

Time

A shader doesn’t remember anything from one frame to the next, so it can’t count frames itself. I count them for it, two ways.

TIME

TIME is how many seconds my renderer has been running, with a point: 2.5 is two and a half seconds. It’s the same for every shader and every pixel, and it’s real time, so a shader that moves by TIME moves at the same speed on a slow phone and on a fast PC.

SIN turns time into something that goes back and forth: SIN(TIME) goes from 0 up to 1, down to -1 and back again every 6.28 seconds. Multiply TIME to make it faster.

SCREEN 320, 240
SHADER "pulse"
    USE NUMBER speed DEFAULT 3.0
    WHEN DRAWING
        c = IMAGE
        glow = 0.5 + SIN(TIME * speed) * 0.5
        SET COLOR = MIX(c, RGBA(1.0, 1.0, 1.0, c.a), glow * 0.6)
    END WHEN
END SHADER

LOADIMG "hero.png", "hero"
ENABLE SPRITE 0
SET SPRITE 0 !! image("hero") x(140) y(100) scale((3, 3)) shader("pulse")
SHOW SPRITE 0
PAUSE

0.5 + SIN(...) * 0.5 moves the back and forth from -1 to 1 up into 0 to 1, which is the range most things want.

TIME with FRACT goes round instead: FRACT(TIME) climbs from 0 to 1 every second and starts again. Here it scrolls the picture forever:

SCREEN 320, 240
SHADER "conveyor"
    USE NUMBER speed DEFAULT 0.25
    WHEN DRAWING
        SET COLOR = OWNTEXTURE(VEC2(FRACT(UV.x + TIME * speed), UV.y))
    END WHEN
END SHADER

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

TICK

TICK counts frames, from 0, since this shader went on this layer or this sprite. Where TIME is the same everywhere, TICK is each one’s own: put the shader on a sprite now and its TICK starts at 0 now, whatever any other sprite’s is doing. That makes it the one for things that happen once, starting when the shader goes on:

SCREEN 320, 240
SHADER "appear"
    USE NUMBER frames DEFAULT 60
    WHEN DRAWING
        c = IMAGE
        shown = CLAMP(TICK / frames, 0.0, 1.0)
        SET COLOR = RGBA(c.r, c.g, c.b, c.a * shown)
    END WHEN
END SHADER

LOADIMG "hero.png", "hero"
ENABLE SPRITE 0
SET SPRITE 0 !! image("hero") x(140) y(100) scale((3, 3)) shader("appear")
SHOW SPRITE 0
PAUSE

The hero fades in over 60 frames, a second at sixty frames a second.

Crash-tan’s Tip: TICK counts the frames that are drawn, so on a machine that’s struggling it counts more slowly than sixty a second, and the fade takes longer. It always finishes, though. TIME is never late, but it’s the renderer’s clock, not the shader’s: a one-off driven by TIME starts wherever the clock happens to be.

A one-off from a setting

When a one-off has to keep to real time, have your program tell the shader how far along it is. Here my program counts the ticks, and the shader only draws what it’s told:

SCREEN 320, 240
SHADER "wipe"
    USE NUMBER done DEFAULT 0
    WHEN DRAWING
        c = IMAGE
        gone = STEP(UV.x, done)
        SET COLOR = MIX(c, RGBA(0.0, 0.0, 0.0, 1.0), gone)
    END WHEN
END SHADER

LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("wipe") shader_params({done: 0})
done = 0
DO
    IF COOLDOWN(1) AND done < 1
        done = done + 1 / 90
        SET LAYER 0 !! shader_params({done: done})
    END IF
LOOP

The city is wiped away from the left, in 90 ticks. The next chapter is all about settings like done.