Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_sprite_render/src/sprite_mesh/sprite_material.wgsl
9423 views
#import bevy_sprite::{    
    mesh2d_functions as mesh_functions,
    mesh2d_vertex_output::VertexOutput,
    mesh2d_view_bindings::view,
}

#ifdef TONEMAP_IN_SHADER
#import bevy_core_pipeline::tonemapping
#endif

struct Vertex {
    @builtin(instance_index) instance_index: u32,
#ifdef VERTEX_POSITIONS
    @location(0) position: vec3<f32>,
#endif
#ifdef VERTEX_NORMALS
    @location(1) normal: vec3<f32>,
#endif
#ifdef VERTEX_UVS
    @location(2) uv: vec2<f32>,
#endif
#ifdef VERTEX_TANGENTS
    @location(3) tangent: vec4<f32>,
#endif
#ifdef VERTEX_COLORS
    @location(4) color: vec4<f32>,
#endif
};

@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
    var out: VertexOutput;

#ifdef VERTEX_UVS
    out.uv = vertex.uv;
#endif

#ifdef VERTEX_POSITIONS
    var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
    let position = vec4<f32>(vertex.position * vec3<f32>(material.vertex_scale, 1.0) + vec3<f32>(material.vertex_offset, 0.0), 1.0);

    out.world_position = mesh_functions::mesh2d_position_local_to_world(
        world_from_local,
        position
    );
    out.position = mesh_functions::mesh2d_position_world_to_clip(out.world_position);
#endif

#ifdef VERTEX_NORMALS
    out.world_normal = mesh_functions::mesh2d_normal_local_to_world(vertex.normal, vertex.instance_index);
#endif

#ifdef VERTEX_TANGENTS
    out.world_tangent = mesh_functions::mesh2d_tangent_local_to_world(
        world_from_local,
        vertex.tangent
    );
#endif

#ifdef VERTEX_COLORS
    out.color = vertex.color;
#endif
    return out;
}

struct SpriteMaterial {
    color: vec4<f32>,
    flags: u32,
    alpha_cutoff: f32, 
    vertex_scale: vec2<f32>,
    vertex_offset: vec2<f32>,
    uv_transform: mat3x3<f32>,
    
    tile_stretch_value: vec2<f32>,

    scale: vec2<f32>,
    min_inset: vec2<f32>,
    max_inset: vec2<f32>,
    side_stretch_value: vec2<f32>,
    center_stretch_value: vec2<f32>,
};

const SPRITE_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS: u32 = 3221225472u; // (0b11u32 << 30)
const SPRITE_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32        = 0u;          // (0u32 << 30)
const SPRITE_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32          = 1073741824u; // (1u32 << 30)
const SPRITE_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32         = 2147483648u; // (2u32 << 30)

const SPRITE_MATERIAL_FLAGS_FLIP_X: u32                   = 1u;
const SPRITE_MATERIAL_FLAGS_FLIP_Y: u32                   = 2u;
const SPRITE_MATERIAL_FLAGS_TILE_X: u32                   = 4u;
const SPRITE_MATERIAL_FLAGS_TILE_Y: u32                   = 8u;

