BambooBasic Logo


Custom Shaders in BambooBasic

BambooBasic supports custom GLSL shaders for advanced visual effects. Shaders are compiled to SPIR-V when you load them and can be applied to individual 2D images or to text.

Note for this edition. The Linux edition renders with Vulkan, which consumes SPIR-V, so shaders are written in GLSL rather than the HLSL used by the Windows edition. The API is identical — the same function names, the same 64-slot parameter system — but a shader file is not portable between the two editions without translating it. In practice that is mostly a syntax change: float4 becomes vec4, lerp becomes mix, and tex.Sample(s, uv) becomes texture(tex, uv).

The compiler is built into the runtime, so there is nothing to install and no shader tools to ship with your game.



Shader Types
Type Purpose Apply Function Example Effects Status
Image Shader Per-draw 2D image effects b2dDrawImageEx(), b2dDrawAnimImageEx() Glow, blur, colour tint, distortion Available
Text Shader Per-draw text effects b2dDrawTextEx() Glow, rainbow colours, sheen animation Available
Screen Shader Full-screen post-processing b2dApplyScreenShader() CRT effect, bloom, vignette, colour grading Available

Entity shaders (custom materials on 3D meshes) are not listed because the 3D scene system is not part of the Linux edition.



GLSL Shader Structure

You write only the fragment stage. The vertex stage belongs to the runtime, which is what guarantees a shader cannot move your sprite around — it can only decide what colour each pixel comes out.

You also do not write the boilerplate. The runtime prepends a preamble declaring the texture, the parameters and the inputs, so a shader file is just its main() and any helper functions:

// Everything below is declared FOR you - do not redeclare it:
//
//   imageTexture   the image being drawn
//   params[64]     your parameters, also reachable as param0 .. param63
//   texCoord       UV coordinates, 0..1
//   tint           the current draw colour (b2dSetColor / b2dSetAlpha)
//   alpha          shorthand for tint.a
//   fragColour     the output (fragColor also works)

void main()
{
    vec4 colour = texture(imageTexture, texCoord);
    colour.a *= alpha;
    fragColour = colour;
}

The entry point must be main. A #version line of your own is harmless — it is stripped, since the preamble supplies one.

Error messages use your line numbers. The preamble is followed by a #line 1 directive, so if the compiler reports an error on line 12, that is line 12 of your file, not line 12 of the generated source.



Image Shaders (2D)

Image shaders are applied to individual images using b2dDrawImageEx() or b2dDrawAnimImageEx(). Passing shader 0 means "no shader", so the same call does both jobs.

Available Resources:

Name Type Description
imageTexture sampler2D The image being drawn
texCoord vec2 UV coordinates (0-1). For an animation frame these span that frame's cell, not the whole sheet.
tint vec4 The current draw colour and alpha
alpha float Shorthand for tint.a
params[64] float[] Your parameters, also named param0-param63

Example: Image Glow Shader

// ImageGlow.glsl
//   param0 = glow intensity (0.0 - 2.0)
//   param1 = brightness threshold (0.0 - 1.0)
//   param2 = blur radius, in texels
//   param3 = time, for animation

void main()
{
    float intensity = param0;
    float threshold = param1;
    float radius    = param2;
    float time      = param3;

    vec4 colour = texture(imageTexture, texCoord);

    // Brighten the whole sprite.
    colour.rgb *= (1.0 + intensity);

    // Bloom: average the neighbours brighter than the threshold.
    vec2 texel = radius / vec2(textureSize(imageTexture, 0));
    vec4 bloom = vec4(0.0);
    float taken = 0.0;

    for (int y = -2; y <= 2; y++) {
        for (int x = -2; x <= 2; x++) {
            vec4 s = texture(imageTexture, texCoord + vec2(x, y) * texel);
            if ((s.r + s.g + s.b) / 3.0 > threshold) {
                bloom += s;
                taken += 1.0;
            }
        }
    }
    if (taken > 0.0) {
        bloom /= taken;
        float pulse = 0.5 + 0.5 * sin(time * 3.0);
        colour.rgb += bloom.rgb * intensity * pulse * 0.6;
    }

    colour *= tint;
    fragColour = colour;
}

Usage in BambooBasic:

; Load and configure an image shader
glowShader = b2dLoadImageShader("ImageGlow.glsl")
If glowShader = 0 Then
    Print "Shader failed to compile - see the console for the reason"
EndIf

b2dSetImageShaderFloat(glowShader, "0", 1.5)   ; param0 = intensity

; Draw the image through the shader
b2dDrawImageEx(myImage, 100, 100, glowShader)

; ...or without it
b2dDrawImageEx(myImage, 100, 100, 0)

b2dFreeImageShader(glowShader)


Text Shaders (2D)

Text shaders are applied with b2dDrawTextEx() and operate on the font atlas texture.

The important thing to understand is that texCoord is the glyph's position within the atlas, not its position on screen. Using it directly makes an effect follow the shape of each letter, which is usually what you want.

Font atlases are white with the glyph shape carried entirely in the alpha channel, so the colour you output is yours to choose — only .a carries information about the letterform.

// RainbowText.glsl
//   param0 = time
//   param1 = sweep speed

