Numbers and vectors
Everything in a shader is a number, or a few numbers kept together. A few
numbers together are a vector: a place like UV is two, a color is four.
| Made by | How many | Its parts |
|---|---|---|
a number, like 0.5 | 1 | |
VEC2(x, y), UV, SCREEN, SCREEN_SIZE, PX(x, y) | 2 | .x .y |
VEC3(x, y, z), a swizzle of three | 3 | .x .y .z |
VEC4(x, y, z, w), RGBA(...), RGB(...), IMAGE, OWNTEXTURE(...), SAMPLE(...) | 4 | .x .y .z .w |
The parts of any vector can be called either way: .r .g .b .a are the same
parts as .x .y .z .w, so UV.r is UV.x. A color reads better with
.r, and a place with .x.
Every number is a number with a point: 1 and 1.0 are the same, and
7 / 2 is 3.5.
Sums
+, -, * and / work as they do in Crash BASIC: * and / before +
and -, left to right otherwise, and brackets first. A minus in front of
something turns it around.
Between two vectors of the same size, a sum works part by part. Between a
vector and a number, the number goes into every part: UV * 2.0 is
VEC2(UV.x * 2.0, UV.y * 2.0). Two vectors of different sizes can’t be added
up, and that’s one of the ways a shader goes wrong:
SCREEN 320, 240
SHADER "mismatch"
WHEN DRAWING
SET COLOR = IMAGE + UV
END WHEN
END SHADER
SET LAYER 0 !! shader("mismatch")
Functions
These work on a number, or on every part of a vector at once:
| Function | Gives |
|---|---|
ABS(x) | x without its minus |
FLOOR(x) | x with what comes after the point dropped, downwards: FLOOR(-0.5) is -1 |
FRACT(x) | only what comes after the point: x - FLOOR(x) |
SIN(x), COS(x) | sine and cosine, with x in radians |
SQRT(x) | the square root |
POW(x, y) | x to the power y |
MIN(a, b), MAX(a, b) | the smaller, the bigger |
CLAMP(x, low, high) | x, kept between low and high |
MIX(a, b, t) | the way from a to b: a at 0, b at 1 |
STEP(edge, x) | 0 if x is under edge, 1 if it’s not |
SMOOTHSTEP(low, high, x) | 0 under low, 1 over high, and a smooth curve between |
When a function takes more than one value, they all have to be the same
size: two numbers, or two colors, but not a color and a number. MIN(c, 0.5) won’t do; MIN(c, RGBA(0.5, 0.5, 0.5, 0.5)) will. The one exception is
MIX, whose last value, how far along, can be a number whatever the first
two are.
SCREEN 320, 240
SHADER "capped"
WHEN DRAWING
SET COLOR = MIN(IMAGE, 0.5)
END WHEN
END SHADER
SET LAYER 0 !! shader("capped")
And these take vectors and give a number back:
| Function | Gives |
|---|---|
LENGTH(v) | how long v is: for a place, how far it is from (0, 0) |
DOT(a, b) | the parts multiplied in pairs and added up |
NORMALIZE(v) | v made 1 long: only its direction left. Not a number: a vector the same size |
LENGTH is the one you’ll use most, for how far a pixel is from somewhere:
SCREEN 320, 240
SHADER "spotlight"
USE NUMBER size DEFAULT 0.35
WHEN DRAWING
c = IMAGE
far = LENGTH(UV - VEC2(0.5, 0.5))
light = 1.0 - CLAMP(far / size, 0.0, 1.0)
SET COLOR = RGBA(c.r * light, c.g * light, c.b * light, c.a)
END WHEN
END SHADER
LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("spotlight")
PAUSE
UV - VEC2(0.5, 0.5) is the way from the middle to the pixel, and its length
is how far that is. Divided by size, it’s 1 at the edge of the light, and
1.0 - CLAMP(...) turns that into light: 1 in the middle, 0 at the edge and
beyond.
Crash-tan’s Tip: The layer is wider than it is tall, and
UVdoesn’t know: it goes 0 to 1 both ways. So the “circle” above is an oval, as wide as the layer is. To make it round, stretchUV.xby how much wider the screen is than tall:VEC2((UV.x - 0.5) * SCREEN_SIZE.x / SCREEN_SIZE.y, UV.y - 0.5).
A swizzle on a sum
A swizzle goes on a name, on one of the language’s own words like UV, or on
something in brackets: c.r, UV.x, (UV * 2.0).x. It can’t go straight on
a function, so OWNTEXTURE(UV).r won’t do. Put the color in a variable first:
c = OWNTEXTURE(UV)
red = c.r