home icon blog icon blog icon

Shader

You can easily use multiple custom shaders in Bottomless-Pit with diffrent uniforms. This example features 2 diffrent shaders, one that uses the mouse position to change the colour of a rectangle. The other shader uses the time to offset the postion by sine and cosine of time.

main.rs:

use std::f32::consts::PI;

use bottomless_pit::colour::Colour;
use bottomless_pit::engine_handle::{Engine, EngineBuilder};
use bottomless_pit::material::{Material, MaterialBuilder};
use bottomless_pit::render::RenderInformation;
use bottomless_pit::shader::{Shader, UniformData};
use bottomless_pit::vectors::Vec2;
use bottomless_pit::Game;
use encase::ShaderType;

fn main() {
    let mut engine = EngineBuilder::new()
        .set_clear_colour(Colour::WHITE)
        .build()
        .unwrap();

    let mouse_shader = Shader::new("examples/mouse.wgsl", true, &mut engine);

    let circle_shader = Shader::new("examples/movement.wgsl", true, &mut engine);

    let data = MousePos {
        x: 0.0,
        y: 0.0,
        _junk: 0.0,
        _padding2: 0.0,
    };

    let mouse_uniform_data = UniformData::new(&engine, &data);

    // On wasm we need this to be 16 bytes aligned so we have added this instead of
    // a 0.0_f32
    let circle_uniform_data = UniformData::new(&engine, &data);

    let mouse_material = MaterialBuilder::new()
        .set_shader(mouse_shader)
        .set_uniform(&mouse_uniform_data)
        .build(&mut engine);

    let circle_material = MaterialBuilder::new()
        .set_shader(circle_shader)
        .set_uniform(&circle_uniform_data)
        .build(&mut engine);

    let defualt_material = MaterialBuilder::new().build(&mut engine);

    let game = ShaderExample {
        data,
        mouse_material,
        circle_material,
        defualt_material,
        theta: 0.0,
    };

    engine.run(game);
}

#[derive(ShaderType)]
struct MousePos {
    x: f32,
    y: f32,
    _junk: f32,
    _padding2: f32,
}

struct ShaderExample {
    mouse_material: Material,
    circle_material: Material,
    defualt_material: Material,
    data: MousePos,
    theta: f32,
}

impl Game for ShaderExample {
    fn render<'pass, 'others>(
        &'others mut self,
        mut render_handle: RenderInformation<'pass, 'others>,
    ) where
        'others: 'pass,
    {
        self.mouse_material.add_rectangle(
            Vec2 { x: 0.0, y: 0.0 },
            Vec2 { x: 100.0, y: 100.0 },
            Colour::RED,
            &render_handle,
        );
        self.circle_material.add_rectangle(
            Vec2 { x: 100.0, y: 100.0 },
            Vec2 { x: 100.0, y: 100.0 },
            Colour::RED,
            &render_handle,
        );
        self.defualt_material.add_rectangle(
            Vec2 { x: 0.0, y: 200.0 },
            Vec2 { x: 100.0, y: 100.0 },
            Colour::RED,
            &render_handle,
        );

        self.mouse_material.draw(&mut render_handle);
        self.circle_material.draw(&mut render_handle);
        self.defualt_material.draw(&mut render_handle);
    }

    fn update(&mut self, engine_handle: &mut Engine) {
        let dt = engine_handle.get_frame_delta_time();

        self.theta = (self.theta + dt) % (2.0 * PI);

        let size = engine_handle.get_window_size();
        let mouse_pos = engine_handle.get_mouse_position();

        let new_data = MousePos {
            x: mouse_pos.x / size.x as f32,
            y: mouse_pos.y / size.y as f32,
            _junk: 0.0,
            _padding2: 0.0,
        };

        self.data = new_data;
        self.mouse_material
            .update_uniform_data(&self.data, &engine_handle);
        self.circle_material
            .update_uniform_data(&self.theta, &engine_handle);
    }
}

mouse.wgsl

struct EngineUniforms {
    camera: mat3x3<f32>,
    screen_size: vec2<f32>,
}

@group(1) @binding(0)
var<uniform> engine: EngineUniforms;

struct MousePos {
    stuff: vec2<f32>,
    _junk: vec2<f32>,
}

@group(1) @binding(0)
var<uniform> camera: EngineUniforms;

@group(2) @binding(0)
var<uniform> mouse: MousePos;

struct VertexInput {
    @location(0) position: vec2<f32>,
    @location(1) tex_coords: vec2<f32>,
    @location(2) colour: vec4<f32>
}

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) tex_coords: vec2<f32>,
    @location(1) colour: vec4<f32>,
}

// vertex shader
@vertex
fn vs_main(model: VertexInput) -> VertexOutput {
    var out: VertexOutput;
    var pos: vec3<f32> = engine.camera * vec3<f32>(model.position, 1.0); // the vectors on the right the matrices go on the left in order of importance
    
    pos = pos / pos.z;
    pos.x = 2.0 * pos.x / engine.screen_size.x - 1.0;
    pos.y = ((2.0 * pos.y / engine.screen_size.y) - 1.0) * -1.0;
    out.clip_position = vec4(pos.xy, 0.0, 1.0);
    
    return out;
}


// Fragment shader

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let pain = mouse.stuff;
    return vec4(pain.x, pain.y, 1.0, 1.0);
}

movement.wgsl:

struct EngineUniforms {
    camera: mat3x3,
    screen_size: vec2,
}

@group(1) @binding(0)
var<uniform> engine: EngineUniforms;

struct Time {
    time: f32,
    _junk: f32,
    _junk1: f32,
    _junk4: f32,
}

@group(1) @binding(0)
var<uniform> camera: EngineUniforms;

@group(2) @binding(0)
var<uniform> time: Time;

struct VertexInput {
    @location(0) position: vec2<f32>,
    @location(1) tex_coords: vec2<f32>,
    @location(2) colour: vec4<f32>
}

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) tex_coords: vec2<f32>,
    @location(1) colour: vec4<f32>,
}

// vertex shader
@vertex
fn vs_main(model: VertexInput) -> VertexOutput {
    var out: VertexOutput;
    out.tex_coords = model.tex_coords;
    var pos: vec3<f32> = engine.camera * vec3<f32>(model.position.x + sin(time.time) * (engine.screen_size.x / 2.0), model.position.y + cos(time.time) * (engine.screen_size.y / 2.0), 1.0); // the vectors on the right the matrices go on the left in order of importance
    
    pos = pos / pos.z;
    pos.x = 2.0 * pos.x / engine.screen_size.x - 1.0;
    pos.y = ((2.0 * pos.y / engine.screen_size.y) - 1.0) * -1.0;
    out.clip_position = vec4(pos.xy, 0.0, 1.0);
    
    
    out.colour = model.colour;
    return out;
}


// Fragment shader

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4 <f32> {
    return in.colour;
}