Skip to main content

wde_terrain/render/dependencies/
materials.rs

1use wde_logger::prelude::*;
2
3use bevy::{
4    ecs::system::{
5        SystemParamItem,
6        lifetimeless::{SRes, SResMut}
7    },
8    prelude::*
9};
10use wde_renderer::prelude::*;
11
12use crate::render::dependencies::terrain_buffer::TERRAIN_MATERIAL_COUNT;
13
14pub(crate) struct TerrainMaterialsPlugin;
15impl Plugin for TerrainMaterialsPlugin {
16    fn build(&self, app: &mut App) {
17        app.init_resource::<TerrainMaterialTextures>().add_plugins((
18            ExtractResourcePlugin::<TerrainMaterialTextures>::default(),
19            RenderBindingRegisterPlugin::<TerrainMaterialsBinding>::default(),
20            RenderDataRegisterPlugin::<TerrainMaterials>::default()
21        ));
22        app.get_sub_app_mut(RenderApp)
23            .unwrap()
24            .add_systems(Render, fill_arrays.in_set(RenderSet::Prepare));
25    }
26}
27
28const TEX_SIZE: (u32, u32) = (1024, 1024);
29/// Materials in splat map order (`splatmap_x_z-0.png` RGBA, then `-1.png` RGBA), from
30/// `res/core/models/terrain/` (see `SOURCES.md` there).
31const MATERIALS: [&str; TERRAIN_MATERIAL_COUNT] = [
32    "mars_dust",
33    "mars_regolith",
34    "mars_pebbles",
35    "mars_gravel",
36    "mars_sediment",
37    "mars_cliff",
38    "mars_basalt",
39    "mars_compacted"
40];
41const TEX_TYPES: [&str; 5] = [
42    "albedo",
43    "normal",
44    "roughness",
45    "ambient_occlusion",
46    "displacement"
47];
48const TEX_FORMATS: [TextureFormat; 5] = [
49    TextureFormat::Rgba8UnormSrgb,
50    TextureFormat::Rgba8Unorm,
51    TextureFormat::R8Unorm,
52    TextureFormat::R8Unorm,
53    TextureFormat::R8Unorm
54];
55
56#[derive(Resource, Default, ExtractResource, Clone)]
57pub(crate) struct TerrainMaterialTextures {
58    albedo_textures: Vec<Option<Handle<Texture>>>,
59    normal_textures: Vec<Option<Handle<Texture>>>,
60    roughness_textures: Vec<Option<Handle<Texture>>>,
61    ao_textures: Vec<Option<Handle<Texture>>>,
62    displacement_textures: Vec<Option<Handle<Texture>>>
63}
64
65#[derive(Asset, Clone, Debug, TypePath, Default)]
66pub(crate) struct TerrainMaterials;
67impl TerrainMaterials {
68    pub const TERRAIN_ALBEDO_IDX: u32 = 0;
69    pub const TERRAIN_NORMAL_IDX: u32 = 1;
70    pub const TERRAIN_ROUGHNESS_IDX: u32 = 2;
71    pub const TERRAIN_AO_IDX: u32 = 3;
72    pub const TERRAIN_DISPLACEMENT_IDX: u32 = 4;
73}
74impl RenderData for TerrainMaterials {
75    type Params = (SResMut<TerrainMaterialTextures>, SRes<AssetServer>);
76
77    fn describe(
78        (materials, asset_server): &mut SystemParamItem<Self::Params>,
79        builder: &mut RenderDataBuilder
80    ) {
81        // Load textures for each material and type
82        let usages = TextureUsages::COPY_SRC | TextureUsages::TEXTURE_BINDING;
83        for material in MATERIALS {
84            for (i, tex_type) in TEX_TYPES.iter().enumerate() {
85                let path = format!("core/models/terrain/{}/{}.png", material, tex_type);
86                let handle = asset_server.load_with_settings(
87                    path,
88                    move |settings: &mut TextureLoaderSettings| {
89                        settings.label = format!("terrain-{}-{}", material, tex_type);
90                        settings.format = TEX_FORMATS[i];
91                        settings.usages = usages;
92                    }
93                );
94                match i {
95                    0 => materials.albedo_textures.push(Some(handle)),
96                    1 => materials.normal_textures.push(Some(handle)),
97                    2 => materials.roughness_textures.push(Some(handle)),
98                    3 => materials.ao_textures.push(Some(handle)),
99                    4 => materials.displacement_textures.push(Some(handle)),
100                    _ => unreachable!()
101                }
102            }
103        }
104
105        // Build texture arrays for each type
106        let size = TEX_SIZE; // Assume all textures have the same size
107        let usages = TextureUsages::TEXTURE_BINDING
108            | TextureUsages::COPY_DST
109            | TextureUsages::RENDER_ATTACHMENT;
110        builder
111            .add_texture(
112                Self::TERRAIN_ALBEDO_IDX,
113                Texture {
114                    label: "terrain_material_albedo_array".to_string(),
115                    size,
116                    format: TextureFormat::Rgba8UnormSrgb,
117                    usages,
118                    layer_count: materials.albedo_textures.len() as u32,
119                    mip_level_count: 0, // Auto-calculate max mip levels
120                    tiling: true,
121                    ..Default::default()
122                }
123            )
124            .add_texture(
125                Self::TERRAIN_NORMAL_IDX,
126                Texture {
127                    label: "terrain_material_normal_array".to_string(),
128                    size,
129                    format: TextureFormat::Rgba8Unorm,
130                    usages,
131                    layer_count: materials.normal_textures.len() as u32,
132                    mip_level_count: 0, // Auto-calculate max mip levels
133                    tiling: true,
134                    ..Default::default()
135                }
136            )
137            .add_texture(
138                Self::TERRAIN_ROUGHNESS_IDX,
139                Texture {
140                    label: "terrain_material_roughness_array".to_string(),
141                    size,
142                    format: TextureFormat::R8Unorm,
143                    usages,
144                    layer_count: materials.roughness_textures.len() as u32,
145                    mip_level_count: 0, // Auto-calculate max mip levels
146                    tiling: true,
147                    ..Default::default()
148                }
149            )
150            .add_texture(
151                Self::TERRAIN_AO_IDX,
152                Texture {
153                    label: "terrain_material_ao_array".to_string(),
154                    size,
155                    format: TextureFormat::R8Unorm,
156                    usages,
157                    layer_count: materials.ao_textures.len() as u32,
158                    mip_level_count: 0, // Auto-calculate max mip levels
159                    tiling: true,
160                    ..Default::default()
161                }
162            )
163            .add_texture(
164                Self::TERRAIN_DISPLACEMENT_IDX,
165                Texture {
166                    label: "terrain_material_displacement_array".to_string(),
167                    size,
168                    format: TextureFormat::R8Unorm,
169                    usages,
170                    layer_count: materials.displacement_textures.len() as u32,
171                    mip_level_count: 0, // Auto-calculate max mip levels
172                    tiling: true,
173                    ..Default::default()
174                }
175            );
176    }
177}
178
179#[derive(Asset, Clone, Debug, TypePath, Default)]
180pub(crate) struct TerrainMaterialsBinding;
181impl RenderBinding for TerrainMaterialsBinding {
182    type Params = SRenderData<TerrainMaterials>;
183
184    fn describe(
185        &mut self,
186        mat: &SystemParamItem<Self::Params>,
187        builder: &mut RenderBindingBuilder
188    ) {
189        builder
190            .add_texture_array_view(mat, TerrainMaterials::TERRAIN_ALBEDO_IDX)
191            .add_texture_sampler(mat, TerrainMaterials::TERRAIN_ALBEDO_IDX)
192            .add_texture_array_view(mat, TerrainMaterials::TERRAIN_NORMAL_IDX)
193            .add_texture_sampler(mat, TerrainMaterials::TERRAIN_NORMAL_IDX)
194            .add_texture_array_view(mat, TerrainMaterials::TERRAIN_ROUGHNESS_IDX)
195            .add_texture_sampler(mat, TerrainMaterials::TERRAIN_ROUGHNESS_IDX)
196            .add_texture_array_view(mat, TerrainMaterials::TERRAIN_AO_IDX)
197            .add_texture_sampler(mat, TerrainMaterials::TERRAIN_AO_IDX)
198            .add_texture_array_view(mat, TerrainMaterials::TERRAIN_DISPLACEMENT_IDX)
199            .add_texture_sampler(mat, TerrainMaterials::TERRAIN_DISPLACEMENT_IDX);
200    }
201
202    fn label(&self) -> &str {
203        "terrain_texture_arrays"
204    }
205}
206
207/// Copy individual textures into the arrays and create the mipmaps
208fn fill_arrays(
209    render_instance: Res<RenderInstance>,
210    textures: Res<RenderAssets<GpuTexture>>,
211    materials: ResRenderData<TerrainMaterials>,
212    material_textures: Res<TerrainMaterialTextures>,
213    mut is_done: Local<bool>
214) {
215    // Only run once when all textures are loaded
216    if *is_done {
217        return;
218    }
219
220    // Get the array texture
221    let materials = match materials.iter().next() {
222        Some((_, materials)) => materials,
223        None => return
224    };
225    let (albedo_array, normal_array, roughness_array, ao_array, displacement_array) = match (
226        textures.get(
227            &materials
228                .get_texture(TerrainMaterials::TERRAIN_ALBEDO_IDX)
229                .unwrap()
230        ),
231        textures.get(
232            &materials
233                .get_texture(TerrainMaterials::TERRAIN_NORMAL_IDX)
234                .unwrap()
235        ),
236        textures.get(
237            &materials
238                .get_texture(TerrainMaterials::TERRAIN_ROUGHNESS_IDX)
239                .unwrap()
240        ),
241        textures.get(
242            &materials
243                .get_texture(TerrainMaterials::TERRAIN_AO_IDX)
244                .unwrap()
245        ),
246        textures.get(
247            &materials
248                .get_texture(TerrainMaterials::TERRAIN_DISPLACEMENT_IDX)
249                .unwrap()
250        )
251    ) {
252        (Some(albedo), Some(normal), Some(roughness), Some(ao), Some(displacement)) => {
253            (albedo, normal, roughness, ao, displacement)
254        }
255        _ => return
256    };
257
258    // Copy individual textures into the arrays
259    let render_instance = render_instance.0.read().unwrap();
260    let size = TEX_SIZE;
261    for i in 0..material_textures.albedo_textures.len() {
262        let albedo_tex = match textures.get(material_textures.albedo_textures[i].as_ref().unwrap())
263        {
264            Some(tex) => tex,
265            None => return
266        };
267        albedo_array.texture.copy_from_texture_layered(
268            &render_instance,
269            &albedo_tex.texture.texture,
270            i,
271            size
272        );
273
274        let normal_tex = match textures.get(material_textures.normal_textures[i].as_ref().unwrap())
275        {
276            Some(tex) => tex,
277            None => return
278        };
279        normal_array.texture.copy_from_texture_layered(
280            &render_instance,
281            &normal_tex.texture.texture,
282            i,
283            size
284        );
285
286        let roughness_tex =
287            match textures.get(material_textures.roughness_textures[i].as_ref().unwrap()) {
288                Some(tex) => tex,
289                None => return
290            };
291        roughness_array.texture.copy_from_texture_layered(
292            &render_instance,
293            &roughness_tex.texture.texture,
294            i,
295            size
296        );
297
298        let ao_tex = match textures.get(material_textures.ao_textures[i].as_ref().unwrap()) {
299            Some(tex) => tex,
300            None => return
301        };
302        ao_array.texture.copy_from_texture_layered(
303            &render_instance,
304            &ao_tex.texture.texture,
305            i,
306            size
307        );
308
309        let displacement_tex =
310            match textures.get(material_textures.displacement_textures[i].as_ref().unwrap()) {
311                Some(tex) => tex,
312                None => return
313            };
314        displacement_array.texture.copy_from_texture_layered(
315            &render_instance,
316            &displacement_tex.texture.texture,
317            i,
318            size
319        );
320    }
321
322    // Generate mipmaps for all texture arrays
323    debug!("Generating mipmaps for terrain material arrays.");
324    albedo_array.texture.generate_mipmaps(&render_instance);
325    normal_array.texture.generate_mipmaps(&render_instance);
326    roughness_array.texture.generate_mipmaps(&render_instance);
327    ao_array.texture.generate_mipmaps(&render_instance);
328    displacement_array
329        .texture
330        .generate_mipmaps(&render_instance);
331
332    *is_done = true;
333}