prevent redundant occlusion queries for visible objects

This commit is contained in:
Dmitry Stepanov
2024-08-17 21:49:31 +03:00
parent e363cbba19
commit 56f9ae0439
2 changed files with 102 additions and 45 deletions
+29 -26
View File
@@ -596,32 +596,35 @@ impl DeferredLightRenderer {
// for now we'll skip it, since this optimization could be useful only for scenes with
// mixed indoor/outdoor environment.
if light.cast::<DirectionalLight>().is_none() {
// Draw full screen quad, that will be used to count pixels that passed the stencil test
// on the stencil buffer's content generated by two previous drawing commands.
visibility_cache.begin_query(state, camera_global_position, light_handle)?;
frame_buffer.draw(
&self.quad,
state,
viewport,
&self.flat_shader.program,
&DrawParameters {
cull_face: None,
color_write: ColorMask::all(false),
depth_write: false,
stencil_test: Some(StencilFunc {
func: CompareFunc::NotEqual,
..Default::default()
}),
depth_test: true,
blend: None,
stencil_op: Default::default(),
},
ElementRange::Full,
|mut program_binding| {
program_binding.set_matrix4(&self.flat_shader.wvp_matrix, &frame_matrix);
},
)?;
visibility_cache.end_query();
if visibility_cache.needs_occlusion_query(camera_global_position, light_handle) {
// Draw full screen quad, that will be used to count pixels that passed the stencil test
// on the stencil buffer's content generated by two previous drawing commands.
visibility_cache.begin_query(state, camera_global_position, light_handle)?;
frame_buffer.draw(
&self.quad,
state,
viewport,
&self.flat_shader.program,
&DrawParameters {
cull_face: None,
color_write: ColorMask::all(false),
depth_write: false,
stencil_test: Some(StencilFunc {
func: CompareFunc::NotEqual,
..Default::default()
}),
depth_test: true,
blend: None,
stencil_op: Default::default(),
},
ElementRange::Full,
|mut program_binding| {
program_binding
.set_matrix4(&self.flat_shader.wvp_matrix, &frame_matrix);
},
)?;
visibility_cache.end_query();
}
if !visibility_cache.is_visible(camera_global_position, light_handle) {
continue;
+73 -19
View File
@@ -36,7 +36,14 @@ struct PendingQuery {
node: Handle<Node>,
}
type NodeVisibilityMap = FxHashMap<Handle<Node>, Option<bool>>;
#[derive(Debug)]
enum Visibility {
Undefined,
Invisible,
Visible,
}
type NodeVisibilityMap = FxHashMap<Handle<Node>, Visibility>;
/// Volumetric visibility cache based on occlusion query.
pub struct VisibilityCache {
@@ -66,20 +73,59 @@ impl VisibilityCache {
world_to_grid(world_position, self.granularity)
}
pub fn is_visible(&self, observer_position: Vector3<f32>, node: Handle<Node>) -> bool {
fn visibility_info(
&self,
observer_position: Vector3<f32>,
node: Handle<Node>,
) -> Option<&Visibility> {
let grid_position = self.world_to_grid(observer_position);
let Some(cell) = self.cells.get(&grid_position) else {
self.cells
.get(&grid_position)
.and_then(|cell| cell.get(&node))
}
pub fn needs_occlusion_query(
&self,
observer_position: Vector3<f32>,
node: Handle<Node>,
) -> bool {
let Some(visibility) = self.visibility_info(observer_position, node) else {
// There's no data about the visibility, so the occlusion query is needed.
return true;
};
match visibility {
Visibility::Undefined => {
// There's already an occlusion query on GPU.
false
}
Visibility::Invisible => {
// The object could be invisible from one angle at the observer position, but visible
// from another. Since we're using only position of the observer, we cannot be 100%
// sure, that the object is invisible even if a previous query told us so.
true
}
Visibility::Visible => {
// Some pixels of the object is visible from the given observer position, so we don't
// need a new occlusion query.
false
}
}
}
pub fn is_visible(&self, observer_position: Vector3<f32>, node: Handle<Node>) -> bool {
let Some(visibility_info) = self.visibility_info(observer_position, node) else {
return false;
};
let Some(visibility_info) = cell.get(&node) else {
return false;
};
// Undefined visibility is treated like the object is visible, this is needed because
// GPU queries are async.
visibility_info.unwrap_or(true)
match *visibility_info {
Visibility::Visible
// Undefined visibility is treated like the object is visible, this is needed because
// GPU queries are async, and we must still render the object to prevent popping light.
| Visibility::Undefined => true,
Visibility::Invisible => false,
}
}
pub fn begin_query(
@@ -101,7 +147,7 @@ impl VisibilityCache {
.entry(grid_position)
.or_default()
.entry(node)
.or_default();
.or_insert(Visibility::Undefined);
Ok(())
}
@@ -130,15 +176,23 @@ impl VisibilityCache {
.expect("object visibility must be predefined!");
match visibility {
None => {
*visibility = Some(query_result);
}
Some(visibility) => {
// Override "invisibility" - if any fragment of an object is visible, then
// it will remain visible forever. This is ok for non-moving objects only.
if !*visibility && query_result {
*visibility = true;
Visibility::Undefined => match query_result {
true => {
*visibility = Visibility::Visible;
}
false => {
*visibility = Visibility::Invisible;
}
},
Visibility::Invisible => {
if query_result {
// Override "invisibility" - if any fragment of an object is visible, then
// it will remain visible forever. This is ok for non-moving objects only.
*visibility = Visibility::Visible;
}
}
Visibility::Visible => {
// Ignore the query result and keep the visibility.
}
}