fyrox_impl/renderer/
visibility.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Volumetric visibility cache based on occlusion query.
22
23use crate::{
24    core::{algebra::Vector3, pool::Handle},
25    graph::BaseSceneGraph,
26    renderer::framework::{
27        error::FrameworkError,
28        query::{Query, QueryKind, QueryResult},
29        server::GraphicsServer,
30    },
31    scene::{graph::Graph, node::Node},
32};
33use fxhash::FxHashMap;
34
35#[derive(Debug)]
36struct PendingQuery {
37    query: Box<dyn Query>,
38    observer_position: Vector3<f32>,
39    node: Handle<Node>,
40}
41
42#[derive(Debug)]
43enum Visibility {
44    Undefined,
45    Invisible,
46    Visible,
47}
48
49type NodeVisibilityMap = FxHashMap<Handle<Node>, Visibility>;
50
51/// Volumetric visibility cache based on occlusion query.
52#[derive(Debug)]
53pub struct ObserverVisibilityCache {
54    cells: FxHashMap<Vector3<i32>, NodeVisibilityMap>,
55    pending_queries: Vec<PendingQuery>,
56    granularity: Vector3<u32>,
57    distance_discard_threshold: f32,
58}
59
60fn world_to_grid(world_position: Vector3<f32>, granularity: Vector3<u32>) -> Vector3<i32> {
61    Vector3::new(
62        (world_position.x * (granularity.x as f32)).round() as i32,
63        (world_position.y * (granularity.y as f32)).round() as i32,
64        (world_position.z * (granularity.z as f32)).round() as i32,
65    )
66}
67
68fn grid_to_world(grid_position: Vector3<i32>, granularity: Vector3<u32>) -> Vector3<f32> {
69    Vector3::new(
70        grid_position.x as f32 / (granularity.x as f32),
71        grid_position.y as f32 / (granularity.y as f32),
72        grid_position.z as f32 / (granularity.z as f32),
73    )
74}
75
76impl ObserverVisibilityCache {
77    /// Creates new visibility cache with the given granularity and distance discard threshold.
78    /// Granularity in means how much the cache should subdivide the world. For example 2 means that
79    /// 1 meter cell will be split into 8 blocks by 0.5 meters. Distance discard threshold means how
80    /// far an observer can without discarding visibility info about distant objects.
81    pub fn new(granularity: Vector3<u32>, distance_discard_threshold: f32) -> Self {
82        Self {
83            cells: Default::default(),
84            pending_queries: Default::default(),
85            granularity,
86            distance_discard_threshold,
87        }
88    }
89
90    /// Transforms the given world-space position into internal grid-space position.
91    pub fn world_to_grid(&self, world_position: Vector3<f32>) -> Vector3<i32> {
92        world_to_grid(world_position, self.granularity)
93    }
94
95    /// Transforms the given grid-space position into the world-space position.
96    pub fn grid_to_world(&self, grid_position: Vector3<i32>) -> Vector3<f32> {
97        grid_to_world(grid_position, self.granularity)
98    }
99
100    fn visibility_info(
101        &self,
102        observer_position: Vector3<f32>,
103        node: Handle<Node>,
104    ) -> Option<&Visibility> {
105        let grid_position = self.world_to_grid(observer_position);
106
107        self.cells
108            .get(&grid_position)
109            .and_then(|cell| cell.get(&node))
110    }
111
112    /// Checks whether the given object needs an occlusion query for the given observer position.
113    pub fn needs_occlusion_query(
114        &self,
115        observer_position: Vector3<f32>,
116        node: Handle<Node>,
117    ) -> bool {
118        let Some(visibility) = self.visibility_info(observer_position, node) else {
119            // There's no data about the visibility, so the occlusion query is needed.
120            return true;
121        };
122
123        match visibility {
124            Visibility::Undefined => {
125                // There's already an occlusion query on GPU.
126                false
127            }
128            Visibility::Invisible => {
129                // The object could be invisible from one angle at the observer position, but visible
130                // from another. Since we're using only position of the observer, we cannot be 100%
131                // sure, that the object is invisible even if a previous query told us so.
132                true
133            }
134            Visibility::Visible => {
135                // Some pixels of the object is visible from the given observer position, so we don't
136                // need a new occlusion query.
137                false
138            }
139        }
140    }
141
142    /// Checks whether the object at the given handle is visible from the given observer position.
143    /// This method returns `true` for non-completed occlusion queries, because occlusion query is
144    /// async operation.
145    pub fn is_visible(&self, observer_position: Vector3<f32>, node: Handle<Node>) -> bool {
146        let Some(visibility_info) = self.visibility_info(observer_position, node) else {
147            return false;
148        };
149
150        match *visibility_info {
151            Visibility::Visible
152            // Undefined visibility is treated like the object is visible, this is needed because
153            // GPU queries are async, and we must still render the object to prevent popping light.
154            | Visibility::Undefined => true,
155            Visibility::Invisible => false,
156        }
157    }
158
159    /// Begins a new visibility query (using occlusion query) for the object at the given handle from
160    /// the given observer position.
161    pub fn begin_query(
162        &mut self,
163        server: &dyn GraphicsServer,
164        observer_position: Vector3<f32>,
165        node: Handle<Node>,
166    ) -> Result<(), FrameworkError> {
167        let query = server.create_query()?;
168        query.begin(QueryKind::AnySamplesPassed);
169        self.pending_queries.push(PendingQuery {
170            query,
171            observer_position,
172            node,
173        });
174
175        let grid_position = self.world_to_grid(observer_position);
176        self.cells
177            .entry(grid_position)
178            .or_default()
179            .entry(node)
180            .or_insert(Visibility::Undefined);
181
182        Ok(())
183    }
184
185    /// Ends the last visibility query.
186    pub fn end_query(&mut self) {
187        let last_pending_query = self
188            .pending_queries
189            .last()
190            .expect("begin_query/end_query calls mismatch!");
191        last_pending_query.query.end();
192    }
193
194    /// This method removes info about too distant objects and processes the pending visibility queries.
195    pub fn update(&mut self, observer_position: Vector3<f32>) {
196        self.pending_queries.retain_mut(|pending_query| {
197            if let Some(QueryResult::AnySamplesPassed(query_result)) =
198                pending_query.query.try_get_result()
199            {
200                let grid_position =
201                    world_to_grid(pending_query.observer_position, self.granularity);
202
203                let Some(cell) = self.cells.get_mut(&grid_position) else {
204                    return false;
205                };
206
207                let Some(visibility) = cell.get_mut(&pending_query.node) else {
208                    return false;
209                };
210
211                match visibility {
212                    Visibility::Undefined => match query_result {
213                        true => {
214                            *visibility = Visibility::Visible;
215                        }
216                        false => {
217                            *visibility = Visibility::Invisible;
218                        }
219                    },
220                    Visibility::Invisible => {
221                        if query_result {
222                            // Override "invisibility" - if any fragment of an object is visible, then
223                            // it will remain visible forever. This is ok for non-moving objects only.
224                            *visibility = Visibility::Visible;
225                        }
226                    }
227                    Visibility::Visible => {
228                        // Ignore the query result and keep the visibility.
229                    }
230                }
231
232                false
233            } else {
234                true
235            }
236        });
237
238        // Remove visibility info from the cache for distant cells.
239        self.cells.retain(|grid_position, _| {
240            let world_position = grid_to_world(*grid_position, self.granularity);
241
242            world_position.metric_distance(&observer_position) < self.distance_discard_threshold
243        });
244    }
245}
246
247#[derive(Debug)]
248struct ObserverData {
249    position: Vector3<f32>,
250    visibility_cache: ObserverVisibilityCache,
251}
252
253/// Visibility cache that caches visibility info for multiple cameras.
254#[derive(Default, Debug)]
255pub struct VisibilityCache {
256    observers: FxHashMap<Handle<Node>, ObserverData>,
257}
258
259impl VisibilityCache {
260    /// Gets or adds new storage for the given observer.
261    pub fn get_or_register(
262        &mut self,
263        graph: &Graph,
264        observer: Handle<Node>,
265    ) -> &mut ObserverVisibilityCache {
266        &mut self
267            .observers
268            .entry(observer)
269            .or_insert_with(|| ObserverData {
270                position: graph[observer].global_position(),
271                visibility_cache: ObserverVisibilityCache::new(Vector3::repeat(2), 100.0),
272            })
273            .visibility_cache
274    }
275
276    /// Updates the cache by removing unused data.
277    pub fn update(&mut self, graph: &Graph) {
278        self.observers.retain(|observer, data| {
279            let Some(observer_ref) = graph.try_get(*observer) else {
280                return false;
281            };
282
283            data.position = observer_ref.global_position();
284
285            data.visibility_cache.update(data.position);
286
287            true
288        });
289    }
290}