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        render_with_material(&self.context, viewer, &self, &self.material, lights)
115    }
116
117    fn material_type(&self) -> MaterialType {
118        self.material.material_type()
119    }
120}
121
122struct ImpostersMaterial {
123    context: Context,
124    texture: Texture2DArray,
125}
126
127impl ImpostersMaterial {
128    pub fn new(
129        context: &Context,
130        aabb: AxisAlignedBoundingBox,
131        objects: impl IntoIterator<Item = impl Object> + Clone,
132        lights: &[&dyn Light],
133        max_texture_size: u32,
134    ) -> Self {
135        let mut m = Self {
136            context: context.clone(),
137            texture: Texture2DArray::new_empty::<[u8; 4]>(
138                context,
139                1,
140                1,
141                NO_VIEW_ANGLES,
142                Interpolation::Nearest,
143                Interpolation::Nearest,
144                None,
145                Wrapping::ClampToEdge,
146                Wrapping::ClampToEdge,
147            ),
148        };
149        m.update(aabb, objects, lights, max_texture_size);
150        m
151    }
152    pub fn update(
153        &mut self,
154        aabb: AxisAlignedBoundingBox,
155        objects: impl IntoIterator<Item = impl Object> + Clone,
156        lights: &[&dyn Light],
157        max_texture_size: u32,
158    ) {
159        if !aabb.is_empty() {
160            let (min, max) = (aabb.min(), aabb.max());
161            let width = f32::sqrt(f32::powi(max.x - min.x, 2) + f32::powi(max.z - min.z, 2));
162            let height = max.y - min.y;
163            let texture_width = (max_texture_size as f32 * (width / height).min(1.0)) as u32;
164            let texture_height = (max_texture_size as f32 * (height / width).min(1.0)) as u32;
165            let viewport = Viewport::new_at_origo(texture_width, texture_height);
166            let center = 0.5 * min + 0.5 * max;
167            let mut camera = Camera::new_orthographic(
168                viewport,
169                center + vec3(0.0, 0.0, -1.0),
170                center,
171                vec3(0.0, 1.0, 0.0),
172                height,
173                -2.0 * (width + height),
174                2.0 * (width + height),
175            );
176            camera.disable_tone_and_color_mapping();
177            self.texture = Texture2DArray::new_empty::<[f16; 4]>(
178                &self.context,
179                texture_width,
180                texture_height,
181                NO_VIEW_ANGLES,
182                Interpolation::Linear,
183                Interpolation::Linear,
184                None,
185                Wrapping::ClampToEdge,
186                Wrapping::ClampToEdge,
187            );
188            let mut depth_texture = DepthTexture2D::new::<f32>(
189                &self.context,
190                texture_width,
191                texture_height,
192                Wrapping::ClampToEdge,
193                Wrapping::ClampToEdge,
194            );
195            for i in 0..NO_VIEW_ANGLES {
196                let layers = [i];
197                let angle = i as f32 * 2.0 * PI / NO_VIEW_ANGLES as f32;
198                camera.set_view(
199                    center + vec3(f32::cos(angle), 0.0, f32::sin(angle)),
200                    center,
201                    vec3(0.0, 1.0, 0.0),
202                );
203                RenderTarget::new(
204                    self.texture.as_color_target(&layers, None),
205                    depth_texture.as_depth_target(),
206                )
207                .clear(ClearState::color_and_depth(0.0, 0.0, 0.0, 0.0, 1.0))
208                .render(&camera, objects.clone(), lights);
209            }
210        }
211    }
212}
213
214impl Material for ImpostersMaterial {
215    fn id(&self) -> EffectMaterialId {
216        EffectMaterialId::ImpostersMaterial
217    }
218
219    fn fragment_shader_source(&self, _lights: &[&dyn Light]) -> String {
220        format!(
221            "{}{}{}{}",
222            ToneMapping::fragment_shader_source(),
223            ColorMapping::fragment_shader_source(),
224            include_str!("../../core/shared.frag"),
225            include_str!("shaders/imposter.frag")
226        )
227    }
228
229    fn use_uniforms(&self, program: &Program, viewer: &dyn Viewer, _lights: &[&dyn Light]) {
230        viewer.tone_mapping().use_uniforms(program);
231        viewer.color_mapping().use_uniforms(program);
232        program.use_uniform("no_views", NO_VIEW_ANGLES as i32);
233        program.use_uniform("view", viewer.view());
234        program.use_texture_array("tex", &self.texture);
235    }
236
237    fn render_states(&self) -> RenderStates {
238        RenderStates {
239            blend: Blend::TRANSPARENCY,
240            cull: Cull::Back,
241            ..Default::default()
242        }
243    }
244    fn material_type(&self) -> MaterialType {
245        MaterialType::Transparent
246    }
247}