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