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.axslUniform 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.