Custom Shaders

Writing a Custom Shader

With valora, you can shade each path with its own shader, and define custom uniforms. We'll start with this simple GLSL fragment shader:

#version 400

uniform vec3 color;
uniform float scale;
uniform float height;
uniform float width;

out vec4 frag;

void main() {
  vec2 pos = gl_FragCoord.xy / scale;

  vec3 white = vec3(1., 1., 1.);

  frag = vec4(mix(white, color, pos.x / width), 1.);
}

Save this to a file named pattern.frag.

Now, we'll use valora's shader API to define some uniforms and load the shader. A shader in valora is made of two parts:

  1. The shader program. This is the GLSL above, which exposes an interface to provide uniforms.

  2. The uniforms bound to the shader.

First let's define the uniforms type in Rust:

#[derive(UniformSet, Copy, Clone, Debug)]
struct Uniforms {
    /// A solid color overlay on the shader pattern.
    color: (f32, f32, f32),
    /// The vector scale of the output.
    scale: f32,
    /// Width of the output.
    width: f32,
    /// Height of the output.
    height: f32,
}

The derive macro UniformSet will generate mappings from the field types to something we can send to the GPU, should such a mapping exist for the type.

Now let's load the shader and draw with it:

fn main() -> Result<()> {
    run_fn(Options::from_args(), |gpu, world, _rng| {
        let mut program = ShaderProgram::new(&gpu, "pattern.frag")?;
        let uniforms = Uniforms {
            color: Hsv::new(0., 0.7, 0.7).into_rgb::<Srgb>().into_components(),
            scale: world.scale,
            width: world.width,
            height: world.height,
        };
        let shader = program.bind(uniforms);

        Ok(move |ctx: Context, canvas: &mut Canvas| {
            canvas.set_color(LinSrgb::new(1., 1., 1.));
            canvas.paint(Filled(ctx.world));

            canvas.set_shader(shader.clone());
            let square = Ngon::square(world.center(), 200.);

            canvas.paint(Filled(square));
        })
    })
}

If you run the painting with cargo run --release, and change the GLSL, you'll notice that the view pane will update live!

Updating Uniforms

We can update our uniform values each frame. Once the uniforms are updated, we can rebind with the shader program to make a new shader.

let hue = ctx.time.as_secs_f32().sin().abs() * 40.;
uniforms.color = Hsv::new(hue, 0.7, 0.7).into_rgb::<Srgb>().into_components();
canvas.set_shader(program.bind(uniforms));

Last updated