Skip to content

Examples

Complete, working examples of AXSL shaders.

Basic Textured Mesh

A simple vertex + fragment shader that renders a textured mesh.

axsl
@version 450;

// Shared interface between vertex and fragment
interface Varyings {
    vec2 v_uv;
    vec3 v_normal;
};

@stage vertex {
    // Input attributes
    in Attributes {
        @location(0) vec3 a_pos;
        @location(1) vec2 a_uv;
        @location(2) vec3 a_normal;
    } a;

    // Output to fragment
    out Varyings v;

    // Uniforms
    @binding(0) uniform mat4 u_mvp;
    @binding(1) uniform mat4 u_model;

    void main() {
        v.v_uv = a.a_uv;
        v.v_normal = mat3(u_model) * a.a_normal;
        gl_Position = u_mvp * vec4(a.a_pos, 1.0);
    }
}

@stage fragment {
    // Input from vertex
    in Varyings v;

    // Output
    out vec4 o_color;

    // Uniforms
    @binding(2) uniform sampler2D u_texture;

    void main() {
        vec3 normal = normalize(v.v_normal);
        vec4 tex_color = texture(u_texture, v.v_uv);
        o_color = tex_color;
    }
}

PBR Material Shader

A more complex shader with global structs and functions.

axsl
@version 450;

// Global material struct
struct Material {
    vec3 albedo;
    float roughness;
    float metallic;
    float ao;
};

// Global constants
const float PI = 3.14159265359;

// Global utility functions
vec3 gamma_correct(vec3 color) {
    return pow(color, vec3(1.0 / 2.2));
}

float distribution_ggx(vec3 N, vec3 H, float roughness) {
    float a = roughness * roughness;
    float a2 = a * a;
    float NdotH = max(dot(N, H), 0.0);
    float NdotH2 = NdotH * NdotH;

    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
    denom = PI * denom * denom;

    return a2 / denom;
}

// Shared interface
interface Varyings {
    vec2 v_uv;
    vec3 v_normal;
    vec3 v_world_pos;
};

@stage vertex {
    in Attributes {
        @location(0) vec3 a_pos;
        @location(1) vec2 a_uv;
        @location(2) vec3 a_normal;
    } a;

    out Varyings v;

    @binding(0) uniform mat4 u_mvp;
    @binding(1) uniform mat4 u_model;

    void main() {
        v.v_uv = a.a_uv;
        v.v_normal = mat3(u_model) * a.a_normal;
        vec4 world_pos = u_model * vec4(a.a_pos, 1.0);
        v.v_world_pos = world_pos.xyz;
        gl_Position = u_mvp * world_pos;
    }
}

@stage fragment {
    in Varyings v;

    out vec4 o_color;

    // Material uniform
    @std140 @binding(2) uniform MaterialBlock {
        Material material;
    } u_mat;

    // Textures
    @binding(3) uniform sampler2D u_albedo_map;
    @binding(4) uniform sampler2D u_normal_map;

    // Lighting
    @binding(5) uniform vec3 u_light_pos;
    @binding(6) uniform vec3 u_light_color;
    @binding(7) uniform vec3 u_camera_pos;

    void main() {
        vec3 albedo = texture(u_albedo_map, v.v_uv).rgb * u_mat.material.albedo;
        vec3 normal = normalize(v.v_normal);

        vec3 N = normal;
        vec3 V = normalize(u_camera_pos - v.v_world_pos);
        vec3 L = normalize(u_light_pos - v.v_world_pos);
        vec3 H = normalize(V + L);

        float NDF = distribution_ggx(N, H, u_mat.material.roughness);
        float NdotL = max(dot(N, L), 0.0);

        vec3 radiance = u_light_color;
        vec3 Lo = albedo * radiance * NDF * NdotL;

        vec3 ambient = vec3(0.03) * albedo * u_mat.material.ao;
        vec3 color = ambient + Lo;

        o_color = vec4(gamma_correct(color), 1.0);
    }
}

Instanced Rendering with SSBO

Using shader storage buffer objects for instanced rendering.

axsl
@version 450;

interface Varyings {
    vec2 v_uv;
    vec3 v_color;
};

@stage vertex {
    in Attributes {
        @location(0) vec3 a_pos;
        @location(1) vec2 a_uv;
    } a;

    out Varyings v;

    @binding(0) uniform mat4 u_view_projection;

    @std430 @binding(1) buffer InstanceBuffer {
        mat4 models[];
        vec4 colors[];
    } instances;

    void main() {
        uint instance_id = gl_InstanceID;
        mat4 model = instances.models[instance_id];
        vec4 color = instances.colors[instance_id];

        v.v_uv = a.a_uv;
        v.v_color = color.rgb;

        gl_Position = u_view_projection * model * vec4(a.a_pos, 1.0);
    }
}

@stage fragment {
    in Varyings v;
    out vec4 o_color;

    @binding(2) uniform sampler2D u_texture;

    void main() {
        vec4 tex_color = texture(u_texture, v.v_uv);
        o_color = vec4(tex_color.rgb * v.v_color, tex_color.a);
    }
}

