// Vertex shader struct InstanceInput { @location(2) position: vec2, @location(3) parallax: f32, @location(4) height: f32, }; struct VertexInput { @location(0) position: vec3, @location(1) texture_coords: vec2, } struct VertexOutput { @builtin(position) position: vec4, @location(0) texture_coords: vec2, } @group(1) @binding(0) var global: GlobalUniform; struct GlobalUniform { camera_position: vec2, camera_zoom: f32, window_aspect: f32, starfield_texture: u32, starfield_tile_size: f32 }; fn fmod(x: vec2, m: f32) -> vec2 { return x - floor(x * (1.0 / m)) * m; } @vertex fn vertex_shader_main( vertex: VertexInput, instance: InstanceInput, ) -> VertexOutput { // Center of the tile the camera is currently in, in game coordinates. // x div y = x - (x mod y) let tile_center = ( global.camera_position - ( fmod( global.camera_position + global.starfield_tile_size / 2.0, global.starfield_tile_size ) - global.starfield_tile_size / 2.0 ) ); // Apply sprite aspect ratio & scale factor // also applies screen aspect ratio let scale = instance.height / global.camera_zoom; var pos: vec2 = vec2( vertex.position.x * scale / global.window_aspect, vertex.position.y * scale ); // World position relative to camera // (Note that instance position is in a different // coordinate system than usual) let camera_pos = (instance.position + tile_center) - global.camera_position; // Translate pos = pos + ( // Don't forget to correct distance for screen aspect ratio too! (camera_pos / (global.camera_zoom * instance.parallax)) / vec2(global.window_aspect, 1.0) ); var out: VertexOutput; out.position = vec4(pos, 0.0, 1.0) * instance.parallax; out.texture_coords = vertex.texture_coords; return out; } @group(0) @binding(0) var texture_array: binding_array>; @group(0) @binding(1) var sampler_array: binding_array; // Fragment shader @fragment fn fragment_shader_main(in: VertexOutput) -> @location(0) vec4 { return textureSampleLevel( texture_array[1], sampler_array[1], in.texture_coords, 0.0 ).rgba * vec4(0.5, 0.5, 0.5, 1.0); }