Skip to main content

three_d/core/texture/
texture_cube_map.rs

1use crate::core::texture::*;
2
3#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
4///
5/// The 6 sides of a cube map
6///
7pub enum CubeMapSide {
8    /// Positive y
9    Top,
10    /// Negative y
11    Bottom,
12    /// Positive x
13    Right,
14    /// Negative x
15    Left,
16    /// Negative z
17    Front,
18    /// Positive z
19    Back,
20}
21
22///
23/// Iterator over the 6 side of a cube map.
24///
25pub struct CubeMapSideIterator {
26    index: usize,
27}
28
29impl CubeMapSideIterator {
30    ///
31    /// Creates a new iterator over the 6 side of a cube map.
32    ///
33    pub fn new() -> Self {
34        Self { index: 0 }
35    }
36}
37
38impl Default for CubeMapSideIterator {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl Iterator for CubeMapSideIterator {
45    type Item = CubeMapSide;
46    fn next(&mut self) -> Option<Self::Item> {
47        self.index += 1;
48        match self.index {
49            1 => Some(CubeMapSide::Right),
50            2 => Some(CubeMapSide::Left),
51            3 => Some(CubeMapSide::Top),
52            4 => Some(CubeMapSide::Bottom),
53            5 => Some(CubeMapSide::Front),
54            6 => Some(CubeMapSide::Back),
55            _ => None,
56        }
57    }
58}
59
60impl CubeMapSide {
61    ///
62    /// Iterator over the 6 side of a cube map.
63    ///
64    pub fn iter() -> CubeMapSideIterator {
65        CubeMapSideIterator::new()
66    }
67
68    pub(in crate::core) fn to_const(self) -> u32 {
69        match self {
70            CubeMapSide::Right => crate::context::TEXTURE_CUBE_MAP_POSITIVE_X,
71            CubeMapSide::Left => crate::context::TEXTURE_CUBE_MAP_NEGATIVE_X,
72            CubeMapSide::Top => crate::context::TEXTURE_CUBE_MAP_POSITIVE_Y,
73            CubeMapSide::Bottom => crate::context::TEXTURE_CUBE_MAP_NEGATIVE_Y,
74            CubeMapSide::Front => crate::context::TEXTURE_CUBE_MAP_POSITIVE_Z,
75            CubeMapSide::Back => crate::context::TEXTURE_CUBE_MAP_NEGATIVE_Z,
76        }
77    }
78
79    /// The up direction that should be used when rendering into this cube map side.
80    pub fn up(&self) -> Vec3 {
81        match self {
82            CubeMapSide::Right => vec3(0.0, -1.0, 0.0),
83            CubeMapSide::Left => vec3(0.0, -1.0, 0.0),
84            CubeMapSide::Top => vec3(0.0, 0.0, 1.0),
85            CubeMapSide::Bottom => vec3(0.0, 0.0, -1.0),
86            CubeMapSide::Front => vec3(0.0, -1.0, 0.0),
87            CubeMapSide::Back => vec3(0.0, -1.0, 0.0),
88        }
89    }
90
91    /// The direction from origo towards the center of this cube map side.
92    pub fn direction(&self) -> Vec3 {
93        match self {
94            CubeMapSide::Right => vec3(1.0, 0.0, 0.0),
95            CubeMapSide::Left => vec3(-1.0, 0.0, 0.0),
96            CubeMapSide::Top => vec3(0.0, 1.0, 0.0),
97            CubeMapSide::Bottom => vec3(0.0, -1.0, 0.0),
98            CubeMapSide::Front => vec3(0.0, 0.0, 1.0),
99            CubeMapSide::Back => vec3(0.0, 0.0, -1.0),
100        }
101    }
102}
103
104///
105/// A texture that covers all 6 sides of a cube.
106///
107pub struct TextureCubeMap {
108    context: Context,
109    id: crate::context::Texture,
110    width: u32,
111    height: u32,
112    number_of_mip_maps: u32,
113    data_byte_size: usize,
114}
115
116impl TextureCubeMap {
117    ///
118    /// Creates a new cube map texture from the given [CpuTexture]s.
119    /// All of the cpu textures must contain data with the same [TextureDataType].
120    ///
121    /// **Note:** Mip maps will not be generated for RGB16F and RGB32F format, even if `mip_map_filter` is specified.
122    ///
123    pub fn new(
124        context: &Context,
125        right: &CpuTexture,
126        left: &CpuTexture,
127        top: &CpuTexture,
128        bottom: &CpuTexture,
129        front: &CpuTexture,
130        back: &CpuTexture,
131    ) -> Self {
132        match &front.data {
133            TextureData::RU8(front_data) => Self::new_with_data(
134                context,
135                front,
136                right.wrap_s,
137                ru8_data(right),
138                ru8_data(left),
139                ru8_data(top),
140                ru8_data(bottom),
141                front_data,
142                ru8_data(back),
143            ),
144            TextureData::RgU8(front_data) => Self::new_with_data(
145                context,
146                front,
147                right.wrap_s,
148                rgu8_data(right),
149                rgu8_data(left),
150                rgu8_data(top),
151                rgu8_data(bottom),
152                front_data,
153                rgu8_data(back),
154            ),
155            TextureData::RgbU8(front_data) => Self::new_with_data(
156                context,
157                front,
158                right.wrap_s,
159                rgbu8_data(right),
160                rgbu8_data(left),
161                rgbu8_data(top),
162                rgbu8_data(bottom),
163                front_data,
164                rgbu8_data(back),
165            ),
166            TextureData::RgbaU8(front_data) => Self::new_with_data(
167                context,
168                front,
169                right.wrap_s,
170                rgbau8_data(right),
171                rgbau8_data(left),
172                rgbau8_data(top),
173                rgbau8_data(bottom),
174                front_data,
175                rgbau8_data(back),
176            ),
177            TextureData::RF16(front_data) => Self::new_with_data(
178                context,
179                front,
180                right.wrap_s,
181                rf16_data(right),
182                rf16_data(left),
183                rf16_data(top),
184                rf16_data(bottom),
185                front_data,
186                rf16_data(back),
187            ),
188            TextureData::RgF16(front_data) => Self::new_with_data(
189                context,
190                front,
191                right.wrap_s,
192                rgf16_data(right),
193                rgf16_data(left),
194                rgf16_data(top),
195                rgf16_data(bottom),
196                front_data,
197                rgf16_data(back),
198            ),
199            TextureData::RgbF16(front_data) => Self::new_with_data(
200                context,
201                front,
202                right.wrap_s,
203                rgbf16_data(right),
204                rgbf16_data(left),
205                rgbf16_data(top),
206                rgbf16_data(bottom),
207                front_data,
208                rgbf16_data(back),
209            ),
210            TextureData::RgbaF16(front_data) => Self::new_with_data(
211                context,
212                front,
213                right.wrap_s,
214                rgbaf16_data(right),
215                rgbaf16_data(left),
216                rgbaf16_data(top),
217                rgbaf16_data(bottom),
218                front_data,
219                rgbaf16_data(back),
220            ),
221            TextureData::RF32(front_data) => Self::new_with_data(
222                context,
223                front,
224                right.wrap_s,
225                rf32_data(right),
226                rf32_data(left),
227                rf32_data(top),
228                rf32_data(bottom),
229                front_data,
230                rf32_data(back),
231            ),
232            TextureData::RgF32(front_data) => Self::new_with_data(
233                context,
234                front,
235                right.wrap_s,
236                rgf32_data(right),
237                rgf32_data(left),
238                rgf32_data(top),
239                rgf32_data(bottom),
240                front_data,
241                rgf32_data(back),
242            ),
243            TextureData::RgbF32(front_data) => Self::new_with_data(
244                context,
245                front,
246                right.wrap_s,
247                rgbf32_data(right),
248                rgbf32_data(left),
249                rgbf32_data(top),
250                rgbf32_data(bottom),
251                front_data,
252                rgbf32_data(back),
253            ),
254            TextureData::RgbaF32(front_data) => Self::new_with_data(
255                context,
256                front,
257                right.wrap_s,
258                rgbaf32_data(right),
259                rgbaf32_data(left),
260                rgbaf32_data(top),
261                rgbaf32_data(bottom),
262                front_data,
263                rgbaf32_data(back),
264            ),
265        }
266    }
267
268    fn new_with_data<T: TextureDataType>(
269        context: &Context,
270        cpu_texture: &CpuTexture,
271        wrap_r: Wrapping,
272        right_data: &[T],
273        left_data: &[T],
274        top_data: &[T],
275        bottom_data: &[T],
276        front_data: &[T],
277        back_data: &[T],
278    ) -> Self {
279        let texture = Self::new_empty::<T>(
280            context,
281            cpu_texture.width,
282            cpu_texture.height,
283            cpu_texture.min_filter,
284            cpu_texture.mag_filter,
285            cpu_texture.mipmap,
286            cpu_texture.wrap_s,
287            cpu_texture.wrap_t,
288            wrap_r,
289        );
290        texture.fill(
291            right_data,
292            left_data,
293            top_data,
294            bottom_data,
295            front_data,
296            back_data,
297        );
298        texture
299    }
300
301    ///
302    /// Creates a new texture cube map.
303    ///
304    /// **Note:** Mip maps will not be generated for RGB16F and RGB32F format, even if `mip_map_filter` is specified.
305    ///
306    pub fn new_empty<T: TextureDataType>(
307        context: &Context,
308        width: u32,
309        height: u32,
310        min_filter: Interpolation,
311        mag_filter: Interpolation,
312        mipmap: Option<Mipmap>,
313        wrap_s: Wrapping,
314        wrap_t: Wrapping,
315        wrap_r: Wrapping,
316    ) -> Self {
317        unsafe {
318            Self::new_unchecked::<T>(
319                context,
320                width,
321                height,
322                min_filter,
323                mag_filter,
324                mipmap,
325                wrap_s,
326                wrap_t,
327                wrap_r,
328                |texture| {
329                    context.tex_storage_2d(
330                        crate::context::TEXTURE_CUBE_MAP,
331                        texture.number_of_mip_maps() as i32,
332                        T::internal_format(),
333                        width as i32,
334                        height as i32,
335                    )
336                },
337            )
338        }
339    }
340
341    ///
342    /// Fills the cube map texture with the given pixel data for the 6 images and generate mip maps if specified at construction.
343    ///
344    /// # Panic
345    /// Will panic if the length of the data for all 6 images does not correspond to the width, height and format specified at construction.
346    /// It is therefore necessary to create a new texture if the texture size or format has changed.
347    ///
348    pub fn fill<T: TextureDataType>(
349        &self,
350        right_data: &[T],
351        left_data: &[T],
352        top_data: &[T],
353        bottom_data: &[T],
354        front_data: &[T],
355        back_data: &[T],
356    ) {
357        check_data_length::<T>(
358            self.width,
359            self.height,
360            1,
361            self.data_byte_size,
362            right_data.len(),
363        );
364        check_data_length::<T>(
365            self.width,
366            self.height,
367            1,
368            self.data_byte_size,
369            left_data.len(),
370        );
371        check_data_length::<T>(
372            self.width,
373            self.height,
374            1,
375            self.data_byte_size,
376            top_data.len(),
377        );
378        check_data_length::<T>(
379            self.width,
380            self.height,
381            1,
382            self.data_byte_size,
383            bottom_data.len(),
384        );
385        check_data_length::<T>(
386            self.width,
387            self.height,
388            1,
389            self.data_byte_size,
390            front_data.len(),
391        );
392        check_data_length::<T>(
393            self.width,
394            self.height,
395            1,
396            self.data_byte_size,
397            back_data.len(),
398        );
399        self.bind();
400        for i in 0..6 {
401            let data = match i {
402                0 => right_data,
403                1 => left_data,
404                2 => top_data,
405                3 => bottom_data,
406                4 => front_data,
407                5 => back_data,
408                _ => unreachable!(),
409            };
410            unsafe {
411                self.context.tex_sub_image_2d(
412                    crate::context::TEXTURE_CUBE_MAP_POSITIVE_X + i as u32,
413                    0,
414                    0,
415                    0,
416                    self.width as i32,
417                    self.height as i32,
418                    format_from_data_type::<T>(),
419                    T::data_type(),
420                    crate::context::PixelUnpackData::Slice(Some(to_byte_slice(data))),
421                );
422            }
423        }
424        self.generate_mip_maps();
425    }
426
427    ///
428    /// Creates a new cube texture generated from the equirectangular texture given as input.
429    ///
430    pub fn new_from_equirectangular<T: PrimitiveDataType + TextureDataType>(
431        context: &Context,
432        cpu_texture: &CpuTexture,
433    ) -> Self {
434        let texture_size = cpu_texture.width / 4;
435        let texture = Self::new_empty::<[T; 4]>(
436            context,
437            texture_size,
438            texture_size,
439            Interpolation::Linear,
440            Interpolation::Linear,
441            Some(Mipmap::default()),
442            Wrapping::ClampToEdge,
443            Wrapping::ClampToEdge,
444            Wrapping::ClampToEdge,
445        );
446
447        {
448            let map = Texture2D::new(context, cpu_texture);
449            let fragment_shader_source = "
450            uniform sampler2D equirectangularMap;
451            uniform vec3 direction;
452            uniform vec3 up;
453
454            in vec2 uvs;
455            
456            layout (location = 0) out vec4 outColor;
457            
458            void main()
459            {
460                vec3 right = cross(direction, up);
461                vec3 dir = normalize(up * (uvs.y - 0.5) * 2.0 + right * (uvs.x - 0.5) * 2.0 + direction);
462                vec2 uv = vec2(0.1591 * atan(dir.z, dir.x) + 0.5, 0.3183 * asin(dir.y) + 0.5);
463                outColor = texture(equirectangularMap, uv);
464            }";
465
466            let program = Program::from_source(
467                context,
468                full_screen_vertex_shader_source(),
469                fragment_shader_source,
470            )
471            .expect("Failed compiling shader");
472
473            for side in CubeMapSide::iter() {
474                let viewport = Viewport::new_at_origo(texture_size, texture_size);
475                texture
476                    .as_color_target(&[side], None)
477                    .clear(ClearState::default())
478                    .write::<CoreError>(|| {
479                        program.use_texture("equirectangularMap", &map);
480                        program.use_uniform("direction", side.direction());
481                        program.use_uniform("up", side.up());
482                        full_screen_draw(context, &program, RenderStates::default(), viewport);
483                        Ok(())
484                    })
485                    .unwrap();
486            }
487        }
488        texture
489    }
490
491    ///
492    /// Returns a [ColorTarget] which can be used to clear, write to and read from the given side and mip level of this texture.
493    /// Combine this together with a [DepthTarget] with [RenderTarget::new] to be able to write to both a depth and color target at the same time.
494    /// If `None` is specified as the mip level, the 0 level mip level is used and mip maps are generated after a write operation if a mip map filter is specified.
495    /// Otherwise, the given mip level is used and no mip maps are generated.
496    ///
497    /// **Note:** [DepthTest] is disabled if not also writing to a depth texture.
498    ///
499    pub fn as_color_target<'a>(
500        &'a self,
501        sides: &'a [CubeMapSide],
502        mip_level: Option<u32>,
503    ) -> ColorTarget<'a> {
504        ColorTarget::new_texture_cube_map(&self.context, self, sides, mip_level)
505    }
506
507    /// The width of this texture.
508    pub fn width(&self) -> u32 {
509        self.width
510    }
511
512    /// The height of this texture.
513    pub fn height(&self) -> u32 {
514        self.height
515    }
516
517    /// The number of mip maps of this texture.
518    pub fn number_of_mip_maps(&self) -> u32 {
519        self.number_of_mip_maps
520    }
521
522    pub(in crate::core) fn generate_mip_maps(&self) {
523        if self.number_of_mip_maps > 1 {
524            self.bind();
525            unsafe {
526                self.context
527                    .generate_mipmap(crate::context::TEXTURE_CUBE_MAP);
528            }
529        }
530    }
531
532    pub(in crate::core) fn bind_as_color_target(
533        &self,
534        side: CubeMapSide,
535        channel: u32,
536        mip_level: u32,
537    ) {
538        unsafe {
539            self.context.framebuffer_texture_2d(
540                crate::context::DRAW_FRAMEBUFFER,
541                crate::context::COLOR_ATTACHMENT0 + channel,
542                side.to_const(),
543                Some(self.id),
544                mip_level as i32,
545            );
546        }
547    }
548
549    pub(in crate::core) fn bind(&self) {
550        unsafe {
551            self.context
552                .bind_texture(crate::context::TEXTURE_CUBE_MAP, Some(self.id));
553        }
554    }
555
556    ///
557    /// Creates a new texture where it is up to the caller to allocate and transfer data to the GPU
558    /// using low-level context calls inside the callback.
559    /// This function binds the texture and sets the parameters before calling the callback and generates mip maps afterwards.
560    ///
561    /// # Safety
562    ///
563    /// This function is unsafe and should only be used in special cases,
564    /// for example when you have an uncommon source of data or the data is in a special format like sRGB.
565    ///
566    pub unsafe fn new_unchecked<T: TextureDataType>(
567        context: &Context,
568        width: u32,
569        height: u32,
570        min_filter: Interpolation,
571        mag_filter: Interpolation,
572        mipmap: Option<Mipmap>,
573        wrap_s: Wrapping,
574        wrap_t: Wrapping,
575        wrap_r: Wrapping,
576        callback: impl FnOnce(&Self),
577    ) -> Self {
578        let id = generate(context);
579        let number_of_mip_maps = calculate_number_of_mip_maps::<T>(mipmap, width, height, None);
580        let texture = Self {
581            context: context.clone(),
582            id,
583            width,
584            height,
585            number_of_mip_maps,
586            data_byte_size: std::mem::size_of::<T>(),
587        };
588        texture.bind();
589        set_parameters(
590            context,
591            crate::context::TEXTURE_CUBE_MAP,
592            min_filter,
593            mag_filter,
594            if number_of_mip_maps == 1 {
595                None
596            } else {
597                mipmap
598            },
599            wrap_s,
600            wrap_t,
601            Some(wrap_r),
602        );
603        callback(&texture);
604        texture.generate_mip_maps();
605        texture
606    }
607}
608
609impl Drop for TextureCubeMap {
610    fn drop(&mut self) {
611        unsafe {
612            self.context.delete_texture(self.id);
613        }
614    }
615}