void main()
{
    float time  = param0;
    float speed = param1;

    vec4 glyph = texture(imageTexture, texCoord);

    float hue = fract(texCoord.x * 2.0 + texCoord.y + time * speed);
    vec3 rainbow = clamp(abs(mod(hue * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);

    fragColour = vec4(rainbow, glyph.a * alpha);
}
; Load and use a text shader
rainbow = b2dLoadTextShader("RainbowText.glsl")
b2dSetTextShaderFloat(rainbow, "0", elapsedTime)
b2dSetTextShaderFloat(rainbow, "1", 0.4)

b2dDrawTextEx("Shaded text", 40, 70, 0, font, rainbow)

b2dFreeTextShader(rainbow)


Screen Shaders (Post-Process)

A screen shader post-processes the whole finished frame rather than one image. The runtime draws everything into an offscreen target, then composites it into the window through your shader as a single fullscreen quad.

These are b2d calls on every target. They used to be named b3d, from when they sat beside the 3D entity shaders — they never had anything to do with a 3D scene, and the b3d names are gone.

Available Resources: the same as an image shader, except the texture is the rendered frame. It is called screenTexture, though imageTexture also works — they are the same sampler, so one preamble serves both kinds.

; Load, configure, apply
crt = b2dLoadScreenShader("CRT.glsl")

b2dSetShaderFloat(crt, "0", 0.5)     ; scanline intensity
b2dSetShaderFloat(crt, "1", 200.0)   ; scanline count
b2dSetShaderFloat(crt, "2", 0.5)     ; vignette strength
b2dSetShaderFloat(crt, "6", 1.1)     ; brightness

b2dApplyScreenShader(crt)            ; on - affects every frame from now on

; ...and off again
b2dClearScreenShader()

b2dFreeScreenShader(crt)
FunctionDescription
b2dLoadScreenShader(filename$)Compile a GLSL screen shader. Returns a handle, or 0 on failure.
b2dApplyScreenShader(shader)Make it the active effect. It applies to every frame until cleared.
b2dClearScreenShader()Turn the effect off; the frame is presented untouched.
b2dSetDefaultScreenShader()Same as clearing — there is no built-in default effect in this edition.
b2dFreeScreenShader(shader)Release it. If it was active it is cleared first.
b2dSetShaderFloat/Int/Float2/Float3/Float4Set parameters, exactly as for image and text shaders.

A trap worth knowing. Setting the scanline count to the window height gives you one cycle per pixel, which aliases into flat grey — it looks like the effect is not working. Use a number well below the height; 200 bands over a 600-pixel window is what actually reads as a CRT.

There is a cost: with a screen shader active the frame is rendered to an offscreen target and then drawn again, so it is one extra fullscreen pass. Clear it when you are not using it.



Parameter System

Every shader has 64 float parameters that can be set from BambooBasic code.

Reading them in GLSL: use either the array or the numbered names — they are the same storage.

float intensity = params[0];   // array form
float intensity = param0;      // named form - identical

Both work, so an HLSL shader being ported over can keep its param0 style names unchanged.

Setting them from BambooBasic. The parameter "name" is the slot index written as a string"0" is param0, "7" is param7:

Function Description
b2dSetImageShaderFloat(shader, "index", value) Set a single float
b2dSetImageShaderFloat2(shader, "index", x, y) Set two consecutive slots
b2dSetImageShaderFloat3(shader, "index", x, y, z) Set three consecutive slots (RGB)
b2dSetImageShaderFloat4(shader, "index", x, y, z, w) Set four consecutive slots (RGBA)
b2dSetImageShaderInt(shader, "index", value) Set an integer (see below)

The same set exists for text shaders as b2dSetTextShader*.

Integers are stored bit-for-bit in a float slot, so read them back with floatBitsToInt():

int mode = floatBitsToInt(param5);

Example:

b2dSetImageShaderFloat(shader, "0", 1.5)          ; param0 = 1.5
b2dSetImageShaderFloat(shader, "1", 0.8)          ; param1 = 0.8
b2dSetImageShaderFloat2(shader, "2", 10.0, 20.0)  ; param2 = 10.0, param3 = 20.0

Parameters are read when the frame is drawn, not at the moment you set them. If you set a parameter twice in one frame, the last value wins for every draw that frame.



Loading and Compiling

Shaders are compiled from GLSL source when you load them. There is no pre-compiled format and no separate compiler tool — the compiler is linked into the runtime itself, so a shipped game needs nothing extra installed:

shader = b2dLoadImageShader("MyShader.glsl")
If shader = 0 Then
    Print "Failed to compile shader - the reason is on the console"
EndIf

Compilation happens once, at load. Loading a shader every frame would recompile it every frame, so load them during setup.

Compilation errors are printed to the console with the file name and your line numbers. Common causes:

Tip: run your program from a terminal while developing shaders, so you can see the compiler's messages.



Tips and Best Practices

Performance:

Debugging:

Porting a shader from the Windows edition:

HLSLGLSL
float2 / float3 / float4vec2 / vec3 / vec4
lerp(a, b, t)mix(a, b, t)
saturate(x)clamp(x, 0.0, 1.0)
frac(x)fract(x)
tex.Sample(samp, uv)texture(tex, uv)
asint(x)floatBitsToInt(x)
input.texCoordtexCoord
return colour;fragColour = colour;

The parameter names (param0 and friends) carry across unchanged, and you can delete the whole resource-declaration block — the preamble supplies it.



Examples

A complete working example lives in:

Example shaders included:


Back to Documentation Index