struct Uniforms {
view: mat4x4<f32>,
projection: mat4x4<f32>,
camera_position: vec4<f32>,
num_lights: vec4<u32>,
ambient_light: vec4<f32>,
light_view_projection: mat4x4<f32>,
shadow_bias: f32,
shadows_enabled: f32,
global_unlit: f32,
shadow_normal_bias: f32,
snap_resolution: vec2<f32>,
snap_enabled: u32,
affine_enabled: u32,
fog_color: vec3<f32>,
fog_enabled: u32,
fog_start: f32,
fog_end: f32,
cascade_count: u32,
directional_light_size: f32,
cascade_view_projections: array<mat4x4<f32>, 4>,
cascade_split_distances: vec4<f32>,
cascade_atlas_offsets: array<vec4<f32>, 4>,
cascade_atlas_scale: vec4<f32>,
time: f32,
pbr_debug_mode: u32,
texture_debug_stripes: u32,
texture_debug_stripes_speed: f32,
directional_light_direction: vec4<f32>,
ibl_blend_factor: f32,
};
struct PackedSpriteData {
position: vec4<f32>,
rs_col0: vec2<f32>,
rs_col1: vec2<f32>,
uv_min: vec2<f32>,
uv_max: vec2<f32>,
color: vec4<f32>,
texture_slot: u32,
texture_slot2: u32,
blend_factor: f32,
_padding: f32,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) @interpolate(flat) texture_slot: u32,
@location(2) @interpolate(flat) texture_slot2: u32,
@location(3) blend_factor: f32,
@location(4) sprite_color: vec4<f32>,
};
@group(0) @binding(0)
var<uniform> uniforms: Uniforms;
@group(1) @binding(0)
var<storage, read> sprite_data: array<PackedSpriteData>;
@group(1) @binding(1)
var sprite_atlas: texture_2d<f32>;
@group(1) @binding(2)
var sprite_sampler: sampler;
@group(1) @binding(3)
var<uniform> sprite_atlas_info: vec4<f32>;
@group(1) @binding(4)
var<storage, read> visible_indices: array<u32>;
const QUAD_POSITIONS = array<vec2<f32>, 4>(
vec2(-0.5, -0.5),
vec2(0.5, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5),
);
const QUAD_UVS = array<vec2<f32>, 4>(
vec2(0.0, 1.0),
vec2(1.0, 1.0),
vec2(1.0, 0.0),
vec2(0.0, 0.0),
);
@vertex
fn vs_main(
@builtin(vertex_index) vertex_index: u32,
@builtin(instance_index) instance_index: u32,
) -> VertexOutput {
var out: VertexOutput;
let sprite_index = visible_indices[instance_index];
let s = sprite_data[sprite_index];
let local = QUAD_POSITIONS[vertex_index];
let tex_coords = QUAD_UVS[vertex_index];
let world_xy = vec2<f32>(
local.x * s.rs_col0.x + local.y * s.rs_col1.x + s.position.x,
local.x * s.rs_col0.y + local.y * s.rs_col1.y + s.position.y,
);
let world_pos = vec4<f32>(world_xy, s.position.z, 1.0);
let view_projection = uniforms.projection * uniforms.view;
out.position = view_projection * world_pos;
out.uv = mix(s.uv_min, s.uv_max, tex_coords);
out.texture_slot = s.texture_slot;
out.texture_slot2 = s.texture_slot2;
out.blend_factor = s.blend_factor;
out.sprite_color = s.color;
return out;
}
fn sample_atlas_slot(slot: u32, uv: vec2<f32>) -> vec4<f32> {
let slots_per_row = u32(sprite_atlas_info.x);
let slot_uv_size = sprite_atlas_info.y;
let row = slot / slots_per_row;
let col = slot % slots_per_row;
let atlas_uv = vec2<f32>(
f32(col) * slot_uv_size + uv.x * slot_uv_size,
f32(row) * slot_uv_size + uv.y * slot_uv_size,
);
return textureSampleLevel(sprite_atlas, sprite_sampler, atlas_uv, 0.0);
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
var tex_color = sample_atlas_slot(in.texture_slot, in.uv);
if in.blend_factor > 0.0 {
let tex_color2 = sample_atlas_slot(in.texture_slot2, in.uv);
tex_color = mix(tex_color, tex_color2, in.blend_factor);
}
let final_color = tex_color * in.sprite_color;
if final_color.a < 0.001 {
discard;
}
return vec4<f32>(final_color.rgb * final_color.a, final_color.a);
}