Skip to main content

three_d/renderer/object/
imposters.rs

1use crate::core::*;
2use crate::renderer::*;
3use std::f32::consts::PI;
4
5const NO_VIEW_ANGLES: u32 = 8;
6
7///
8/// A level-of-detail technique to replace rendering of high-poly meshes.
9/// Should only be used where details cannot be seen, for example when the objects are far away.
10/// A set of objects are rendered from different angles into a set of textures and the textures are then
11/// rendered continuously instead of the expensive objects.
12///
13pub struct Imposters {
14    context: Context,
15    sprites: Sprites,
16    material: ImpostersMaterial,
17}
18
19impl Imposters {
20    ///
21    /// Constructs a new [Imposters] and render the imposter texture from the given objects with the given lights.
22    /// The imposters are placed at the given positions.
23    ///
24    pub fn new(
25        context: &Context,
26        positions: &[Vec3],
27        objects: impl IntoIterator<Item = impl Object> + Clone,
28        lights: &[&dyn Light],
29        max_texture_size: u32,
30    ) -> Self {
31        let mut aabb = AxisAlignedBoundingBox::EMPTY;
32        objects
33            .clone()
34            .into_iter()
35            .for_each(|o| aabb.expand_with_aabb(o.aabb()));
36        let mut sprites = Sprites::new(context, positions, Some(vec3(0.0, 1.0, 0.0)));
37        sprites.set_transformation(get_sprite_transform(aabb));
38        Imposters {
39            context: context.clone(),
40            sprites,
41            material: ImpostersMaterial::new(context, aabb, objects, lights, max_texture_size),
42        }
43    }
44
45    ///
46    /// Set the positions of the imposters.
47    ///
48    pub fn set_positions(&mut self, positions: &[Vec3]) {
49        self.sprites.set_centers(positions);
50    }
51
52    ///
53    /// Render the imposter texture from the given objects with the given lights.
54    /// Use this if you want to update the look of the imposters.
55    ///
56    pub fn update_texture(
57        &mut self,
58        objects: impl IntoIterator<Item = impl Object> + Clone,
59        lights: &[&dyn Light],
60        max_texture_size: u32,
61    ) {
62        let mut aabb = AxisAlignedBoundingBox::EMPTY;
63        objects
64            .clone()
65            .into_iter()
66            .for_each(|o| aabb.expand_with_aabb(o.aabb()));
67        self.sprites.set_transformation(get_sprite_transform(aabb));
68        self.material
69            .update(aabb, objects, lights, max_texture_size);
70    }
71}
72
73fn get_sprite_transform(aabb: AxisAlignedBoundingBox) -> Mat4 {
74    if aabb.is_empty() {
75        Mat4::identity()
76    } else {
77        let (min, max) = (aabb.min(), aabb.max());
78        let width = f32::sqrt(f32::powi(max.x - min.x, 2) + f32::powi(max.z - min.z, 2));
79        let height = max.y - min.y;
80        let center = 0.5 * min + 0.5 * max;
81        Mat4::from_translation(center) * Mat4::from_nonuniform_scale(0.5 * width, 0.5 * height, 0.0)
82    }
83}
84
85impl<'a> IntoIterator for &'a Imposters {
86    type Item = &'a dyn Object;
87    type IntoIter = std::iter::Once<&'a dyn Object>;
88
89    fn into_iter(self) -> Self::IntoIter {
90        std::iter::once(self)
91    }
92}
93
94use std::ops::Deref;
95impl Deref for Imposters {
96    type Target = Sprites;
97    fn deref(&self) -> &Self::Target {
98        &self.sprites
99    }
100}
101
102impl std::ops::DerefMut for Imposters {
103    fn deref_mut(&mut self) -> &mut Self::Target {
104        &mut self.sprites
105    }
106}
107
108impl Geometry for Imposters {
109    impl_geometry_body!(deref);
110}
111
112impl Object for Imposters {
113    fn render(&self, viewer: &dyn Viewer, lights: &[&dyn Light]) {
114        if let Err(e) = render_with_material(&self.context, viewer, &self, &self.material, lights) {
115            panic!("{}", e.to_string());
116        }
117    }
118
119    fn material_type(&self) -> MaterialType {
120        self.material.material_type()
121    }
122}
123
124struct ImpostersMaterial {
125    context: Context,
126    texture: Texture2DArray,
127}
128
129impl ImpostersMaterial {
130    pub fn new(
131        context: &Context,
132        aabb: AxisAlignedBoundingBox,
133        objects: impl IntoIterator<Item = impl Object> + Clone,
134        lights: &[&dyn Light],
135        max_texture_size: u32,
136    ) -> Self {
137        let mut m = Self {
138            context: context.clone(),
139            texture: Texture2DArray::new_empty::<[u8; 4]>(
140                context,
141                1,
142                1,
143                NO_VIEW_ANGLES,
144                Interpolation::Nearest,
145                Interpolation::Nearest,
146                None,
147                Wrapping::ClampToEdge,
148                Wrapping::ClampToEdge,
149            ),
150        };
151        m.update(aabb, objects, lights, max_texture_size);
152        m
153    }
154    pub fn update(
155        &mut self,
156        aabb: AxisAlignedBoundingBox,
157        objects: impl IntoIterator<Item = impl Object> + Clone,
158        lights: &[&dyn Light],
159        max_texture_size: u32,
160    ) {
161        if !aabb.is_empty() {
162            let (min, max) = (aabb.min(), aabb.max());
163            let width = f32::sqrt(f32::powi(max.x - min.x, 2) + f32::powi(max.z - min.z, 2));
164            let height = max.y - min.y;
165            let texture_width = (max_texture_size as f32 * (width / height).min(1.0)) as u32;
166            let texture_height = (max_texture_size as f32 * (height / width).min(1.0)) as u32;
167            let viewport = Viewport::new_at_origo(texture_width, texture_height);
168            let center = 0.5 * min + 0.5 * max;
169            let mut camera = Camera::new_orthographic(
170                viewport,
171                center + vec3(0.0, 0.0, -1.0),
172                center,
173                vec3(0.0, 1.0, 0.0),
174                height,
175                -2.0 * (width + height),
176                2.0 * (width + height),
177            );
178            camera.disable_tone_and_color_mapping();
179            self.texture = Texture2DArray::new_empty::<[f16; 4]>(
180                &self.context,
181                texture_width,
182                texture_height,
183                NO_VIEW_ANGLES,
184                Interpolation::Linear,
185                Interpolation::Linear,
186                None,
187                Wrapping::ClampToEdge,
188                Wrapping::ClampToEdge,
189            );
190            let depth_texture = DepthTexture2D::new::<f32>(
191                &self.context,
192                texture_width,
193                texture_height,
194                Wrapping::ClampToEdge,
195                Wrapping::ClampToEdge,
196            );
197            for i in 0..NO_VIEW_ANGLES {
198                let layers = [i];
199                let angle = i as f32 * 2.0 * PI / NO_VIEW_ANGLES as f32;
200                camera.set_view(
201                    center + vec3(f32::cos(angle), 0.0, f32::sin(angle)),
202                    center,
203                    vec3(0.0, 1.0, 0.0),
204                );
205                RenderTarget::new(
206                    self.texture.as_color_target(&layers, None),
207                    depth_texture.as_depth_target(),
208                )
209                .clear(ClearState::color_and_depth(0.0, 0.0, 0.0, 0.0, 1.0))
210                .render(&camera, objects.clone(), lights);
211            }
212        }
213    }
214}
215
216impl Material for ImpostersMaterial {
217    fn id(&self) -> EffectMaterialId {
218        EffectMaterialId::ImpostersMaterial
219    }
220
221    fn fragment_shader_source(&self, _lights: &[&dyn Light]) -> String {
222        format!(
223            "{}{}{}{}",
224            ToneMapping::fragment_shader_source(),
225            ColorMapping::fragment_shader_source(),
226            include_str!("../../core/shared.frag"),
227            include_str!("shaders/imposter.frag")
228        )
229    }
230
231    fn use_uniforms(&self, program: &Program, viewer: &dyn Viewer, _lights: &[&dyn Light]) {
232        viewer.tone_mapping().use_uniforms(program);
233        viewer.color_mapping().use_uniforms(program);
234        program.use_uniform("no_views", NO_VIEW_ANGLES as i32);
235        program.use_uniform("view", viewer.view());
236        program.use_texture_array("tex", &self.texture);
237    }
238
239    fn render_states(&self) -> RenderStates {
240        RenderStates {
241            blend: Blend::TRANSPARENCY,
242            cull: Cull::Back,
243            ..Default::default()
244        }
245    }
246    fn material_type(&self) -> MaterialType {
247        MaterialType::Transparent
248    }
249}