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