// 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: vec2, window_size: vec2, window_aspect: vec2, starfield_texture: vec2, starfield_tile_size: vec2, }; fn fmod(x: vec2, m: f32) -> vec2 { return x - floor(x / 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.xy - ( fmod( global.camera_position.xy + global.starfield_tile_size.x / 2.0, global.starfield_tile_size.x ) - global.starfield_tile_size.x / 2.0 ) ); // Apply sprite aspect ratio & scale factor // also applies screen aspect ratio var scale: f32 = instance.height / global.camera_zoom.x; // Minimum scale to prevent flicker at large zoom levels var real_size = scale * global.window_size.xy; if (real_size.x < 2.0 || real_size.y < 2.0) { scale = 2.0 / min(global.window_size.x, global.window_size.y); } var pos: vec2 = vec2( vertex.position.x * scale / global.window_aspect.x, 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.xy; // Translate pos = pos + ( // Don't forget to correct distance for screen aspect ratio too! (camera_pos / (global.camera_zoom.x * (instance.parallax))) / vec2(global.window_aspect.x, 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[global.starfield_texture.x], sampler_array[global.starfield_texture.x], in.texture_coords, 0.0 ).rgba * vec4(0.5, 0.5, 0.5, 1.0); }