#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput
@group(0) @binding(0) var fullres_color: texture_2d<f32>;
@group(0) @binding(1) var fullres_depth: texture_depth_2d;
@group(0) @binding(2) var lowres_color: texture_2d<f32>;
@group(0) @binding(3) var lowres_depth: texture_depth_2d;
@group(0) @binding(4) var nearest_sampler: sampler;
struct CompositorSettings {
depth_bias: f32,
}
@group(0) @binding(5) var<uniform> settings: CompositorSettings;
@fragment
fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
let fr_color = textureSample(fullres_color, nearest_sampler, in.uv);
let fr_depth = textureSample(fullres_depth, nearest_sampler, in.uv);
let lr_color = textureSample(lowres_color, nearest_sampler, in.uv);
let lr_depth = textureSample(lowres_depth, nearest_sampler, in.uv);
// Bevy reversed-Z: 1.0 = near, 0.0 = far
// Scale bias by depth — near objects (d≈1) get full bias,
// far objects (d≈0) get proportionally less, matching the
// depth-buffer mismatch between lowres and fullres.
let effective_bias = settings.depth_bias * fr_depth;
// Only composite low-res pixel if it's substantially opaque AND closer to camera.
// Higher alpha threshold prevents edge bleed from nearest upscale.
// Particles (alpha-blended, no depth write) remain visible in fullres pass.
if lr_color.a > 0.1 && lr_depth >= fr_depth - effective_bias {
// Blend rather than replace: preserves fullres particles/effects behind pixel art models
let blend = lr_color.a;
return vec4<f32>(
mix(fr_color.rgb, lr_color.rgb, blend),
max(fr_color.a, lr_color.a),
);
}
return fr_color;
}