@group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> material: SpriteMaterial;
@group(#{MATERIAL_BIND_GROUP}) @binding(1) var texture: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(2) var texture_sampler: sampler;

@fragment
fn fragment(
    mesh: VertexOutput,
) -> @location(0) vec4<f32> {

    var uv = mesh.uv; 

    if (material.flags & SPRITE_MATERIAL_FLAGS_FLIP_X) != 0u {
        uv.x = 1.0 - uv.x;
    }
    if (material.flags & SPRITE_MATERIAL_FLAGS_FLIP_Y) != 0u {
        uv.y = 1.0 - uv.y;
    }

    if (material.flags & SPRITE_MATERIAL_FLAGS_TILE_X) != 0u {
        uv.x = (uv.x - material.tile_stretch_value.x * floor(uv.x / material.tile_stretch_value.x)) / material.tile_stretch_value.x;
    }
    if (material.flags & SPRITE_MATERIAL_FLAGS_TILE_Y) != 0u {
        uv.y = (uv.y - material.tile_stretch_value.y * floor(uv.y / material.tile_stretch_value.y)) / material.tile_stretch_value.y;
    }

    // using this as a temp check for slicing
    if material.scale.x != 0.0 {
        uv = apply_slicing(uv);
    }

    uv = (material.uv_transform * vec3(uv, 1.0)).xy;

    let sprite_color = textureSample(texture, texture_sampler, uv);
    var output_color = alpha_discard(sprite_color * material.color); 

#ifdef TONEMAP_IN_SHADER
    output_color = tonemapping::tone_mapping(output_color, view.color_grading);
#endif
    
    return output_color;
}

fn apply_slicing(uv: vec2<f32>) -> vec2<f32> {
    let min_inset_scaled = material.min_inset / material.scale;
    let max_inset_scaled = material.max_inset / material.scale;

    let left = uv.x < min_inset_scaled.x;
    let right = uv.x > 1.0 - max_inset_scaled.x;
    let top = uv.y < min_inset_scaled.y; 
    let bottom = uv.y > 1.0 - max_inset_scaled.y;

    // top-left corner
    if top && left {
        return uv * material.scale; 
    } 

    // top-right corner
    if top && right { 
        return vec2<f32>(
            1.0 - (1.0 - uv.x) * material.scale.x,  
            uv.y * material.scale.y,
        );
    }

    // bottom-left corner
    if bottom && left {
        return vec2<f32>(
            uv.x * material.scale.x, 
            1.0 - (1.0 - uv.y) * material.scale.y
        );
    }

    // bottom-right corner
    if bottom && right {
        return vec2<f32>(1.0) - (vec2<f32>(1.0) - uv) * material.scale;
    }

    // top edge
    if top {
        return vec2<f32>(
            tile_or_stretch(uv.x, min_inset_scaled.x, 1.0 - max_inset_scaled.x, material.min_inset.x, 1.0 - material.max_inset.x, material.side_stretch_value.x),
            uv.y * material.scale.y
        );
    }

    // bottom edge
    if bottom {
        return vec2<f32>(
            tile_or_stretch(uv.x, min_inset_scaled.x, 1.0 - max_inset_scaled.x, material.min_inset.x, 1.0 - material.max_inset.x, material.side_stretch_value.x),
            1.0 - (1.0 - uv.y) * material.scale.y
        );
    }

    // left edge
    if left {
        return vec2<f32>(
            uv.x * material.scale.x, 
            tile_or_stretch(uv.y, min_inset_scaled.y, 1.0 - max_inset_scaled.y, material.min_inset.y, 1.0 - material.max_inset.y, material.side_stretch_value.y)
        );
    }

    // right edge
    if right {
        return vec2<f32>(
            1.0 - (1.0 - uv.x) * material.scale.x,  
            tile_or_stretch(uv.y, min_inset_scaled.y, 1.0 - max_inset_scaled.y, material.min_inset.y, 1.0 - material.max_inset.y, material.side_stretch_value.y)
        );
    }

    // center
    return vec2<f32>(
        tile_or_stretch(uv.x, min_inset_scaled.x, 1.0 - max_inset_scaled.x, material.min_inset.x, 1.0 - material.max_inset.x, material.center_stretch_value.x),
        tile_or_stretch(uv.y, min_inset_scaled.y, 1.0 - max_inset_scaled.y, material.min_inset.y, 1.0 - material.max_inset.y, material.center_stretch_value.y)
    );
}

// Maps a point p from [a, b] to [c, d], tiling it if stretch_value is not 0. 
fn tile_or_stretch(p: f32, a: f32, b: f32, c: f32, d: f32, stretch_value: f32) -> f32 {
    if stretch_value == 0.0 {
        return stretch_interval(p, a, b, c, d); 
    }
    return tile_interval(p, a, b, c, d, stretch_value);
}

// Takes a point p from an interval [a, b] and maps it to a portion of the tile [c, d]
fn tile_interval(p: f32, a: f32, b: f32, c: f32, d: f32, stretch_value: f32) -> f32 {
    let value = (p - a) / (b - a);
    let tile_value = (value - stretch_value * floor(value / stretch_value)) / stretch_value; 
    return tile_value * (d - c) + c; 
}

// Takes a point p from an interval [a, b] and translates it to the interval [c, d]
fn stretch_interval(p: f32, a: f32, b: f32, c: f32, d: f32) -> f32 {
    return (p - a) / (b - a) * (d - c) + c;
}

fn alpha_discard(output_color: vec4<f32>) -> vec4<f32> {
    var color = output_color;
    let alpha_mode = material.flags & SPRITE_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
    
    if alpha_mode == SPRITE_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE {
        // NOTE: If rendering as opaque, alpha should be ignored so set to 1.0
        color.a = 1.0;
    }
    
#ifdef MAY_DISCARD
    else if alpha_mode == SPRITE_MATERIAL_FLAGS_ALPHA_MODE_MASK {
    if color.a >= material.alpha_cutoff {
            // NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque
            color.a = 1.0;
        } else {
            // NOTE: output_color.a < in.material.alpha_cutoff should not be rendered
            discard;
        }
    }
#endif // MAY_DISCARD

    return color;
}