Crash BASIC Shader Guide
A beginner-friendly guide to creating custom visual effects using Crash BASIC's shader system.
What Are Shaders?
Shaders are small programs that run on your GPU (graphics card) and process every pixel of a sprite or layer. Think of them as Instagram filters for your game graphics - they can tint colors, create glows, add distortions, and much more.
Key concept: A shader runs once per pixel, and for each pixel, you decide what color it should be.
The Mental Model
Forget everything you know about procedural programming for a moment. Shaders work differently:
- No loops over pixels - The GPU runs your shader code simultaneously for every pixel (modern GPUs don't really do everything simultaneously, but for the right mental model, you can pretend that they do)
- No global state - Each pixel calculation is independent
- Think in coordinates - You get
UVcoordinates (0.0 to 1.0) telling you where this pixel is - Think in colors - You get
IMAGE(the original pixel color) and outputSET COLOR
For each pixel on screen (handled by GPU, not you):
UV = where this pixel is (0,0 = top-left, 1,1 = bottom-right)
IMAGE = what color this pixel currently is
... your shader code runs here ...
SET COLOR = the final color you wantBasic Shader Structure
SHADER "MyShader"
' Optional: declare parameters
USE NUMBER brightness DEFAULT 1.0
USE COLOR tint DEFAULT RGBA(1.0, 1.0, 1.0, 1.0)
USE IMAGE noiseTexture
WHEN DRAWING
' Your shader code here
' Must end with SET COLOR = ...
SET COLOR = IMAGE
END WHEN
END SHADERApplying Shaders
' Apply to a sprite
SET SPRITE 1 SHADER "MyShader"
SET SPRITE 1 SHADER "MyShader", @brightness=0.5 ' with parameter
' Apply to an entire layer
SET LAYER 0 SHADER "MyShader"
' Remove shader
SET SPRITE 1 SHADER NONEBuilt-in Variables
| Variable | Type | Description |
|---|---|---|
IMAGE |
vec4 (RGBA) | The original pixel color at this position (0.0-1.0 range) |
UV |
vec2 | Texture coordinates. UV.x = 0.0 (left) to 1.0 (right), UV.y = 0.0 (top) to 1.0 (bottom) |
TIME |
float | Seconds elapsed since program started (for animations) |
TICK |
float | Frames since shader was applied (60fps, for frame-based animations) |
SCREEN |
vec2 | Fragment position in screen pixels (like gl_FragCoord). Useful for screen-space effects |
SCREEN_SIZE |
vec2 | Screen dimensions in pixels (width, height). Use SCREEN / SCREEN_SIZE for normalized screen coords |
Understanding IMAGE
IMAGE contains the RGBA color of the current pixel:
IMAGE.r= red (0.0 to 1.0)IMAGE.g= green (0.0 to 1.0)IMAGE.b= blue (0.0 to 1.0)IMAGE.a= alpha/transparency (0.0 = invisible, 1.0 = solid)
Important: Colors are 0.0-1.0, NOT 0-255!
Understanding UV
UV tells you where this pixel is located:
UV.x = 0.0= left edgeUV.x = 1.0= right edgeUV.y = 0.0= top edgeUV.y = 1.0= bottom edgeUV.x = 0.5, UV.y = 0.5= center
Essential Functions
Color Creation
RGB(r, g, b) ' Create color with alpha=1.0
RGBA(r, g, b, a) ' Create color with custom alphaMath Functions
SIN(x), COS(x) ' Trigonometry (radians)
ABS(x) ' Absolute value
FLOOR(x) ' Round down
FRACT(x) ' Fractional part (x - floor(x))
POW(x, y) ' x raised to power y
SQRT(x) ' Square root
MIN(a, b), MAX(a, b) ' Minimum/maximum
CLAMP(x, min, max) ' Constrain x between min and maxInterpolation (Super Important!)
MIX(a, b, t) ' Blend between a and b
' t=0.0 returns a, t=1.0 returns b, t=0.5 returns halfway
STEP(edge, x) ' Returns 0.0 if x < edge, else 1.0
SMOOTHSTEP(a, b, x) ' Smooth transition from 0 to 1 as x goes from a to bPseudo-Random
RND(x, y) ' Pseudo-random 0-1 from two seed values
' Same inputs always produce same output (deterministic)
RND(seed) ' Pseudo-random 0-1 from single seed value
' Examples:
RND(UV.x, UV.y) ' Random per-pixel (static)
RND(cellX, cellY) ' Random per-grid-cell (static)
RND(UV.x, UV.y + TIME) ' Random per-pixel (animated/flickering)Vector Functions
VEC2(x, y) ' Create 2D vector
VEC3(x, y, z) ' Create 3D vector
VEC4(x, y, z, w) ' Create 4D vector
LENGTH(v) ' Vector magnitude
NORMALIZE(v) ' Unit vector (length = 1)
DOT(a, b) ' Dot productTexture Sampling
SAMPLE(textureName, uv) ' Sample a USE IMAGE texture at given UV
OWNTEXTURE(uv) ' Sample the sprite's own texture at different UV
' (useful for distortion effects)
PX(x, y) ' Convert pixel offset to UV offsetSwizzling (Rearranging Components)
color.r ' Just the red component
color.rgb ' RGB as vec3 (no alpha)
color.bgr ' Blue, green, red (swapped)
uv.yx ' Swap x and yImportant limitation: Swizzles only work on variables and built-ins (IMAGE, UV, etc.), NOT directly on function results:
' CORRECT - store in variable first, then swizzle
n = SAMPLE(noise, UV)
value = n.r
' WRONG - will not parse!
' value = SAMPLE(noise, UV).r
' CORRECT - parenthesized expressions work
offset = (UV * 2.0).x
' WRONG - function calls don't
' offset = VEC2(1.0, 2.0).xExample 1: Fade (Transparency Control)
The simplest shader - control how transparent something is.
SHADER "Fade"
USE NUMBER alpha DEFAULT 1.0
WHEN DRAWING
c = IMAGE
SET COLOR = RGBA(c.r, c.g, c.b, c.a * alpha)
END WHEN
END SHADER
' Usage:
SET SPRITE 1 SHADER "Fade", @alpha=0.5 ' 50% transparent
SET SPRITE 1 SHADER "Fade", @alpha=0.0 ' Invisible
SET SPRITE 1 SHADER "Fade", @alpha=1.0 ' Fully visibleHow it works:
IMAGEgives us the original pixel color- We multiply the alpha by our
alphaparameter - Lower alpha = more transparent
Animated Fade (Pulsing)
SHADER "Pulse"
USE NUMBER speed DEFAULT 2.0
WHEN DRAWING
c = IMAGE
' SIN oscillates between -1 and 1
' ABS makes it 0 to 1
' Result: smoothly pulses between invisible and visible
alpha = ABS(SIN(TIME * speed))
SET COLOR = RGBA(c.r, c.g, c.b, c.a * alpha)
END WHEN
END SHADERExample 2: Whiteout (Flash to White)
Makes a sprite flash white - perfect for damage effects.
SHADER "Whiteout"
USE NUMBER amount DEFAULT 0.0
WHEN DRAWING
c = IMAGE
white = RGBA(1.0, 1.0, 1.0, c.a)
SET COLOR = MIX(c, white, amount)
END WHEN
END SHADER
' Usage:
SET SPRITE 1 SHADER "Whiteout", @amount=0.0 ' Normal
SET SPRITE 1 SHADER "Whiteout", @amount=0.5 ' Half white
SET SPRITE 1 SHADER "Whiteout", @amount=1.0 ' Fully whiteHow it works:
MIX(a, b, t)blends between two colors- When
amount=0, you see the original image - When
amount=1, you see pure white - The alpha is preserved so transparent areas stay transparent
Auto-Flashing Whiteout
SHADER "DamageFlash"
USE NUMBER speed DEFAULT 10.0
WHEN DRAWING
c = IMAGE
white = RGBA(1.0, 1.0, 1.0, c.a)
flash = ABS(SIN(TIME * speed))
SET COLOR = MIX(c, white, flash)
END WHEN
END SHADERColored Flash (Red for Damage)
SHADER "RedFlash"
USE NUMBER amount DEFAULT 0.0
WHEN DRAWING
c = IMAGE
red = RGBA(1.0, 0.0, 0.0, c.a)
SET COLOR = MIX(c, red, amount)
END WHEN
END SHADERExample 3: Melt Effect (Doom-Style)
The melt effect shifts pixels downward at varying speeds, creating that classic Doom transition.
SHADER "Melt"
USE NUMBER progress DEFAULT 0.0
WHEN DRAWING
' Calculate how far this column should have "melted"
' Use UV.x to make each column melt at different rates
columnOffset = SIN(UV.x * 20.0) * 0.3 + 0.7
meltAmount = progress * columnOffset
' Sample from above the current position
sampleY = UV.y - meltAmount
' If we're sampling from above the image, show black
belowTop = STEP(0.0, sampleY)
' Sample the texture at the offset position
c = OWNTEXTURE(VEC2(UV.x, sampleY))
' Black if we've "melted past" this point
SET COLOR = RGBA(c.r * belowTop, c.g * belowTop, c.b * belowTop, c.a)
END WHEN
END SHADER
' Animate with BSHADER in a behavior:
BEHAVIOR "meltAway"
BSHADER "Melt" @progress=0.0 TO 1.5 DURATION 60
END BEHAVIORHow it works:
OWNTEXTURE(uv)lets us sample the sprite at different coordinates- We offset the Y coordinate based on
progress - Different columns melt at different speeds using
SIN(UV.x * 20.0) STEP(0.0, sampleY)creates a hard cutoff when we've scrolled past the top
Example 4: Random Purple Translucent Rectangles
This creates an overlay of random semi-transparent purple rectangles.
SHADER "PurpleRects"
USE NUMBER rectSize DEFAULT 0.1
USE NUMBER alpha DEFAULT 0.3
WHEN DRAWING
c = IMAGE
' Divide screen into grid cells
cellX = FLOOR(UV.x / rectSize)
cellY = FLOOR(UV.y / rectSize)
' Get pseudo-random value for this cell (0-1)
random = RND(cellX, cellY)
' Only show rectangle if random > 0.5 (about 50% of cells)
showRect = STEP(0.5, random)
' Purple color with variable alpha
purple = RGBA(0.6, 0.0, 0.8, alpha * showRect)
' Blend purple over original image
SET COLOR = MIX(c, purple, purple.a)
END WHEN
END SHADERHow it works:
FLOOR(UV / rectSize)divides the screen into grid cellsRND(cellX, cellY)returns a pseudo-random value 0-1 for each cell- Each cell gets a consistent random value (same cell = same random every frame)
- We use
STEP(0.5, random)to show rectangles in ~50% of cells MIXblends the purple overlay with the original image
Animated Random Rectangles
SHADER "AnimatedPurpleRects"
USE NUMBER rectSize DEFAULT 0.1
USE NUMBER alpha DEFAULT 0.3
USE NUMBER speed DEFAULT 1.0
WHEN DRAWING
c = IMAGE
cellX = FLOOR(UV.x / rectSize)
cellY = FLOOR(UV.y / rectSize)
seedOffset = FLOOR(TIME * speed)
random = RND(cellX, cellY + seedOffset)
showRect = STEP(0.5, random)
purple = RGBA(0.6, 0.0, 0.8, alpha * showRect)
SET COLOR = MIX(c, purple, purple.a)
END WHEN
END SHADERExample 5: Dissolve Effect
Pixels disappear based on a noise pattern - great for death animations.
SHADER "Dissolve"
USE NUMBER progress DEFAULT 0.0
USE IMAGE noise
WHEN DRAWING
c = IMAGE
' Sample noise texture to get random value for this pixel
n = SAMPLE(noise, UV)
' If noise value is less than progress, make pixel transparent
visible = STEP(progress, n.r)
SET COLOR = RGBA(c.r, c.g, c.b, c.a * visible)
END WHEN
END SHADER
' Usage requires a noise texture:
LOADIMG "noise.png", "noise"
SET SPRITE 1 SHADER "Dissolve", @progress=0.5Dissolve Without External Texture
If you don't have a noise image, use RND to generate pseudo-random noise:
SHADER "DissolveSimple"
USE NUMBER progress DEFAULT 0.0
WHEN DRAWING
c = IMAGE
' Generate noise from UV coordinates
noise = RND(UV.x, UV.y)
visible = STEP(progress, noise)
SET COLOR = RGBA(c.r, c.g, c.b, c.a * visible)
END WHEN
END SHADERExample 6: CRT Monitor Effect
Scanlines and slight color separation for a retro look.
SHADER "CRT"
USE NUMBER scanlineIntensity DEFAULT 0.3
USE NUMBER rgbSeparation DEFAULT 0.003
WHEN DRAWING
' Sample with slight RGB offset for chromatic aberration
rSample = OWNTEXTURE(VEC2(UV.x + rgbSeparation, UV.y))
gSample = OWNTEXTURE(UV)
bSample = OWNTEXTURE(VEC2(UV.x - rgbSeparation, UV.y))
r = rSample.r
g = gSample.g
b = bSample.b
a = IMAGE.a
' Create scanlines (dark every other line)
scanline = 1.0 - scanlineIntensity * STEP(0.5, FRACT(UV.y * 200.0))
SET COLOR = RGBA(r * scanline, g * scanline, b * scanline, a)
END WHEN
END SHADERUsing SCREEN Coordinates
SCREEN gives you the actual pixel position on screen (not within the sprite). This is useful for effects that should be consistent across multiple sprites or the whole screen.
Global Scanlines (Screen-Space)
Unlike the CRT example above which uses UV.y, this uses SCREEN.y so scanlines stay fixed on screen as sprites move:
SHADER "GlobalScanlines"
USE NUMBER intensity DEFAULT 0.3
WHEN DRAWING
c = IMAGE
' Scanlines based on screen position, not texture position
scanline = 1.0 - intensity * STEP(0.5, FRACT(SCREEN.y / 2.0))
SET COLOR = RGBA(c.r * scanline, c.g * scanline, c.b * scanline, c.a)
END WHEN
END SHADERScreen-Space Dither Pattern
Creates a dithering pattern that's consistent across all sprites:
SHADER "Dither"
USE NUMBER threshold DEFAULT 0.5
WHEN DRAWING
c = IMAGE
' 2x2 Bayer dither pattern based on screen position
px = FLOOR(SCREEN.x)
py = FLOOR(SCREEN.y)
pattern = FRACT((px + py * 2.0) / 4.0)
grey = (c.r + c.g + c.b) / 3.0
visible = STEP(pattern, grey + threshold - 0.5)
SET COLOR = RGBA(c.r * visible, c.g * visible, c.b * visible, c.a)
END WHEN
END SHADERNormalized Screen Position
To get 0-1 range for the whole screen (like UV but for screen space):
screenUV = SCREEN / SCREEN_SIZE ' 0,0 = top-left, 1,1 = bottom-rightCommon Patterns & Recipes
Distance from Center
center = VEC2(0.5, 0.5)
dist = LENGTH(UV - center) ' 0 at center, ~0.707 at cornersCircular Gradient
dist = LENGTH(UV - VEC2(0.5, 0.5))
gradient = 1.0 - CLAMP(dist * 2.0, 0.0, 1.0) ' 1 at center, 0 at edgesVignette (Darken Edges)
dist = LENGTH(UV - VEC2(0.5, 0.5))
vignette = 1.0 - SMOOTHSTEP(0.3, 0.7, dist) * 0.5
SET COLOR = RGBA(c.r * vignette, c.g * vignette, c.b * vignette, c.a)Greyscale
grey = (c.r + c.g + c.b) / 3.0
SET COLOR = RGBA(grey, grey, grey, c.a)Invert Colors
SET COLOR = RGBA(1.0 - c.r, 1.0 - c.g, 1.0 - c.b, c.a)Sepia Tone
grey = (c.r + c.g + c.b) / 3.0
r = MIN(grey * 1.2, 1.0)
g = grey
b = grey * 0.8
SET COLOR = RGBA(r, g, b, c.a)Wave Distortion
offset = SIN(UV.y * 20.0 + TIME * 5.0) * 0.02
c = OWNTEXTURE(VEC2(UV.x + offset, UV.y))
SET COLOR = cPixelation
pixels = 32.0
pixelUV = FLOOR(UV * pixels) / pixels
SET COLOR = OWNTEXTURE(pixelUV)Animating Shaders with BSHADER
Use BSHADER in behaviors to animate shader parameters:
SHADER "Dissolve"
USE NUMBER progress DEFAULT 0.0
WHEN DRAWING
c = IMAGE
noise = RND(UV.x, UV.y)
visible = STEP(progress, noise)
SET COLOR = RGBA(c.r, c.g, c.b, c.a * visible)
END WHEN
END SHADER
BEHAVIOR "deathDissolve"
BSHADER "Dissolve" @progress=0.0 TO 1.0 DURATION 60
END BEHAVIOR
' Apply to sprite when it dies:
SET SPRITE enemy BEHAVIOR "deathDissolve"Debugging Tips
- Start simple - Get basic colors working before adding complexity
- Visualize UV - Use
SET COLOR = RGBA(UV.x, UV.y, 0.0, 1.0)to see coordinates - Check ranges - Remember colors are 0.0-1.0, not 0-255
- Test incrementally - Change one thing at a time
- Use CLAMP - Prevent colors going outside 0.0-1.0 range
Debug Shader: See UV Coordinates
SHADER "DebugUV"
WHEN DRAWING
SET COLOR = RGBA(UV.x, UV.y, 0.0, 1.0)
END WHEN
END SHADERDebug Shader: See Alpha Channel
SHADER "DebugAlpha"
WHEN DRAWING
a = IMAGE.a
SET COLOR = RGBA(a, a, a, 1.0)
END WHEN
END SHADERPerformance Notes
- Shaders run on the GPU - they're very fast
- Avoid extremely complex math if not needed
OWNTEXTUREis slightly more expensive thanIMAGESAMPLEwith external textures adds texture lookup cost- Simple shaders (tint, fade, flash) have negligible performance impact
Quick Reference
| Want to... | Use... |
|---|---|
| Make transparent | Multiply c.a by a value 0.0-1.0 |
| Flash to color | MIX(c, targetColor, amount) |
| Animate over time | Use TIME or TICK |
| Distort image | OWNTEXTURE(modifiedUV) |
| Random per-pixel | RND(UV.x, UV.y) |
| Grid pattern | FLOOR(UV * gridSize) |
| Distance from point | LENGTH(UV - point) |
| Smooth transition | SMOOTHSTEP(start, end, value) |
| Hard cutoff | STEP(threshold, value) |
| Oscillate | SIN(TIME * speed) |