#import bevy_render::view::View @group(0) @binding(0) var view: View; @group(0) @binding(1) var layers: array>; @group(0) @binding(2) var layer_ids: array>; @group(1) @binding(0) var depth: texture_depth_2d; struct OitFragment { color: vec3, alpha: f32, depth: f32, } // Contains all the colors and depth for this specific fragment var fragment_list: array; struct FullscreenVertexOutput { @builtin(position) position: vec4, @location(0) uv: vec2, }; @fragment fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { let buffer_size = i32(view.viewport.z * view.viewport.w); let screen_index = i32(floor(in.position.x) + floor(in.position.y) * view.viewport.z); let counter = atomicLoad(&layer_ids[screen_index]); if counter == 0 { reset_indices(screen_index); // https://github.com/gfx-rs/wgpu/issues/4416 if true { discard; } return vec4(0.0); } else { // Load depth for manual depth testing. // This is necessary because early z doesn't seem to trigger in the transparent pass. // This should be done during the draw pass so those fragments simply don't exist in the list, // but this requires a bigger refactor let d = textureLoad(depth, vec2(in.position.xy), 0); let result = sort(screen_index, buffer_size, d); reset_indices(screen_index); return result.color; } } // Resets all indices to 0. // This means we don't have to clear the entire layers buffer fn reset_indices(screen_index: i32) { atomicStore(&layer_ids[screen_index], 0); layers[screen_index] = vec2(0u); } struct SortResult { color: vec4f, depth: f32, } fn sort(screen_index: i32, buffer_size: i32, opaque_depth: f32) -> SortResult { var counter = atomicLoad(&layer_ids[screen_index]); // fill list for (var i = 0; i < counter; i += 1) { let fragment = layers[screen_index + buffer_size * i]; // unpack color/alpha/depth let color = bevy_pbr::rgb9e5::rgb9e5_to_vec3_(fragment.x); let depth_alpha = bevy_core_pipeline::oit::unpack_24bit_depth_8bit_alpha(fragment.y); fragment_list[i].color = color; fragment_list[i].alpha = depth_alpha.y; fragment_list[i].depth = depth_alpha.x; } // bubble sort the list based on the depth for (var i = counter; i >= 0; i -= 1) { for (var j = 0; j < i; j += 1) { if fragment_list[j].depth < fragment_list[j + 1].depth { // swap let temp = fragment_list[j + 1]; fragment_list[j + 1] = fragment_list[j]; fragment_list[j] = temp; } } } // resolve blend var final_color = vec4(0.0); for (var i = 0; i <= counter; i += 1) { // depth testing // This needs to happen here because we can only stop iterating if the fragment is // occluded by something opaque and the fragments need to be sorted first if fragment_list[i].depth < opaque_depth { break; } let color = fragment_list[i].color; let alpha = fragment_list[i].alpha; var base_color = vec4(color.rgb * alpha, alpha); final_color = blend(final_color, base_color); if final_color.a == 1.0 { break; } } var result: SortResult; result.color = final_color; result.depth = fragment_list[0].depth; return result; } // OVER operator using premultiplied alpha // see: https://en.wikipedia.org/wiki/Alpha_compositing fn blend(color_a: vec4, color_b: vec4) -> vec4 { let final_color = color_a.rgb + (1.0 - color_a.a) * color_b.rgb; let alpha = color_a.a + (1.0 - color_a.a) * color_b.a; return vec4(final_color.rgb, alpha); }