Compute Shader

A compute shader for parallel processing.

axsl
@version 450;

@stage compute {
    layout(local_size_x = 16, local_size_y = 16) in;

    @std430 @binding(0) buffer InputBuffer {
        vec4 data[];
    } input_buf;

    @std430 @binding(1) buffer OutputBuffer {
        vec4 data[];
    } output_buf;

    @binding(2) uniform float u_time;
    @binding(3) uniform uvec2 u_resolution;

    void main() {
        uvec2 id = gl_GlobalInvocationID.xy;

        if (id.x >= u_resolution.x || id.y >= u_resolution.y) {
            return;
        }

        uint idx = id.y * u_resolution.x + id.x;

        vec4 input_data = input_buf.data[idx];

        // Some processing
        vec4 result = input_data * sin(u_time);

        output_buf.data[idx] = result;
    }
}

Geometry Shader

A geometry shader that generates additional geometry.

axsl
@version 450;

interface VertexOut {
    vec3 v_color;
};

@stage vertex {
    @location(0) in vec3 a_pos;
    @location(1) in vec3 a_color;

    out VertexOut v;

    @binding(0) uniform mat4 u_mvp;

    void main() {
        v.v_color = a_color;
        gl_Position = u_mvp * vec4(a_pos, 1.0);
    }
}

@stage geometry {
    layout(triangles) in;
    layout(triangle_strip, max_vertices = 3) out;

    in VertexOut v_in[];
    out VertexOut v_out;

    void main() {
        for (int i = 0; i < 3; i++) {
            gl_Position = gl_in[i].gl_Position;
            v_out.v_color = v_in[i].v_color;
            emit();
        }
        endPrimitive();
    }
}

@stage fragment {
    in VertexOut v;
    out vec4 o_color;

    void main() {
        o_color = vec4(v.v_color, 1.0);
    }
}

Using @include

Organizing shared code with includes.

common/lighting.axsl:

axsl
@version 450;

struct PointLight {
    vec3 position;
    vec3 color;
    float intensity;
    float radius;
};

float attenuation(float distance, float radius) {
    float att = 1.0 - (distance / radius);
    return max(att, 0.0);
}

vec3 compute_point_light(PointLight light, vec3 world_pos, vec3 normal) {
    vec3 light_dir = light.position - world_pos;
    float distance = length(light_dir);
    light_dir = normalize(light_dir);

    float diff = max(dot(normal, light_dir), 0.0);
    float att = attenuation(distance, light.radius);

    return light.color * light.intensity * diff * att;
}

main.axsl:

axsl
@version 450;
@include "common/lighting.axsl";

interface Varyings {
    vec3 v_normal;
    vec3 v_world_pos;
};

@stage vertex {
    @location(0) in vec3 a_pos;
    @location(1) in vec3 a_normal;

    out Varyings v;

    @binding(0) uniform mat4 u_mvp;
    @binding(1) uniform mat4 u_model;

    void main() {
        v.v_normal = mat3(u_model) * a_normal;
        vec4 world_pos = u_model * vec4(a_pos, 1.0);
        v.v_world_pos = world_pos.xyz;
        gl_Position = u_mvp * world_pos;
    }
}

@stage fragment {
    in Varyings v;
    out vec4 o_color;

    @std430 @binding(2) buffer LightBuffer {
        PointLight lights[];
    } light_buf;

    @binding(3) uniform uint u_light_count;

    void main() {
        vec3 normal = normalize(v.v_normal);
        vec3 total_light = vec3(0.0);

        for (uint i = 0; i < u_light_count; i++) {
            total_light += compute_point_light(
                light_buf.lights[i],
                v.v_world_pos,
                normal
            );
        }

        vec3 ambient = vec3(0.1);
        vec3 color = ambient + total_light;

        o_color = vec4(color, 1.0);
    }
}

Compile with:

bash
axslc -I . main.axsl

Uniform Shadowing Example

Global uniforms with per-stage overrides.

axsl
@version 450;

// Global default time uniform
uniform float u_time = 0.0;

interface Varyings {
    vec2 v_uv;
};

@stage vertex {
    @location(0) in vec3 a_pos;
    @location(1) in vec2 a_uv;

    out Varyings v;

    // Vertex-specific time (different binding)
    @binding(5) uniform float u_time;

    @binding(0) uniform mat4 u_mvp;

    void main() {
        // Animated vertex positions
        vec3 pos = a_pos;
        pos.y += sin(u_time + a_pos.x) * 0.1;

        v.v_uv = a_uv;
        gl_Position = u_mvp * vec4(pos, 1.0);
    }
}

@stage fragment {
    in Varyings v;
    out vec4 o_color;

    // Uses global u_time (no override)

    void main() {
        float pulse = sin(u_time * 2.0) * 0.5 + 0.5;
        o_color = vec4(v.v_uv, pulse, 1.0);
    }
}

Vertex output uses @binding(5) for u_time, fragment uses the global default binding.