forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Array texture example (bevyengine#5077)
# Objective - Make the reusable PBR shading functionality a little more reusable - Add constructor functions for `StandardMaterial` and `PbrInput` structs to populate them with default values - Document unclear `PbrInput` members - Demonstrate how to reuse the bevy PBR shading functionality - The final important piece from bevyengine#3969 as the initial shot at making the PBR shader code reusable in custom materials ## Solution - Add back and rework the 'old' `array_texture` example from pre-0.6. - Create a custom shader material - Use a single array texture binding and sampler for the material bind group - Use a shader that calls `pbr()` from the `bevy_pbr::pbr_functions` import - Spawn a row of cubes using the custom material - In the shader, select the array texture layer to sample by using the world position x coordinate modulo the number of array texture layers <img width="1392" alt="Screenshot 2022-06-23 at 12 28 05" src="https://user-images.githubusercontent.com/302146/175278593-2296f519-f577-4ece-81c0-d842283784a1.png"> Co-authored-by: Carter Anderson <[email protected]>
- Loading branch information
Showing
7 changed files
with
309 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
#import bevy_pbr::mesh_view_bindings | ||
#import bevy_pbr::mesh_bindings | ||
|
||
#import bevy_pbr::pbr_types | ||
#import bevy_pbr::utils | ||
#import bevy_pbr::clustered_forward | ||
#import bevy_pbr::lighting | ||
#import bevy_pbr::shadows | ||
#import bevy_pbr::pbr_functions | ||
|
||
[[group(1), binding(0)]] | ||
var my_array_texture: texture_2d_array<f32>; | ||
[[group(1), binding(1)]] | ||
var my_array_texture_sampler: sampler; | ||
|
||
struct FragmentInput { | ||
[[builtin(front_facing)]] is_front: bool; | ||
[[builtin(position)]] frag_coord: vec4<f32>; | ||
[[location(0)]] world_position: vec4<f32>; | ||
[[location(1)]] world_normal: vec3<f32>; | ||
[[location(2)]] uv: vec2<f32>; | ||
#ifdef VERTEX_TANGENTS | ||
[[location(3)]] world_tangent: vec4<f32>; | ||
#endif | ||
#ifdef VERTEX_COLORS | ||
[[location(4)]] color: vec4<f32>; | ||
#endif | ||
}; | ||
|
||
[[stage(fragment)]] | ||
fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> { | ||
let layer = i32(in.world_position.x) & 0x3; | ||
|
||
// Prepare a 'processed' StandardMaterial by sampling all textures to resolve | ||
// the material members | ||
var pbr_input: PbrInput = pbr_input_new(); | ||
|
||
pbr_input.material.base_color = textureSample(my_array_texture, my_array_texture_sampler, in.uv, layer); | ||
#ifdef VERTEX_COLORS | ||
pbr_input.material.base_color = pbr_input.material.base_color * in.color; | ||
#endif | ||
|
||
pbr_input.frag_coord = in.frag_coord; | ||
pbr_input.world_position = in.world_position; | ||
pbr_input.world_normal = in.world_normal; | ||
|
||
pbr_input.is_orthographic = view.projection[3].w == 1.0; | ||
|
||
pbr_input.N = prepare_normal( | ||
pbr_input.material.flags, | ||
in.world_normal, | ||
#ifdef VERTEX_TANGENTS | ||
#ifdef STANDARDMATERIAL_NORMAL_MAP | ||
in.world_tangent, | ||
#endif | ||
#endif | ||
in.uv, | ||
in.is_front, | ||
); | ||
pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic); | ||
|
||
return pbr(pbr_input); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
use bevy::{ | ||
asset::LoadState, | ||
ecs::system::{lifetimeless::SRes, SystemParamItem}, | ||
pbr::MaterialPipeline, | ||
prelude::*, | ||
reflect::TypeUuid, | ||
render::{ | ||
render_asset::{PrepareAssetError, RenderAsset, RenderAssets}, | ||
render_resource::{ | ||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, | ||
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, | ||
SamplerBindingType, ShaderStages, TextureSampleType, TextureViewDimension, | ||
}, | ||
renderer::RenderDevice, | ||
}, | ||
}; | ||
|
||
/// This example illustrates how to create a texture for use with a `texture_2d_array<f32>` shader | ||
/// uniform variable. | ||
fn main() { | ||
App::new() | ||
.add_plugins(DefaultPlugins) | ||
.add_plugin(MaterialPlugin::<ArrayTextureMaterial>::default()) | ||
.add_startup_system(setup) | ||
.add_system(create_array_texture) | ||
.run(); | ||
} | ||
|
||
struct LoadingTexture { | ||
is_loaded: bool, | ||
handle: Handle<Image>, | ||
} | ||
|
||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { | ||
// Start loading the texture. | ||
commands.insert_resource(LoadingTexture { | ||
is_loaded: false, | ||
handle: asset_server.load("textures/array_texture.png"), | ||
}); | ||
|
||
// light | ||
commands.spawn_bundle(PointLightBundle { | ||
point_light: PointLight { | ||
intensity: 3000.0, | ||
..Default::default() | ||
}, | ||
transform: Transform::from_xyz(-3.0, 2.0, -1.0), | ||
..Default::default() | ||
}); | ||
commands.spawn_bundle(PointLightBundle { | ||
point_light: PointLight { | ||
intensity: 3000.0, | ||
..Default::default() | ||
}, | ||
transform: Transform::from_xyz(3.0, 2.0, 1.0), | ||
..Default::default() | ||
}); | ||
|
||
// camera | ||
commands.spawn_bundle(Camera3dBundle { | ||
transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::new(1.5, 0.0, 0.0), Vec3::Y), | ||
..Default::default() | ||
}); | ||
} | ||
|
||
fn create_array_texture( | ||
mut commands: Commands, | ||
asset_server: Res<AssetServer>, | ||
mut loading_texture: ResMut<LoadingTexture>, | ||
mut images: ResMut<Assets<Image>>, | ||
mut meshes: ResMut<Assets<Mesh>>, | ||
mut materials: ResMut<Assets<ArrayTextureMaterial>>, | ||
) { | ||
if loading_texture.is_loaded | ||
|| asset_server.get_load_state(loading_texture.handle.clone()) != LoadState::Loaded | ||
{ | ||
return; | ||
} | ||
loading_texture.is_loaded = true; | ||
let image = images.get_mut(&loading_texture.handle).unwrap(); | ||
|
||
// Create a new array texture asset from the loaded texture. | ||
let array_layers = 4; | ||
image.reinterpret_stacked_2d_as_array(array_layers); | ||
|
||
// Spawn some cubes using the array texture | ||
let mesh_handle = meshes.add(Mesh::from(shape::Cube { size: 1.0 })); | ||
let material_handle = materials.add(ArrayTextureMaterial { | ||
array_texture: loading_texture.handle.clone(), | ||
}); | ||
for x in -5..=5 { | ||
commands.spawn_bundle(MaterialMeshBundle { | ||
mesh: mesh_handle.clone(), | ||
material: material_handle.clone(), | ||
transform: Transform::from_xyz(x as f32 + 0.5, 0.0, 0.0), | ||
..Default::default() | ||
}); | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone, TypeUuid)] | ||
#[uuid = "9c5a0ddf-1eaf-41b4-9832-ed736fd26af3"] | ||
struct ArrayTextureMaterial { | ||
array_texture: Handle<Image>, | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct GpuArrayTextureMaterial { | ||
bind_group: BindGroup, | ||
} | ||
|
||
impl RenderAsset for ArrayTextureMaterial { | ||
type ExtractedAsset = ArrayTextureMaterial; | ||
type PreparedAsset = GpuArrayTextureMaterial; | ||
type Param = ( | ||
SRes<RenderDevice>, | ||
SRes<MaterialPipeline<Self>>, | ||
SRes<RenderAssets<Image>>, | ||
); | ||
fn extract_asset(&self) -> Self::ExtractedAsset { | ||
self.clone() | ||
} | ||
|
||
fn prepare_asset( | ||
extracted_asset: Self::ExtractedAsset, | ||
(render_device, material_pipeline, gpu_images): &mut SystemParamItem<Self::Param>, | ||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> { | ||
let (array_texture_texture_view, array_texture_sampler) = if let Some(result) = | ||
material_pipeline | ||
.mesh_pipeline | ||
.get_image_texture(gpu_images, &Some(extracted_asset.array_texture.clone())) | ||
{ | ||
result | ||
} else { | ||
return Err(PrepareAssetError::RetryNextUpdate(extracted_asset)); | ||
}; | ||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor { | ||
entries: &[ | ||
BindGroupEntry { | ||
binding: 0, | ||
resource: BindingResource::TextureView(array_texture_texture_view), | ||
}, | ||
BindGroupEntry { | ||
binding: 1, | ||
resource: BindingResource::Sampler(array_texture_sampler), | ||
}, | ||
], | ||
label: Some("array_texture_material_bind_group"), | ||
layout: &material_pipeline.material_layout, | ||
}); | ||
|
||
Ok(GpuArrayTextureMaterial { bind_group }) | ||
} | ||
} | ||
|
||
impl Material for ArrayTextureMaterial { | ||
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> { | ||
Some(asset_server.load("shaders/array_texture.wgsl")) | ||
} | ||
|
||
fn bind_group(render_asset: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup { | ||
&render_asset.bind_group | ||
} | ||
|
||
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout { | ||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { | ||
entries: &[ | ||
// Array Texture | ||
BindGroupLayoutEntry { | ||
binding: 0, | ||
visibility: ShaderStages::FRAGMENT, | ||
ty: BindingType::Texture { | ||
multisampled: false, | ||
sample_type: TextureSampleType::Float { filterable: true }, | ||
view_dimension: TextureViewDimension::D2Array, | ||
}, | ||
count: None, | ||
}, | ||
// Array Texture Sampler | ||
BindGroupLayoutEntry { | ||
binding: 1, | ||
visibility: ShaderStages::FRAGMENT, | ||
ty: BindingType::Sampler(SamplerBindingType::Filtering), | ||
count: None, | ||
}, | ||
], | ||
label: None, | ||
}) | ||
} | ||
} |