Add a built-in feature to bevy similar to bevy_material_wizard for material skinning
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 161
Description
## What problem does this solve or what need does it fill?
One issue with loading many GLTFs for your game in bevy is if some of them use common materials, the material will be duplicated in RAM and bevy is not smart enough to know that the materials are effectively identical . This is a massive stress on resources that is unnecessary so we need something better than just tell bevy to 'load in a bunch of gltfs'. Maybe a way to serialize/deserialize materials and reskin gltf submeshes with them ?
## What solution would you like?
https://github.com/ethereumdegen/bevy_material_wizard
This crate solves that problem by allowing you to define materials as RON files and helping you lazy load them when needed using a component you add to your models that just has a String which points to the material definition name. This also enables me to use a single “rock” mesh and easily wrap it with any material i want in my editor. But most importantly ; now all of my rocks can use a common material handle to increase performance and speed up development time.
One use of this is texture atlases / cutsheets for gltfs. No other way to do it effectively besides something like this bc its important all the meshes share one material handle
Doing it the naive way would cause all of your meshes, even if they use a common cutsheet texture , to load in one texture copy per mesh (!!!) using perhaps 100x more vram and who know how many more draw calls
Essentially causing your memory to have 100 copies of the same texture in it lol
Luckily the foundational theory is extremely simple just swap out handle for gltf children once scene is ready. This crate does a bit of fancy stuff on top of that idea but only 100 LOC and a few hashmaps
Once we have that material metadata thing i already have psuedocode that can surgically replace materials based on that — so even better. As opposed to all the meshes in the gltf needing to receive the same new material
Trees need this… one mat for bark and one for leaves
## The secret sauce to save you time reading TLDR
1. serialize materials:
```
MaterialDefinition(
uv_scale_factor: 1.0,
diffuse_color_tint: Some((1.0, 1.0, 1.0, 1.0)), // RGBA
diffuse_texture: Some("textures/diffuse.png"),
normal_texture: Some("textures/normal.png"),
roughness: 0.8,
metallic: 0.5,
alpha_mode: Opaque //or AlphaBlend
)
```
2. load them into a resource when the game is loading (only text!)
```
#[derive( Resource, Clone, Default )]
pub struct MaterialDefinitionsMap {
pub material_definitions: HashMap,
}
````
3. add components to your GLTFs like MaterialOverride( String) where String is the key of the hashmap above. Then when the GLTF scene entity is ready (loaded) we do this to lazy load the full StandardMaterial , cache the handle, and do handle replacement on child meshes
```
pub fn find_or_load_material(
&mut self ,
material_name: &String,
material_definitions_map: &HashMap,
material_images_cache: &mut MaterialImageHandlesCache,
asset_server: &mut AssetServer,
material_assets: &mut Assets ,
) -> Option< Handle > {
if let Some(existing_loaded_mat_handle) = self.built_materials.get( material_name ) {
return Some( existing_loaded_mat_handle.clone() );
}else {
let material_definition = material_definitions_map.get( material_name )?;
let uv_scale = material_definition.uv_scale_factor;
let alpha_mode = material_definition.alpha_mode.to_alpha_mode();
let base_color = material_definition.diffuse_color_tint.unwrap_or(LinearRgba::WHITE);
//bevy is bugged rn so load_with_settings doesnt work here which we would LOVE to be able to RepeatUvs...! Have to modify your default image loader plugin ...
let base_color_texture_handle: Option> = material_definition.diffuse_texture.as_ref().map(
|tex| asset_server.load(
tex.to_string() )) ;
if let Some(ref base_color_texture_handle) = base_color_texture_handle {
material_images_cache.0.insert( base_color_texture_handle.id() );
}
let normal_texture_handle: Option> = material_definition.normal_texture.as_ref().map(
|tex| asset_server.load(
tex.to_string() )) ;
if let Some(ref normal_texture_handle) = normal_texture_handle {
material_images_cache.0.insert( normal_texture_handle.id() );
}
let loaded_material = StandardMaterial{
base_color: base_color.into(),
base_color_texture: base_color_texture_handle ,
normal_map_texture: normal_texture_handle,
perceptual_roughness: material_definition.roughness,
metallic: material_definition.metallic.unwrap_or(0.0),
alpha_mode,
uv_transform: Affine2::from_scale(Vec2::splat(uv_scale)) ,
//fix uv stretch ?
..default()
};
let loaded_material_handle = material_assets.add( loaded_material );
self.built_materials.insert( material_name .to_string(), loaded_material_handle.clone() );
return Some(loaded_material_handle.clone());
}
}
```
## What alternative(s) have you considered?
The blenvy workflow solves this problem in a different way but for those not using blender, we need something like the bevy_material_wizard.
## Additional context
```
Alice 🌹
—
Yesterday at 5:46 PM
Could you make an issue for this actually?
```
I need to fix up the wizard crate a bit more , remove some cruft from it and add specific-material-replacement which will be MUCH more possible once i get the MaterialMetadata component in 0.15 so I dont expect this to be ready before 0.15 or 0.16 but its good to talk about now .
Contributor guide
Assessment
This issue has not been assessed yet.