mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Better Materials: AsBindGroup trait and derive, simpler Material trait (#5053)
# Objective This PR reworks Bevy's Material system, making the user experience of defining Materials _much_ nicer. Bevy's previous material system leaves a lot to be desired: * Materials require manually implementing the `RenderAsset` trait, which involves manually generating the bind group, handling gpu buffer data transfer, looking up image textures, etc. Even the simplest single-texture material involves writing ~80 unnecessary lines of code. This was never the long term plan. * There are two material traits, which is confusing, hard to document, and often redundant: `Material` and `SpecializedMaterial`. `Material` implicitly implements `SpecializedMaterial`, and `SpecializedMaterial` is used in most high level apis to support both use cases. Most users shouldn't need to think about specialization at all (I consider it a "power-user tool"), so the fact that `SpecializedMaterial` is front-and-center in our apis is a miss. * Implementing either material trait involves a lot of "type soup". The "prepared asset" parameter is particularly heinous: `&<Self as RenderAsset>::PreparedAsset`. Defining vertex and fragment shaders is also more verbose than it needs to be. ## Solution Say hello to the new `Material` system: ```rust #[derive(AsBindGroup, TypeUuid, Debug, Clone)] #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] pub struct CoolMaterial { #[uniform(0)] color: Color, #[texture(1)] #[sampler(2)] color_texture: Handle<Image>, } impl Material for CoolMaterial { fn fragment_shader() -> ShaderRef { "cool_material.wgsl".into() } } ``` Thats it! This same material would have required [~80 lines of complicated "type heavy" code](https://github.com/bevyengine/bevy/blob/v0.7.0/examples/shader/shader_material.rs) in the old Material system. Now it is just 14 lines of simple, readable code. This is thanks to a new consolidated `Material` trait and the new `AsBindGroup` trait / derive. ### The new `Material` trait The old "split" `Material` and `SpecializedMaterial` traits have been removed in favor of a new consolidated `Material` trait. All of the functions on the trait are optional. The difficulty of implementing `Material` has been reduced by simplifying dataflow and removing type complexity: ```rust // Old impl Material for CustomMaterial { fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> { Some(asset_server.load("custom_material.wgsl")) } fn alpha_mode(render_asset: &<Self as RenderAsset>::PreparedAsset) -> AlphaMode { render_asset.alpha_mode } } // New impl Material for CustomMaterial { fn fragment_shader() -> ShaderRef { "custom_material.wgsl".into() } fn alpha_mode(&self) -> AlphaMode { self.alpha_mode } } ``` Specialization is still supported, but it is hidden by default under the `specialize()` function (more on this later). ### The `AsBindGroup` trait / derive The `Material` trait now requires the `AsBindGroup` derive. This can be implemented manually relatively easily, but deriving it will almost always be preferable. Field attributes like `uniform` and `texture` are used to define which fields should be bindings, what their binding type is, and what index they should be bound at: ```rust #[derive(AsBindGroup)] struct CoolMaterial { #[uniform(0)] color: Color, #[texture(1)] #[sampler(2)] color_texture: Handle<Image>, } ``` In WGSL shaders, the binding looks like this: ```wgsl struct CoolMaterial { color: vec4<f32>; }; [[group(1), binding(0)]] var<uniform> material: CoolMaterial; [[group(1), binding(1)]] var color_texture: texture_2d<f32>; [[group(1), binding(2)]] var color_sampler: sampler; ``` Note that the "group" index is determined by the usage context. It is not defined in `AsBindGroup`. Bevy material bind groups are bound to group 1. The following field-level attributes are supported: * `uniform(BINDING_INDEX)` * The field will be converted to a shader-compatible type using the `ShaderType` trait, written to a `Buffer`, and bound as a uniform. It can also be derived for custom structs. * `texture(BINDING_INDEX)` * This field's `Handle<Image>` will be used to look up the matching `Texture` gpu resource, which will be bound as a texture in shaders. The field will be assumed to implement `Into<Option<Handle<Image>>>`. In practice, most fields should be a `Handle<Image>` or `Option<Handle<Image>>`. If the value of an `Option<Handle<Image>>` is `None`, the new `FallbackImage` resource will be used instead. This attribute can be used in conjunction with a `sampler` binding attribute (with a different binding index). * `sampler(BINDING_INDEX)` * Behaves exactly like the `texture` attribute, but sets the Image's sampler binding instead of the texture. Note that fields without field-level binding attributes will be ignored. ```rust #[derive(AsBindGroup)] struct CoolMaterial { #[uniform(0)] color: Color, this_field_is_ignored: String, } ``` As mentioned above, `Option<Handle<Image>>` is also supported: ```rust #[derive(AsBindGroup)] struct CoolMaterial { #[uniform(0)] color: Color, #[texture(1)] #[sampler(2)] color_texture: Option<Handle<Image>>, } ``` This is useful if you want a texture to be optional. When the value is `None`, the `FallbackImage` will be used for the binding instead, which defaults to "pure white". Field uniforms with the same binding index will be combined into a single binding: ```rust #[derive(AsBindGroup)] struct CoolMaterial { #[uniform(0)] color: Color, #[uniform(0)] roughness: f32, } ``` In WGSL shaders, the binding would look like this: ```wgsl struct CoolMaterial { color: vec4<f32>; roughness: f32; }; [[group(1), binding(0)]] var<uniform> material: CoolMaterial; ``` Some less common scenarios will require "struct-level" attributes. These are the currently supported struct-level attributes: * `uniform(BINDING_INDEX, ConvertedShaderType)` * Similar to the field-level `uniform` attribute, but instead the entire `AsBindGroup` value is converted to `ConvertedShaderType`, which must implement `ShaderType`. This is useful if more complicated conversion logic is required. * `bind_group_data(DataType)` * The `AsBindGroup` type will be converted to some `DataType` using `Into<DataType>` and stored as `AsBindGroup::Data` as part of the `AsBindGroup::as_bind_group` call. This is useful if data needs to be stored alongside the generated bind group, such as a unique identifier for a material's bind group. The most common use case for this attribute is "shader pipeline specialization". The previous `CoolMaterial` example illustrating "combining multiple field-level uniform attributes with the same binding index" can also be equivalently represented with a single struct-level uniform attribute: ```rust #[derive(AsBindGroup)] #[uniform(0, CoolMaterialUniform)] struct CoolMaterial { color: Color, roughness: f32, } #[derive(ShaderType)] struct CoolMaterialUniform { color: Color, roughness: f32, } impl From<&CoolMaterial> for CoolMaterialUniform { fn from(material: &CoolMaterial) -> CoolMaterialUniform { CoolMaterialUniform { color: material.color, roughness: material.roughness, } } } ``` ### Material Specialization Material shader specialization is now _much_ simpler: ```rust #[derive(AsBindGroup, TypeUuid, Debug, Clone)] #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] #[bind_group_data(CoolMaterialKey)] struct CoolMaterial { #[uniform(0)] color: Color, is_red: bool, } #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct CoolMaterialKey { is_red: bool, } impl From<&CoolMaterial> for CoolMaterialKey { fn from(material: &CoolMaterial) -> CoolMaterialKey { CoolMaterialKey { is_red: material.is_red, } } } impl Material for CoolMaterial { fn fragment_shader() -> ShaderRef { "cool_material.wgsl".into() } fn specialize( pipeline: &MaterialPipeline<Self>, descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayout, key: MaterialPipelineKey<Self>, ) -> Result<(), SpecializedMeshPipelineError> { if key.bind_group_data.is_red { let fragment = descriptor.fragment.as_mut().unwrap(); fragment.shader_defs.push("IS_RED".to_string()); } Ok(()) } } ``` Setting `bind_group_data` is not required for specialization (it defaults to `()`). Scenarios like "custom vertex attributes" also benefit from this system: ```rust impl Material for CustomMaterial { fn vertex_shader() -> ShaderRef { "custom_material.wgsl".into() } fn fragment_shader() -> ShaderRef { "custom_material.wgsl".into() } fn specialize( pipeline: &MaterialPipeline<Self>, descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayout, key: MaterialPipelineKey<Self>, ) -> Result<(), SpecializedMeshPipelineError> { let vertex_layout = layout.get_layout(&[ Mesh::ATTRIBUTE_POSITION.at_shader_location(0), ATTRIBUTE_BLEND_COLOR.at_shader_location(1), ])?; descriptor.vertex.buffers = vec![vertex_layout]; Ok(()) } } ``` ### Ported `StandardMaterial` to the new `Material` system Bevy's built-in PBR material uses the new Material system (including the AsBindGroup derive): ```rust #[derive(AsBindGroup, Debug, Clone, TypeUuid)] #[uuid = "7494888b-c082-457b-aacf-517228cc0c22"] #[bind_group_data(StandardMaterialKey)] #[uniform(0, StandardMaterialUniform)] pub struct StandardMaterial { pub base_color: Color, #[texture(1)] #[sampler(2)] pub base_color_texture: Option<Handle<Image>>, /* other fields omitted for brevity */ ``` ### Ported Bevy examples to the new `Material` system The overall complexity of Bevy's "custom shader examples" has gone down significantly. Take a look at the diffs if you want a dopamine spike. Please note that while this PR has a net increase in "lines of code", most of those extra lines come from added documentation. There is a significant reduction in the overall complexity of the code (even accounting for the new derive logic). --- ## Changelog ### Added * `AsBindGroup` trait and derive, which make it much easier to transfer data to the gpu and generate bind groups for a given type. ### Changed * The old `Material` and `SpecializedMaterial` traits have been replaced by a consolidated (much simpler) `Material` trait. Materials no longer implement `RenderAsset`. * `StandardMaterial` was ported to the new material system. There are no user-facing api changes to the `StandardMaterial` struct api, but it now implements `AsBindGroup` and `Material` instead of `RenderAsset` and `SpecializedMaterial`. ## Migration Guide The Material system has been reworked to be much simpler. We've removed a lot of boilerplate with the new `AsBindGroup` derive and the `Material` trait is simpler as well! ### Bevy 0.7 (old) ```rust #[derive(Debug, Clone, TypeUuid)] #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] pub struct CustomMaterial { color: Color, color_texture: Handle<Image>, } #[derive(Clone)] pub struct GpuCustomMaterial { _buffer: Buffer, bind_group: BindGroup, } impl RenderAsset for CustomMaterial { type ExtractedAsset = CustomMaterial; type PreparedAsset = GpuCustomMaterial; type Param = (SRes<RenderDevice>, SRes<MaterialPipeline<Self>>); fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() } fn prepare_asset( extracted_asset: Self::ExtractedAsset, (render_device, material_pipeline): &mut SystemParamItem<Self::Param>, ) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> { let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32()); let byte_buffer = [0u8; Vec4::SIZE.get() as usize]; let mut buffer = encase::UniformBuffer::new(byte_buffer); buffer.write(&color).unwrap(); let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { contents: buffer.as_ref(), label: None, usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, }); let (texture_view, texture_sampler) = if let Some(result) = material_pipeline .mesh_pipeline .get_image_texture(gpu_images, &Some(extracted_asset.color_texture.clone())) { result } else { return Err(PrepareAssetError::RetryNextUpdate(extracted_asset)); }; let bind_group = render_device.create_bind_group(&BindGroupDescriptor { entries: &[ BindGroupEntry { binding: 0, resource: buffer.as_entire_binding(), }, BindGroupEntry { binding: 0, resource: BindingResource::TextureView(texture_view), }, BindGroupEntry { binding: 1, resource: BindingResource::Sampler(texture_sampler), }, ], label: None, layout: &material_pipeline.material_layout, }); Ok(GpuCustomMaterial { _buffer: buffer, bind_group, }) } } impl Material for CustomMaterial { fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> { Some(asset_server.load("custom_material.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: &[ BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: Some(Vec4::min_size()), }, count: None, }, BindGroupLayoutEntry { binding: 1, visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled: false, sample_type: TextureSampleType::Float { filterable: true }, view_dimension: TextureViewDimension::D2Array, }, count: None, }, BindGroupLayoutEntry { binding: 2, visibility: ShaderStages::FRAGMENT, ty: BindingType::Sampler(SamplerBindingType::Filtering), count: None, }, ], label: None, }) } } ``` ### Bevy 0.8 (new) ```rust impl Material for CustomMaterial { fn fragment_shader() -> ShaderRef { "custom_material.wgsl".into() } } #[derive(AsBindGroup, TypeUuid, Debug, Clone)] #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] pub struct CustomMaterial { #[uniform(0)] color: Color, #[texture(1)] #[sampler(2)] color_texture: Handle<Image>, } ``` ## Future Work * Add support for more binding types (cubemaps, buffers, etc). This PR intentionally includes a bare minimum number of binding types to keep "reviewability" in check. * Consider optionally eliding binding indices using binding names. `AsBindGroup` could pass in (optional?) reflection info as a "hint". * This would make it possible for the derive to do this: ```rust #[derive(AsBindGroup)] pub struct CustomMaterial { #[uniform] color: Color, #[texture] #[sampler] color_texture: Option<Handle<Image>>, alpha_mode: AlphaMode, } ``` * Or this ```rust #[derive(AsBindGroup)] pub struct CustomMaterial { #[binding] color: Color, #[binding] color_texture: Option<Handle<Image>>, alpha_mode: AlphaMode, } ``` * Or even this (if we flip to "include bindings by default") ```rust #[derive(AsBindGroup)] pub struct CustomMaterial { color: Color, color_texture: Option<Handle<Image>>, #[binding(ignore)] alpha_mode: AlphaMode, } ``` * If we add the option to define custom draw functions for materials (which could be done in a type-erased way), I think that would be enough to support extra non-material bindings. Worth considering!
This commit is contained in:
parent
d4e4a92982
commit
747b0c69b0
18 changed files with 1367 additions and 1061 deletions
|
@ -1,10 +1,15 @@
|
|||
struct CustomMaterial {
|
||||
color: vec4<f32>;
|
||||
};
|
||||
|
||||
[[group(1), binding(0)]]
|
||||
var<uniform> material: CustomMaterial;
|
||||
[[group(1), binding(1)]]
|
||||
var base_color_texture: texture_2d<f32>;
|
||||
[[group(1), binding(2)]]
|
||||
var base_color_sampler: sampler;
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fragment() -> [[location(0)]] vec4<f32> {
|
||||
return material.color;
|
||||
fn fragment([[location(2)]] uv: vec2<f32>) -> [[location(0)]] vec4<f32> {
|
||||
return material.color * textureSample(base_color_texture, base_color_sampler, uv);
|
||||
}
|
||||
|
|
|
@ -1,34 +1,15 @@
|
|||
#import bevy_pbr::mesh_types
|
||||
#import bevy_pbr::mesh_view_bindings
|
||||
struct CustomMaterial {
|
||||
color: vec4<f32>;
|
||||
};
|
||||
|
||||
[[group(1), binding(0)]]
|
||||
var<uniform> mesh: Mesh;
|
||||
|
||||
// NOTE: Bindings must come before functions that use them!
|
||||
#import bevy_pbr::mesh_functions
|
||||
|
||||
struct Vertex {
|
||||
[[location(0)]] position: vec3<f32>;
|
||||
[[location(1)]] normal: vec3<f32>;
|
||||
[[location(2)]] uv: vec2<f32>;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
[[builtin(position)]] clip_position: vec4<f32>;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_position = mesh_position_local_to_clip(mesh.model, vec4<f32>(vertex.position, 1.0));
|
||||
return out;
|
||||
}
|
||||
var<uniform> material: CustomMaterial;
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fragment() -> [[location(0)]] vec4<f32> {
|
||||
var color = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
||||
# ifdef IS_RED
|
||||
color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
# endif
|
||||
return color;
|
||||
#ifdef IS_RED
|
||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
#else
|
||||
return material.color;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{DirectionalLight, PointLight, SpecializedMaterial, StandardMaterial};
|
||||
use crate::{DirectionalLight, Material, PointLight, StandardMaterial};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_ecs::{bundle::Bundle, component::Component, reflect::ReflectComponent};
|
||||
use bevy_reflect::Reflect;
|
||||
|
@ -12,9 +12,9 @@ use bevy_transform::components::{GlobalTransform, Transform};
|
|||
/// A component bundle for PBR entities with a [`Mesh`] and a [`StandardMaterial`].
|
||||
pub type PbrBundle = MaterialMeshBundle<StandardMaterial>;
|
||||
|
||||
/// A component bundle for entities with a [`Mesh`] and a [`SpecializedMaterial`].
|
||||
/// A component bundle for entities with a [`Mesh`] and a [`Material`].
|
||||
#[derive(Bundle, Clone)]
|
||||
pub struct MaterialMeshBundle<M: SpecializedMaterial> {
|
||||
pub struct MaterialMeshBundle<M: Material> {
|
||||
pub mesh: Handle<Mesh>,
|
||||
pub material: Handle<M>,
|
||||
pub transform: Transform,
|
||||
|
@ -25,7 +25,7 @@ pub struct MaterialMeshBundle<M: SpecializedMaterial> {
|
|||
pub computed_visibility: ComputedVisibility,
|
||||
}
|
||||
|
||||
impl<M: SpecializedMaterial> Default for MaterialMeshBundle<M> {
|
||||
impl<M: Material> Default for MaterialMeshBundle<M> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mesh: Default::default(),
|
||||
|
|
|
@ -3,248 +3,228 @@ use crate::{
|
|||
SetMeshViewBindGroup,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{AddAsset, Asset, AssetServer, Handle};
|
||||
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
|
||||
use bevy_core_pipeline::core_3d::{AlphaMask3d, Opaque3d, Transparent3d};
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
event::EventReader,
|
||||
prelude::World,
|
||||
schedule::ParallelSystemDescriptorCoercion,
|
||||
system::{
|
||||
lifetimeless::{Read, SQuery, SRes},
|
||||
Query, Res, ResMut, SystemParamItem,
|
||||
Commands, Local, Query, Res, ResMut, SystemParamItem,
|
||||
},
|
||||
world::FromWorld,
|
||||
};
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
extract_component::ExtractComponentPlugin,
|
||||
mesh::{Mesh, MeshVertexBufferLayout},
|
||||
render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets},
|
||||
prelude::Image,
|
||||
render_asset::{PrepareAssetLabel, RenderAssets},
|
||||
render_phase::{
|
||||
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
|
||||
SetItemPipeline, TrackedRenderPass,
|
||||
},
|
||||
render_resource::{
|
||||
BindGroup, BindGroupLayout, PipelineCache, RenderPipelineDescriptor, Shader,
|
||||
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||
AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource,
|
||||
PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline,
|
||||
SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
texture::FallbackImage,
|
||||
view::{ExtractedView, Msaa, VisibleEntities},
|
||||
RenderApp, RenderStage,
|
||||
};
|
||||
use bevy_utils::tracing::error;
|
||||
use bevy_utils::{tracing::error, HashMap, HashSet};
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Materials are used alongside [`MaterialPlugin`] and [`MaterialMeshBundle`](crate::MaterialMeshBundle)
|
||||
/// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level
|
||||
/// way to render [`Mesh`] entities with custom shader logic. For materials that can specialize their [`RenderPipelineDescriptor`]
|
||||
/// based on specific material values, see [`SpecializedMaterial`]. [`Material`] automatically implements [`SpecializedMaterial`]
|
||||
/// and can be used anywhere that type is used (such as [`MaterialPlugin`]).
|
||||
pub trait Material: Asset + RenderAsset + Sized {
|
||||
/// Returns this material's [`BindGroup`]. This should match the layout returned by [`Material::bind_group_layout`].
|
||||
fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup;
|
||||
|
||||
/// Returns this material's [`BindGroupLayout`]. This should match the [`BindGroup`] returned by [`Material::bind_group`].
|
||||
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout;
|
||||
|
||||
/// Returns this material's vertex shader. If [`None`] is returned, the default mesh vertex shader will be used.
|
||||
/// Defaults to [`None`].
|
||||
#[allow(unused_variables)]
|
||||
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
None
|
||||
/// way to render [`Mesh`] entities with custom shader logic.
|
||||
///
|
||||
/// Materials must implement [`AsBindGroup`] to define how data will be transferred to the GPU and bound in shaders.
|
||||
/// [`AsBindGroup`] can be derived, which makes generating bindings straightforward. See the [`AsBindGroup`] docs for details.
|
||||
///
|
||||
/// Materials must also implement [`TypeUuid`] so they can be treated as an [`Asset`](bevy_asset::Asset).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Here is a simple Material implementation. The [`AsBindGroup`] derive has many features. To see what else is available,
|
||||
/// check out the [`AsBindGroup`] documentation.
|
||||
/// ```
|
||||
/// # use bevy_pbr::{Material, MaterialMeshBundle};
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_reflect::TypeUuid;
|
||||
/// # use bevy_render::{render_resource::{AsBindGroup, ShaderRef}, texture::Image, color::Color};
|
||||
/// # use bevy_asset::{Handle, AssetServer, Assets};
|
||||
///
|
||||
/// #[derive(AsBindGroup, TypeUuid, Debug, Clone)]
|
||||
/// #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"]
|
||||
/// pub struct CustomMaterial {
|
||||
/// // Uniform bindings must implement `ShaderType`, which will be used to convert the value to
|
||||
/// // its shader-compatible equivalent. Most core math types already implement `ShaderType`.
|
||||
/// #[uniform(0)]
|
||||
/// color: Color,
|
||||
/// // Images can be bound as textures in shaders. If the Image's sampler is also needed, just
|
||||
/// // add the sampler attribute with a different binding index.
|
||||
/// #[texture(1)]
|
||||
/// #[sampler(2)]
|
||||
/// color_texture: Handle<Image>,
|
||||
/// }
|
||||
///
|
||||
/// // All functions on `Material` have default impls. You only need to implement the
|
||||
/// // functions that are relevant for your material.
|
||||
/// impl Material for CustomMaterial {
|
||||
/// fn fragment_shader() -> ShaderRef {
|
||||
/// "shaders/custom_material.wgsl".into()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Spawn an entity using `CustomMaterial`.
|
||||
/// fn setup(mut commands: Commands, mut materials: ResMut<Assets<CustomMaterial>>, asset_server: Res<AssetServer>) {
|
||||
/// commands.spawn_bundle(MaterialMeshBundle {
|
||||
/// material: materials.add(CustomMaterial {
|
||||
/// color: Color::RED,
|
||||
/// color_texture: asset_server.load("some_image.png"),
|
||||
/// }),
|
||||
/// ..Default::default()
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
/// In WGSL shaders, the material's binding would look like this:
|
||||
///
|
||||
/// ```wgsl
|
||||
/// struct CustomMaterial {
|
||||
/// color: vec4<f32>;
|
||||
/// };
|
||||
///
|
||||
/// [[group(1), binding(0)]]
|
||||
/// var<uniform> material: CustomMaterial;
|
||||
/// [[group(1), binding(1)]]
|
||||
/// var color_texture: texture_2d<f32>;
|
||||
/// [[group(1), binding(2)]]
|
||||
/// var color_sampler: sampler;
|
||||
/// ```
|
||||
pub trait Material: AsBindGroup + Send + Sync + Clone + TypeUuid + Sized + 'static {
|
||||
/// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader
|
||||
/// will be used.
|
||||
fn vertex_shader() -> ShaderRef {
|
||||
ShaderRef::Default
|
||||
}
|
||||
|
||||
/// Returns this material's fragment shader. If [`None`] is returned, the default mesh fragment shader will be used.
|
||||
/// Defaults to [`None`].
|
||||
/// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the default mesh fragment shader
|
||||
/// will be used.
|
||||
#[allow(unused_variables)]
|
||||
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
None
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
ShaderRef::Default
|
||||
}
|
||||
|
||||
/// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`].
|
||||
#[allow(unused_variables)]
|
||||
fn alpha_mode(material: &<Self as RenderAsset>::PreparedAsset) -> AlphaMode {
|
||||
#[inline]
|
||||
fn alpha_mode(&self) -> AlphaMode {
|
||||
AlphaMode::Opaque
|
||||
}
|
||||
|
||||
/// The dynamic uniform indices to set for the given `material`'s [`BindGroup`].
|
||||
/// Defaults to an empty array / no dynamic uniform indices.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] {
|
||||
&[]
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
/// Add a bias to the view depth of the mesh which can be used to force a specific render order
|
||||
/// for meshes with equal depth, to avoid z-fighting.
|
||||
fn depth_bias(material: &<Self as RenderAsset>::PreparedAsset) -> f32 {
|
||||
fn depth_bias(&self) -> f32 {
|
||||
0.0
|
||||
}
|
||||
|
||||
/// Customizes the default [`RenderPipelineDescriptor`].
|
||||
/// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's
|
||||
/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayout`] as input.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
fn specialize(
|
||||
pipeline: &MaterialPipeline<Self>,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
key: MaterialPipelineKey<Self>,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Material> SpecializedMaterial for M {
|
||||
type Key = ();
|
||||
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material`]
|
||||
/// asset type.
|
||||
pub struct MaterialPlugin<M: Material>(PhantomData<M>);
|
||||
|
||||
#[inline]
|
||||
fn key(_material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key {}
|
||||
|
||||
#[inline]
|
||||
fn specialize(
|
||||
pipeline: &MaterialPipeline<Self>,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
_key: Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
<M as Material>::specialize(pipeline, descriptor, layout)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup {
|
||||
<M as Material>::bind_group(material)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||
<M as Material>::bind_group_layout(render_device)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn alpha_mode(material: &<Self as RenderAsset>::PreparedAsset) -> AlphaMode {
|
||||
<M as Material>::alpha_mode(material)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
<M as Material>::vertex_shader(asset_server)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
<M as Material>::fragment_shader(asset_server)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] {
|
||||
<M as Material>::dynamic_uniform_indices(material)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn depth_bias(material: &<Self as RenderAsset>::PreparedAsset) -> f32 {
|
||||
<M as Material>::depth_bias(material)
|
||||
}
|
||||
}
|
||||
|
||||
/// Materials are used alongside [`MaterialPlugin`] and [`MaterialMeshBundle`](crate::MaterialMeshBundle)
|
||||
/// to spawn entities that are rendered with a specific [`SpecializedMaterial`] type. They serve as an easy to use high level
|
||||
/// way to render [`Mesh`] entities with custom shader logic. [`SpecializedMaterials`](SpecializedMaterial) use their [`SpecializedMaterial::Key`]
|
||||
/// to customize their [`RenderPipelineDescriptor`] based on specific material values. The slightly simpler [`Material`] trait
|
||||
/// should be used for materials that do not need specialization. [`Material`] types automatically implement [`SpecializedMaterial`].
|
||||
pub trait SpecializedMaterial: Asset + RenderAsset + Sized {
|
||||
/// The key used to specialize this material's [`RenderPipelineDescriptor`].
|
||||
type Key: PartialEq + Eq + Hash + Clone + Send + Sync;
|
||||
|
||||
/// Extract the [`SpecializedMaterial::Key`] for the "prepared" version of this material. This key will be
|
||||
/// passed in to the [`SpecializedMaterial::specialize`] function when compiling the [`RenderPipeline`](bevy_render::render_resource::RenderPipeline)
|
||||
/// for a given entity's material.
|
||||
fn key(material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key;
|
||||
|
||||
/// Specializes the given `descriptor` according to the given `key`.
|
||||
fn specialize(
|
||||
pipeline: &MaterialPipeline<Self>,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
key: Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<(), SpecializedMeshPipelineError>;
|
||||
|
||||
/// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial::bind_group_layout`].
|
||||
fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup;
|
||||
|
||||
/// Returns this material's [`BindGroupLayout`]. This should match the [`BindGroup`] returned by [`SpecializedMaterial::bind_group`].
|
||||
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout;
|
||||
|
||||
/// Returns this material's vertex shader. If [`None`] is returned, the default mesh vertex shader will be used.
|
||||
/// Defaults to [`None`].
|
||||
#[allow(unused_variables)]
|
||||
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns this material's fragment shader. If [`None`] is returned, the default mesh fragment shader will be used.
|
||||
/// Defaults to [`None`].
|
||||
#[allow(unused_variables)]
|
||||
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`].
|
||||
#[allow(unused_variables)]
|
||||
fn alpha_mode(material: &<Self as RenderAsset>::PreparedAsset) -> AlphaMode {
|
||||
AlphaMode::Opaque
|
||||
}
|
||||
|
||||
/// The dynamic uniform indices to set for the given `material`'s [`BindGroup`].
|
||||
/// Defaults to an empty array / no dynamic uniform indices.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] {
|
||||
&[]
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
/// Add a bias to the view depth of the mesh which can be used to force a specific render order
|
||||
/// for meshes with equal depth, to avoid z-fighting.
|
||||
fn depth_bias(material: &<Self as RenderAsset>::PreparedAsset) -> f32 {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`SpecializedMaterial`]
|
||||
/// asset type (which includes [`Material`] types).
|
||||
pub struct MaterialPlugin<M: SpecializedMaterial>(PhantomData<M>);
|
||||
|
||||
impl<M: SpecializedMaterial> Default for MaterialPlugin<M> {
|
||||
impl<M: Material> Default for MaterialPlugin<M> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: SpecializedMaterial> Plugin for MaterialPlugin<M> {
|
||||
impl<M: Material> Plugin for MaterialPlugin<M>
|
||||
where
|
||||
M::Data: PartialEq + Eq + Hash + Clone,
|
||||
{
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_asset::<M>()
|
||||
.add_plugin(ExtractComponentPlugin::<Handle<M>>::extract_visible())
|
||||
.add_plugin(RenderAssetPlugin::<M>::default());
|
||||
.add_plugin(ExtractComponentPlugin::<Handle<M>>::extract_visible());
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.add_render_command::<Transparent3d, DrawMaterial<M>>()
|
||||
.add_render_command::<Opaque3d, DrawMaterial<M>>()
|
||||
.add_render_command::<AlphaMask3d, DrawMaterial<M>>()
|
||||
.init_resource::<MaterialPipeline<M>>()
|
||||
.init_resource::<ExtractedMaterials<M>>()
|
||||
.init_resource::<RenderMaterials<M>>()
|
||||
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
|
||||
.add_system_to_stage(RenderStage::Extract, extract_materials::<M>)
|
||||
.add_system_to_stage(
|
||||
RenderStage::Prepare,
|
||||
prepare_materials::<M>.after(PrepareAssetLabel::PreAssetPrepare),
|
||||
)
|
||||
.add_system_to_stage(RenderStage::Queue, queue_material_meshes::<M>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Hash)]
|
||||
pub struct MaterialPipelineKey<T> {
|
||||
/// A key uniquely identifying a specialized [`MaterialPipeline`].
|
||||
pub struct MaterialPipelineKey<M: Material> {
|
||||
pub mesh_key: MeshPipelineKey,
|
||||
pub material_key: T,
|
||||
pub bind_group_data: M::Data,
|
||||
}
|
||||
|
||||
pub struct MaterialPipeline<M: SpecializedMaterial> {
|
||||
impl<M: Material> Eq for MaterialPipelineKey<M> where M::Data: PartialEq {}
|
||||
|
||||
impl<M: Material> PartialEq for MaterialPipelineKey<M>
|
||||
where
|
||||
M::Data: PartialEq,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.mesh_key == other.mesh_key && self.bind_group_data == other.bind_group_data
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Material> Clone for MaterialPipelineKey<M>
|
||||
where
|
||||
M::Data: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
mesh_key: self.mesh_key,
|
||||
bind_group_data: self.bind_group_data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Material> Hash for MaterialPipelineKey<M>
|
||||
where
|
||||
M::Data: Hash,
|
||||
{
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.mesh_key.hash(state);
|
||||
self.bind_group_data.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Render pipeline data for a given [`Material`].
|
||||
pub struct MaterialPipeline<M: Material> {
|
||||
pub mesh_pipeline: MeshPipeline,
|
||||
pub material_layout: BindGroupLayout,
|
||||
pub vertex_shader: Option<Handle<Shader>>,
|
||||
|
@ -252,8 +232,11 @@ pub struct MaterialPipeline<M: SpecializedMaterial> {
|
|||
marker: PhantomData<M>,
|
||||
}
|
||||
|
||||
impl<M: SpecializedMaterial> SpecializedMeshPipeline for MaterialPipeline<M> {
|
||||
type Key = MaterialPipelineKey<M::Key>;
|
||||
impl<M: Material> SpecializedMeshPipeline for MaterialPipeline<M>
|
||||
where
|
||||
M::Data: PartialEq + Eq + Hash + Clone,
|
||||
{
|
||||
type Key = MaterialPipelineKey<M>;
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
|
@ -274,22 +257,29 @@ impl<M: SpecializedMaterial> SpecializedMeshPipeline for MaterialPipeline<M> {
|
|||
let descriptor_layout = descriptor.layout.as_mut().unwrap();
|
||||
descriptor_layout.insert(1, self.material_layout.clone());
|
||||
|
||||
M::specialize(self, &mut descriptor, key.material_key, layout)?;
|
||||
M::specialize(self, &mut descriptor, layout, key)?;
|
||||
Ok(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: SpecializedMaterial> FromWorld for MaterialPipeline<M> {
|
||||
impl<M: Material> FromWorld for MaterialPipeline<M> {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let asset_server = world.resource::<AssetServer>();
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
let material_layout = M::bind_group_layout(render_device);
|
||||
|
||||
MaterialPipeline {
|
||||
mesh_pipeline: world.resource::<MeshPipeline>().clone(),
|
||||
material_layout,
|
||||
vertex_shader: M::vertex_shader(asset_server),
|
||||
fragment_shader: M::fragment_shader(asset_server),
|
||||
material_layout: M::bind_group_layout(render_device),
|
||||
vertex_shader: match M::vertex_shader() {
|
||||
ShaderRef::Default => None,
|
||||
ShaderRef::Handle(handle) => Some(handle),
|
||||
ShaderRef::Path(path) => Some(asset_server.load(path)),
|
||||
},
|
||||
fragment_shader: match M::fragment_shader() {
|
||||
ShaderRef::Default => None,
|
||||
ShaderRef::Handle(handle) => Some(handle),
|
||||
ShaderRef::Path(path) => Some(asset_server.load(path)),
|
||||
},
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
@ -303,9 +293,10 @@ type DrawMaterial<M> = (
|
|||
DrawMesh,
|
||||
);
|
||||
|
||||
pub struct SetMaterialBindGroup<M: SpecializedMaterial, const I: usize>(PhantomData<M>);
|
||||
impl<M: SpecializedMaterial, const I: usize> EntityRenderCommand for SetMaterialBindGroup<M, I> {
|
||||
type Param = (SRes<RenderAssets<M>>, SQuery<Read<Handle<M>>>);
|
||||
/// Sets the bind group for a given [`Material`] at the configured `I` index.
|
||||
pub struct SetMaterialBindGroup<M: Material, const I: usize>(PhantomData<M>);
|
||||
impl<M: Material, const I: usize> EntityRenderCommand for SetMaterialBindGroup<M, I> {
|
||||
type Param = (SRes<RenderMaterials<M>>, SQuery<Read<Handle<M>>>);
|
||||
fn render<'w>(
|
||||
_view: Entity,
|
||||
item: Entity,
|
||||
|
@ -314,17 +305,13 @@ impl<M: SpecializedMaterial, const I: usize> EntityRenderCommand for SetMaterial
|
|||
) -> RenderCommandResult {
|
||||
let material_handle = query.get(item).unwrap();
|
||||
let material = materials.into_inner().get(material_handle).unwrap();
|
||||
pass.set_bind_group(
|
||||
I,
|
||||
M::bind_group(material),
|
||||
M::dynamic_uniform_indices(material),
|
||||
);
|
||||
pass.set_bind_group(I, &material.bind_group, &[]);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn queue_material_meshes<M: SpecializedMaterial>(
|
||||
pub fn queue_material_meshes<M: Material>(
|
||||
opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
|
||||
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3d>>,
|
||||
transparent_draw_functions: Res<DrawFunctions<Transparent3d>>,
|
||||
|
@ -333,7 +320,7 @@ pub fn queue_material_meshes<M: SpecializedMaterial>(
|
|||
mut pipeline_cache: ResMut<PipelineCache>,
|
||||
msaa: Res<Msaa>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
render_materials: Res<RenderAssets<M>>,
|
||||
render_materials: Res<RenderMaterials<M>>,
|
||||
material_meshes: Query<(&Handle<M>, &Handle<Mesh>, &MeshUniform)>,
|
||||
mut views: Query<(
|
||||
&ExtractedView,
|
||||
|
@ -342,7 +329,9 @@ pub fn queue_material_meshes<M: SpecializedMaterial>(
|
|||
&mut RenderPhase<AlphaMask3d>,
|
||||
&mut RenderPhase<Transparent3d>,
|
||||
)>,
|
||||
) {
|
||||
) where
|
||||
M::Data: PartialEq + Eq + Hash + Clone,
|
||||
{
|
||||
for (view, visible_entities, mut opaque_phase, mut alpha_mask_phase, mut transparent_phase) in
|
||||
views.iter_mut()
|
||||
{
|
||||
|
@ -372,19 +361,17 @@ pub fn queue_material_meshes<M: SpecializedMaterial>(
|
|||
let mut mesh_key =
|
||||
MeshPipelineKey::from_primitive_topology(mesh.primitive_topology)
|
||||
| msaa_key;
|
||||
let alpha_mode = M::alpha_mode(material);
|
||||
let alpha_mode = material.properties.alpha_mode;
|
||||
if let AlphaMode::Blend = alpha_mode {
|
||||
mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS;
|
||||
}
|
||||
|
||||
let material_key = M::key(material);
|
||||
|
||||
let pipeline_id = pipelines.specialize(
|
||||
&mut pipeline_cache,
|
||||
&material_pipeline,
|
||||
MaterialPipelineKey {
|
||||
mesh_key,
|
||||
material_key,
|
||||
bind_group_data: material.key.clone(),
|
||||
},
|
||||
&mesh.layout,
|
||||
);
|
||||
|
@ -398,8 +385,8 @@ pub fn queue_material_meshes<M: SpecializedMaterial>(
|
|||
|
||||
// NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix
|
||||
// gives the z component of translation of the mesh in view space
|
||||
let bias = M::depth_bias(material);
|
||||
let mesh_z = inverse_view_row_2.dot(mesh_uniform.transform.col(3)) + bias;
|
||||
let mesh_z = inverse_view_row_2.dot(mesh_uniform.transform.col(3))
|
||||
+ material.properties.depth_bias;
|
||||
match alpha_mode {
|
||||
AlphaMode::Opaque => {
|
||||
opaque_phase.add(Opaque3d {
|
||||
|
@ -444,3 +431,159 @@ pub fn queue_material_meshes<M: SpecializedMaterial>(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Common [`Material`] properties, calculated for a specific material instance.
|
||||
pub struct MaterialProperties {
|
||||
/// The [`AlphaMode`] of this material.
|
||||
pub alpha_mode: AlphaMode,
|
||||
/// Add a bias to the view depth of the mesh which can be used to force a specific render order
|
||||
/// for meshes with equal depth, to avoid z-fighting.
|
||||
pub depth_bias: f32,
|
||||
}
|
||||
|
||||
/// Data prepared for a [`Material`] instance.
|
||||
pub struct PreparedMaterial<T: Material> {
|
||||
pub bindings: Vec<OwnedBindingResource>,
|
||||
pub bind_group: BindGroup,
|
||||
pub key: T::Data,
|
||||
pub properties: MaterialProperties,
|
||||
}
|
||||
|
||||
struct ExtractedMaterials<M: Material> {
|
||||
extracted: Vec<(Handle<M>, M)>,
|
||||
removed: Vec<Handle<M>>,
|
||||
}
|
||||
|
||||
impl<M: Material> Default for ExtractedMaterials<M> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
extracted: Default::default(),
|
||||
removed: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores all prepared representations of [`Material`] assets for as long as they exist.
|
||||
pub type RenderMaterials<T> = HashMap<Handle<T>, PreparedMaterial<T>>;
|
||||
|
||||
/// This system extracts all created or modified assets of the corresponding [`Material`] type
|
||||
/// into the "render world".
|
||||
fn extract_materials<M: Material>(
|
||||
mut commands: Commands,
|
||||
mut events: EventReader<AssetEvent<M>>,
|
||||
assets: Res<Assets<M>>,
|
||||
) {
|
||||
let mut changed_assets = HashSet::default();
|
||||
let mut removed = Vec::new();
|
||||
for event in events.iter() {
|
||||
match event {
|
||||
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
|
||||
changed_assets.insert(handle);
|
||||
}
|
||||
AssetEvent::Removed { handle } => {
|
||||
changed_assets.remove(handle);
|
||||
removed.push(handle.clone_weak());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut extracted_assets = Vec::new();
|
||||
for handle in changed_assets.drain() {
|
||||
if let Some(asset) = assets.get(handle) {
|
||||
extracted_assets.push((handle.clone_weak(), asset.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
commands.insert_resource(ExtractedMaterials {
|
||||
extracted: extracted_assets,
|
||||
removed,
|
||||
});
|
||||
}
|
||||
|
||||
/// All [`Material`] values of a given type that should be prepared next frame.
|
||||
pub struct PrepareNextFrameMaterials<M: Material> {
|
||||
assets: Vec<(Handle<M>, M)>,
|
||||
}
|
||||
|
||||
impl<M: Material> Default for PrepareNextFrameMaterials<M> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
assets: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This system prepares all assets of the corresponding [`Material`] type
|
||||
/// which where extracted this frame for the GPU.
|
||||
fn prepare_materials<M: Material>(
|
||||
mut prepare_next_frame: Local<PrepareNextFrameMaterials<M>>,
|
||||
mut extracted_assets: ResMut<ExtractedMaterials<M>>,
|
||||
mut render_materials: ResMut<RenderMaterials<M>>,
|
||||
render_device: Res<RenderDevice>,
|
||||
images: Res<RenderAssets<Image>>,
|
||||
fallback_image: Res<FallbackImage>,
|
||||
pipeline: Res<MaterialPipeline<M>>,
|
||||
) {
|
||||
let mut queued_assets = std::mem::take(&mut prepare_next_frame.assets);
|
||||
for (handle, material) in queued_assets.drain(..) {
|
||||
match prepare_material(
|
||||
&material,
|
||||
&render_device,
|
||||
&images,
|
||||
&fallback_image,
|
||||
&pipeline,
|
||||
) {
|
||||
Ok(prepared_asset) => {
|
||||
render_materials.insert(handle, prepared_asset);
|
||||
}
|
||||
Err(AsBindGroupError::RetryNextUpdate) => {
|
||||
prepare_next_frame.assets.push((handle, material));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for removed in std::mem::take(&mut extracted_assets.removed) {
|
||||
render_materials.remove(&removed);
|
||||
}
|
||||
|
||||
for (handle, material) in std::mem::take(&mut extracted_assets.extracted) {
|
||||
match prepare_material(
|
||||
&material,
|
||||
&render_device,
|
||||
&images,
|
||||
&fallback_image,
|
||||
&pipeline,
|
||||
) {
|
||||
Ok(prepared_asset) => {
|
||||
render_materials.insert(handle, prepared_asset);
|
||||
}
|
||||
Err(AsBindGroupError::RetryNextUpdate) => {
|
||||
prepare_next_frame.assets.push((handle, material));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_material<M: Material>(
|
||||
material: &M,
|
||||
render_device: &RenderDevice,
|
||||
images: &RenderAssets<Image>,
|
||||
fallback_image: &FallbackImage,
|
||||
pipeline: &MaterialPipeline<M>,
|
||||
) -> Result<PreparedMaterial<M>, AsBindGroupError> {
|
||||
let prepared = material.as_bind_group(
|
||||
&pipeline.material_layout,
|
||||
render_device,
|
||||
images,
|
||||
fallback_image,
|
||||
)?;
|
||||
Ok(PreparedMaterial {
|
||||
bindings: prepared.bindings,
|
||||
bind_group: prepared.bind_group,
|
||||
key: prepared.data,
|
||||
properties: MaterialProperties {
|
||||
alpha_mode: material.alpha_mode(),
|
||||
depth_bias: material.depth_bias(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
use crate::{AlphaMode, MaterialPipeline, SpecializedMaterial, PBR_SHADER_HANDLE};
|
||||
use bevy_asset::{AssetServer, Handle};
|
||||
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
|
||||
use crate::{AlphaMode, Material, MaterialPipeline, MaterialPipelineKey, PBR_SHADER_HANDLE};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_math::Vec4;
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
color::Color,
|
||||
mesh::MeshVertexBufferLayout,
|
||||
prelude::Shader,
|
||||
render_asset::{PrepareAssetError, RenderAsset, RenderAssets},
|
||||
render_resource::*,
|
||||
renderer::RenderDevice,
|
||||
color::Color, mesh::MeshVertexBufferLayout, render_asset::RenderAssets, render_resource::*,
|
||||
texture::Image,
|
||||
};
|
||||
|
||||
|
@ -18,17 +12,23 @@ use bevy_render::{
|
|||
/// <https://google.github.io/filament/Material%20Properties.pdf>.
|
||||
///
|
||||
/// May be created directly from a [`Color`] or an [`Image`].
|
||||
#[derive(Debug, Clone, TypeUuid)]
|
||||
#[derive(AsBindGroup, Debug, Clone, TypeUuid)]
|
||||
#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"]
|
||||
#[bind_group_data(StandardMaterialKey)]
|
||||
#[uniform(0, StandardMaterialUniform)]
|
||||
pub struct StandardMaterial {
|
||||
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
|
||||
/// in between. If used together with a base_color_texture, this is factored into the final
|
||||
/// base color as `base_color * base_color_texture_value`
|
||||
pub base_color: Color,
|
||||
#[texture(1)]
|
||||
#[sampler(2)]
|
||||
pub base_color_texture: Option<Handle<Image>>,
|
||||
// Use a color for user friendliness even though we technically don't use the alpha channel
|
||||
// Might be used in the future for exposure correction in HDR
|
||||
pub emissive: Color,
|
||||
#[texture(3)]
|
||||
#[sampler(4)]
|
||||
pub emissive_texture: Option<Handle<Image>>,
|
||||
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
|
||||
/// Defaults to minimum of 0.089
|
||||
|
@ -39,14 +39,20 @@ pub struct StandardMaterial {
|
|||
/// If used together with a roughness/metallic texture, this is factored into the final base
|
||||
/// color as `metallic * metallic_texture_value`
|
||||
pub metallic: f32,
|
||||
#[texture(5)]
|
||||
#[sampler(6)]
|
||||
pub metallic_roughness_texture: Option<Handle<Image>>,
|
||||
/// Specular intensity for non-metals on a linear scale of [0.0, 1.0]
|
||||
/// defaults to 0.5 which is mapped to 4% reflectance in the shader
|
||||
pub reflectance: f32,
|
||||
#[texture(9)]
|
||||
#[sampler(10)]
|
||||
pub normal_map_texture: Option<Handle<Image>>,
|
||||
/// Normal map textures authored for DirectX have their y-component flipped. Set this to flip
|
||||
/// it to right-handed conventions.
|
||||
pub flip_normal_map_y: bool,
|
||||
#[texture(7)]
|
||||
#[sampler(8)]
|
||||
pub occlusion_texture: Option<Handle<Image>>,
|
||||
/// Support two-sided lighting by automatically flipping the normals for "back" faces
|
||||
/// within the PBR lighting shader.
|
||||
|
@ -140,7 +146,7 @@ bitflags::bitflags! {
|
|||
|
||||
/// The GPU representation of the uniform data of a [`StandardMaterial`].
|
||||
#[derive(Clone, Default, ShaderType)]
|
||||
pub struct StandardMaterialUniformData {
|
||||
pub struct StandardMaterialUniform {
|
||||
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
|
||||
/// in between.
|
||||
pub base_color: Vec4,
|
||||
|
@ -161,105 +167,31 @@ pub struct StandardMaterialUniformData {
|
|||
pub alpha_cutoff: f32,
|
||||
}
|
||||
|
||||
/// The GPU representation of a [`StandardMaterial`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GpuStandardMaterial {
|
||||
/// A buffer containing the [`StandardMaterialUniformData`] of the material.
|
||||
pub buffer: Buffer,
|
||||
/// The bind group specifying how the [`StandardMaterialUniformData`] and
|
||||
/// all the textures of the material are bound.
|
||||
pub bind_group: BindGroup,
|
||||
pub has_normal_map: bool,
|
||||
pub flags: StandardMaterialFlags,
|
||||
pub base_color_texture: Option<Handle<Image>>,
|
||||
pub alpha_mode: AlphaMode,
|
||||
pub depth_bias: f32,
|
||||
pub cull_mode: Option<Face>,
|
||||
}
|
||||
|
||||
impl RenderAsset for StandardMaterial {
|
||||
type ExtractedAsset = StandardMaterial;
|
||||
type PreparedAsset = GpuStandardMaterial;
|
||||
type Param = (
|
||||
SRes<RenderDevice>,
|
||||
SRes<MaterialPipeline<StandardMaterial>>,
|
||||
SRes<RenderAssets<Image>>,
|
||||
);
|
||||
|
||||
fn extract_asset(&self) -> Self::ExtractedAsset {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn prepare_asset(
|
||||
material: Self::ExtractedAsset,
|
||||
(render_device, pbr_pipeline, gpu_images): &mut SystemParamItem<Self::Param>,
|
||||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
|
||||
let (base_color_texture_view, base_color_sampler) = if let Some(result) = pbr_pipeline
|
||||
.mesh_pipeline
|
||||
.get_image_texture(gpu_images, &material.base_color_texture)
|
||||
{
|
||||
result
|
||||
} else {
|
||||
return Err(PrepareAssetError::RetryNextUpdate(material));
|
||||
};
|
||||
|
||||
let (emissive_texture_view, emissive_sampler) = if let Some(result) = pbr_pipeline
|
||||
.mesh_pipeline
|
||||
.get_image_texture(gpu_images, &material.emissive_texture)
|
||||
{
|
||||
result
|
||||
} else {
|
||||
return Err(PrepareAssetError::RetryNextUpdate(material));
|
||||
};
|
||||
|
||||
let (metallic_roughness_texture_view, metallic_roughness_sampler) = if let Some(result) =
|
||||
pbr_pipeline
|
||||
.mesh_pipeline
|
||||
.get_image_texture(gpu_images, &material.metallic_roughness_texture)
|
||||
{
|
||||
result
|
||||
} else {
|
||||
return Err(PrepareAssetError::RetryNextUpdate(material));
|
||||
};
|
||||
let (normal_map_texture_view, normal_map_sampler) = if let Some(result) = pbr_pipeline
|
||||
.mesh_pipeline
|
||||
.get_image_texture(gpu_images, &material.normal_map_texture)
|
||||
{
|
||||
result
|
||||
} else {
|
||||
return Err(PrepareAssetError::RetryNextUpdate(material));
|
||||
};
|
||||
let (occlusion_texture_view, occlusion_sampler) = if let Some(result) = pbr_pipeline
|
||||
.mesh_pipeline
|
||||
.get_image_texture(gpu_images, &material.occlusion_texture)
|
||||
{
|
||||
result
|
||||
} else {
|
||||
return Err(PrepareAssetError::RetryNextUpdate(material));
|
||||
};
|
||||
impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
||||
fn as_bind_group_shader_type(&self, images: &RenderAssets<Image>) -> StandardMaterialUniform {
|
||||
let mut flags = StandardMaterialFlags::NONE;
|
||||
if material.base_color_texture.is_some() {
|
||||
if self.base_color_texture.is_some() {
|
||||
flags |= StandardMaterialFlags::BASE_COLOR_TEXTURE;
|
||||
}
|
||||
if material.emissive_texture.is_some() {
|
||||
if self.emissive_texture.is_some() {
|
||||
flags |= StandardMaterialFlags::EMISSIVE_TEXTURE;
|
||||
}
|
||||
if material.metallic_roughness_texture.is_some() {
|
||||
if self.metallic_roughness_texture.is_some() {
|
||||
flags |= StandardMaterialFlags::METALLIC_ROUGHNESS_TEXTURE;
|
||||
}
|
||||
if material.occlusion_texture.is_some() {
|
||||
if self.occlusion_texture.is_some() {
|
||||
flags |= StandardMaterialFlags::OCCLUSION_TEXTURE;
|
||||
}
|
||||
if material.double_sided {
|
||||
if self.double_sided {
|
||||
flags |= StandardMaterialFlags::DOUBLE_SIDED;
|
||||
}
|
||||
if material.unlit {
|
||||
if self.unlit {
|
||||
flags |= StandardMaterialFlags::UNLIT;
|
||||
}
|
||||
let has_normal_map = material.normal_map_texture.is_some();
|
||||
let has_normal_map = self.normal_map_texture.is_some();
|
||||
if has_normal_map {
|
||||
match gpu_images
|
||||
.get(material.normal_map_texture.as_ref().unwrap())
|
||||
match images
|
||||
.get(self.normal_map_texture.as_ref().unwrap())
|
||||
.unwrap()
|
||||
.texture_format
|
||||
{
|
||||
|
@ -272,13 +204,13 @@ impl RenderAsset for StandardMaterial {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
if material.flip_normal_map_y {
|
||||
if self.flip_normal_map_y {
|
||||
flags |= StandardMaterialFlags::FLIP_NORMAL_MAP_Y;
|
||||
}
|
||||
}
|
||||
// NOTE: 0.5 is from the glTF default - do we want this?
|
||||
let mut alpha_cutoff = 0.5;
|
||||
match material.alpha_mode {
|
||||
match self.alpha_mode {
|
||||
AlphaMode::Opaque => flags |= StandardMaterialFlags::ALPHA_MODE_OPAQUE,
|
||||
AlphaMode::Mask(c) => {
|
||||
alpha_cutoff = c;
|
||||
|
@ -287,86 +219,15 @@ impl RenderAsset for StandardMaterial {
|
|||
AlphaMode::Blend => flags |= StandardMaterialFlags::ALPHA_MODE_BLEND,
|
||||
};
|
||||
|
||||
let value = StandardMaterialUniformData {
|
||||
base_color: material.base_color.as_linear_rgba_f32().into(),
|
||||
emissive: material.emissive.into(),
|
||||
roughness: material.perceptual_roughness,
|
||||
metallic: material.metallic,
|
||||
reflectance: material.reflectance,
|
||||
StandardMaterialUniform {
|
||||
base_color: self.base_color.as_linear_rgba_f32().into(),
|
||||
emissive: self.emissive.into(),
|
||||
roughness: self.perceptual_roughness,
|
||||
metallic: self.metallic,
|
||||
reflectance: self.reflectance,
|
||||
flags: flags.bits(),
|
||||
alpha_cutoff,
|
||||
};
|
||||
|
||||
let byte_buffer = [0u8; StandardMaterialUniformData::SIZE.get() as usize];
|
||||
let mut buffer = encase::UniformBuffer::new(byte_buffer);
|
||||
buffer.write(&value).unwrap();
|
||||
|
||||
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
|
||||
label: Some("pbr_standard_material_uniform_buffer"),
|
||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||
contents: buffer.as_ref(),
|
||||
});
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: buffer.as_entire_binding(),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::TextureView(base_color_texture_view),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: BindingResource::Sampler(base_color_sampler),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: BindingResource::TextureView(emissive_texture_view),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 4,
|
||||
resource: BindingResource::Sampler(emissive_sampler),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 5,
|
||||
resource: BindingResource::TextureView(metallic_roughness_texture_view),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 6,
|
||||
resource: BindingResource::Sampler(metallic_roughness_sampler),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 7,
|
||||
resource: BindingResource::TextureView(occlusion_texture_view),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 8,
|
||||
resource: BindingResource::Sampler(occlusion_sampler),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 9,
|
||||
resource: BindingResource::TextureView(normal_map_texture_view),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 10,
|
||||
resource: BindingResource::Sampler(normal_map_sampler),
|
||||
},
|
||||
],
|
||||
label: Some("pbr_standard_material_bind_group"),
|
||||
layout: &pbr_pipeline.material_layout,
|
||||
});
|
||||
|
||||
Ok(GpuStandardMaterial {
|
||||
buffer,
|
||||
bind_group,
|
||||
flags,
|
||||
has_normal_map,
|
||||
base_color_texture: material.base_color_texture,
|
||||
alpha_mode: material.alpha_mode,
|
||||
depth_bias: material.depth_bias,
|
||||
cull_mode: material.cull_mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,23 +237,23 @@ pub struct StandardMaterialKey {
|
|||
cull_mode: Option<Face>,
|
||||
}
|
||||
|
||||
impl SpecializedMaterial for StandardMaterial {
|
||||
type Key = StandardMaterialKey;
|
||||
|
||||
fn key(render_asset: &<Self as RenderAsset>::PreparedAsset) -> Self::Key {
|
||||
impl From<&StandardMaterial> for StandardMaterialKey {
|
||||
fn from(material: &StandardMaterial) -> Self {
|
||||
StandardMaterialKey {
|
||||
normal_map: render_asset.has_normal_map,
|
||||
cull_mode: render_asset.cull_mode,
|
||||
normal_map: material.normal_map_texture.is_some(),
|
||||
cull_mode: material.cull_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for StandardMaterial {
|
||||
fn specialize(
|
||||
_pipeline: &MaterialPipeline<Self>,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
key: Self::Key,
|
||||
_layout: &MeshVertexBufferLayout,
|
||||
key: MaterialPipelineKey<Self>,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
if key.normal_map {
|
||||
if key.bind_group_data.normal_map {
|
||||
descriptor
|
||||
.fragment
|
||||
.as_mut()
|
||||
|
@ -400,139 +261,24 @@ impl SpecializedMaterial for StandardMaterial {
|
|||
.shader_defs
|
||||
.push(String::from("STANDARDMATERIAL_NORMAL_MAP"));
|
||||
}
|
||||
descriptor.primitive.cull_mode = key.cull_mode;
|
||||
descriptor.primitive.cull_mode = key.bind_group_data.cull_mode;
|
||||
if let Some(label) = &mut descriptor.label {
|
||||
*label = format!("pbr_{}", *label).into();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fragment_shader(_asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
Some(PBR_SHADER_HANDLE.typed())
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
PBR_SHADER_HANDLE.typed().into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bind_group(render_asset: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup {
|
||||
&render_asset.bind_group
|
||||
}
|
||||
|
||||
fn bind_group_layout(
|
||||
render_device: &RenderDevice,
|
||||
) -> bevy_render::render_resource::BindGroupLayout {
|
||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(StandardMaterialUniformData::min_size()),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// Base Color Texture
|
||||
BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
multisampled: false,
|
||||
sample_type: TextureSampleType::Float { filterable: true },
|
||||
view_dimension: TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// Base Color Texture Sampler
|
||||
BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
// Emissive Texture
|
||||
BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
multisampled: false,
|
||||
sample_type: TextureSampleType::Float { filterable: true },
|
||||
view_dimension: TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// Emissive Texture Sampler
|
||||
BindGroupLayoutEntry {
|
||||
binding: 4,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
// Metallic Roughness Texture
|
||||
BindGroupLayoutEntry {
|
||||
binding: 5,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
multisampled: false,
|
||||
sample_type: TextureSampleType::Float { filterable: true },
|
||||
view_dimension: TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// Metallic Roughness Texture Sampler
|
||||
BindGroupLayoutEntry {
|
||||
binding: 6,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
// Occlusion Texture
|
||||
BindGroupLayoutEntry {
|
||||
binding: 7,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
multisampled: false,
|
||||
sample_type: TextureSampleType::Float { filterable: true },
|
||||
view_dimension: TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// Occlusion Texture Sampler
|
||||
BindGroupLayoutEntry {
|
||||
binding: 8,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
// Normal Map Texture
|
||||
BindGroupLayoutEntry {
|
||||
binding: 9,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
multisampled: false,
|
||||
sample_type: TextureSampleType::Float { filterable: true },
|
||||
view_dimension: TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// Normal Map Texture Sampler
|
||||
BindGroupLayoutEntry {
|
||||
binding: 10,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("pbr_material_layout"),
|
||||
})
|
||||
fn alpha_mode(&self) -> AlphaMode {
|
||||
self.alpha_mode
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn alpha_mode(render_asset: &<Self as RenderAsset>::PreparedAsset) -> AlphaMode {
|
||||
render_asset.alpha_mode
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn depth_bias(material: &<Self as RenderAsset>::PreparedAsset) -> f32 {
|
||||
material.depth_bias
|
||||
fn depth_bias(&self) -> f32 {
|
||||
self.depth_bias
|
||||
}
|
||||
}
|
||||
|
|
389
crates/bevy_render/macros/src/as_bind_group.rs
Normal file
389
crates/bevy_render/macros/src/as_bind_group.rs
Normal file
|
@ -0,0 +1,389 @@
|
|||
use bevy_macro_utils::BevyManifest;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse::ParseStream, parse_macro_input, token::Comma, Data, DataStruct, DeriveInput, Field,
|
||||
Fields, LitInt,
|
||||
};
|
||||
|
||||
const BINDING_ATTRIBUTE_NAME: &str = "binding";
|
||||
const UNIFORM_ATTRIBUTE_NAME: &str = "uniform";
|
||||
const TEXTURE_ATTRIBUTE_NAME: &str = "texture";
|
||||
const SAMPLER_ATTRIBUTE_NAME: &str = "sampler";
|
||||
const BIND_GROUP_DATA_ATTRIBUTE_NAME: &str = "bind_group_data";
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum BindingType {
|
||||
Uniform,
|
||||
Texture,
|
||||
Sampler,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum BindingState<'a> {
|
||||
Free,
|
||||
Occupied {
|
||||
binding_type: BindingType,
|
||||
ident: &'a Ident,
|
||||
},
|
||||
OccupiedConvertedUniform,
|
||||
OccupiedMergableUniform {
|
||||
uniform_fields: Vec<&'a Field>,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn derive_as_bind_group(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let manifest = BevyManifest::default();
|
||||
let render_path = manifest.get_path("bevy_render");
|
||||
let asset_path = manifest.get_path("bevy_asset");
|
||||
|
||||
let mut binding_states: Vec<BindingState> = Vec::new();
|
||||
let mut binding_impls = Vec::new();
|
||||
let mut bind_group_entries = Vec::new();
|
||||
let mut binding_layouts = Vec::new();
|
||||
let mut attr_prepared_data_ident = None;
|
||||
|
||||
// Read struct-level attributes
|
||||
for attr in &ast.attrs {
|
||||
if let Some(attr_ident) = attr.path.get_ident() {
|
||||
if attr_ident == BIND_GROUP_DATA_ATTRIBUTE_NAME {
|
||||
if let Ok(prepared_data_ident) =
|
||||
attr.parse_args_with(|input: ParseStream| input.parse::<Ident>())
|
||||
{
|
||||
attr_prepared_data_ident = Some(prepared_data_ident);
|
||||
}
|
||||
} else if attr_ident == UNIFORM_ATTRIBUTE_NAME {
|
||||
let (binding_index, converted_shader_type) = attr
|
||||
.parse_args_with(|input: ParseStream| {
|
||||
let binding_index = input
|
||||
.parse::<LitInt>()
|
||||
.and_then(|i| i.base10_parse::<u32>())?;
|
||||
input.parse::<Comma>()?;
|
||||
let converted_shader_type = input.parse::<Ident>()?;
|
||||
Ok((binding_index, converted_shader_type))
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("struct-level uniform bindings must be in the format: uniform(BINDING_INDEX, ConvertedShaderType)");
|
||||
});
|
||||
|
||||
binding_impls.push(quote! {{
|
||||
use #render_path::render_resource::AsBindGroupShaderType;
|
||||
let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());
|
||||
let converted: #converted_shader_type = self.as_bind_group_shader_type(images);
|
||||
buffer.write(&converted).unwrap();
|
||||
#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(
|
||||
&#render_path::render_resource::BufferInitDescriptor {
|
||||
label: None,
|
||||
usage: #render_path::render_resource::BufferUsages::COPY_DST | #render_path::render_resource::BufferUsages::UNIFORM,
|
||||
contents: buffer.as_ref(),
|
||||
},
|
||||
))
|
||||
}});
|
||||
|
||||
binding_layouts.push(quote!{
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
ty: #render_path::render_resource::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
});
|
||||
|
||||
let binding_vec_index = bind_group_entries.len();
|
||||
bind_group_entries.push(quote! {
|
||||
#render_path::render_resource::BindGroupEntry {
|
||||
binding: #binding_index,
|
||||
resource: bindings[#binding_vec_index].get_binding(),
|
||||
}
|
||||
});
|
||||
|
||||
let required_len = binding_index as usize + 1;
|
||||
if required_len > binding_states.len() {
|
||||
binding_states.resize(required_len, BindingState::Free);
|
||||
}
|
||||
binding_states[binding_index as usize] = BindingState::OccupiedConvertedUniform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fields = match &ast.data {
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Named(fields),
|
||||
..
|
||||
}) => &fields.named,
|
||||
_ => panic!("Expected a struct with named fields"),
|
||||
};
|
||||
|
||||
// Read field-level attributes
|
||||
for field in fields.iter() {
|
||||
for attr in &field.attrs {
|
||||
let attr_ident = if let Some(ident) = attr.path.get_ident() {
|
||||
ident
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let binding_type = if attr_ident == UNIFORM_ATTRIBUTE_NAME {
|
||||
BindingType::Uniform
|
||||
} else if attr_ident == TEXTURE_ATTRIBUTE_NAME {
|
||||
BindingType::Texture
|
||||
} else if attr_ident == SAMPLER_ATTRIBUTE_NAME {
|
||||
BindingType::Sampler
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let binding_index = attr
|
||||
.parse_args_with(|input: ParseStream| {
|
||||
let binding_index = input
|
||||
.parse::<LitInt>()
|
||||
.and_then(|i| i.base10_parse::<u32>())
|
||||
.expect("binding index was not a valid u32");
|
||||
Ok(binding_index)
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("Invalid `{}` attribute format", BINDING_ATTRIBUTE_NAME)
|
||||
});
|
||||
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let required_len = binding_index as usize + 1;
|
||||
if required_len > binding_states.len() {
|
||||
binding_states.resize(required_len, BindingState::Free);
|
||||
}
|
||||
|
||||
match &mut binding_states[binding_index as usize] {
|
||||
value @ BindingState::Free => {
|
||||
*value = match binding_type {
|
||||
BindingType::Uniform => BindingState::OccupiedMergableUniform {
|
||||
uniform_fields: vec![field],
|
||||
},
|
||||
_ => {
|
||||
// only populate bind group entries for non-uniforms
|
||||
// uniform entries are deferred until the end
|
||||
let binding_vec_index = bind_group_entries.len();
|
||||
bind_group_entries.push(quote! {
|
||||
#render_path::render_resource::BindGroupEntry {
|
||||
binding: #binding_index,
|
||||
resource: bindings[#binding_vec_index].get_binding(),
|
||||
}
|
||||
});
|
||||
BindingState::Occupied {
|
||||
binding_type,
|
||||
ident: field_name,
|
||||
}},
|
||||
}
|
||||
},
|
||||
BindingState::Occupied { binding_type, ident: occupied_ident} => panic!(
|
||||
"The '{field_name}' field cannot be assigned to binding {binding_index} because it is already occupied by the field '{occupied_ident}' of type {binding_type:?}."
|
||||
),
|
||||
BindingState::OccupiedConvertedUniform => panic!(
|
||||
"The '{field_name}' field cannot be assigned to binding {binding_index} because it is already occupied by a struct-level uniform binding at the same index."
|
||||
),
|
||||
BindingState::OccupiedMergableUniform { uniform_fields } => {
|
||||
match binding_type {
|
||||
BindingType::Uniform => {
|
||||
uniform_fields.push(field);
|
||||
},
|
||||
_ => {panic!("The '{field_name}' field cannot be assigned to binding {binding_index} because it is already occupied by a {:?}.", BindingType::Uniform)},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
match binding_type {
|
||||
BindingType::Uniform => { /* uniform codegen is deferred to account for combined uniform bindings */
|
||||
}
|
||||
BindingType::Texture => {
|
||||
binding_impls.push(quote! {
|
||||
#render_path::render_resource::OwnedBindingResource::TextureView({
|
||||
let handle: Option<&#asset_path::Handle<#render_path::texture::Image>> = (&self.#field_name).into();
|
||||
if let Some(handle) = handle {
|
||||
images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.texture_view.clone()
|
||||
} else {
|
||||
fallback_image.texture_view.clone()
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
binding_layouts.push(quote!{
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Texture {
|
||||
multisampled: false,
|
||||
sample_type: #render_path::render_resource::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: #render_path::render_resource::TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
});
|
||||
}
|
||||
BindingType::Sampler => {
|
||||
binding_impls.push(quote! {
|
||||
#render_path::render_resource::OwnedBindingResource::Sampler({
|
||||
let handle: Option<&#asset_path::Handle<#render_path::texture::Image>> = (&self.#field_name).into();
|
||||
if let Some(handle) = handle {
|
||||
images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.sampler.clone()
|
||||
} else {
|
||||
fallback_image.sampler.clone()
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
binding_layouts.push(quote!{
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Sampler(#render_path::render_resource::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Produce impls for fields with uniform bindings
|
||||
let struct_name = &ast.ident;
|
||||
let mut field_struct_impls = Vec::new();
|
||||
for (binding_index, binding_state) in binding_states.iter().enumerate() {
|
||||
let binding_index = binding_index as u32;
|
||||
if let BindingState::OccupiedMergableUniform { uniform_fields } = binding_state {
|
||||
let binding_vec_index = bind_group_entries.len();
|
||||
bind_group_entries.push(quote! {
|
||||
#render_path::render_resource::BindGroupEntry {
|
||||
binding: #binding_index,
|
||||
resource: bindings[#binding_vec_index].get_binding(),
|
||||
}
|
||||
});
|
||||
// single field uniform bindings for a given index can use a straightforward binding
|
||||
if uniform_fields.len() == 1 {
|
||||
let field = &uniform_fields[0];
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let field_ty = &field.ty;
|
||||
binding_impls.push(quote! {{
|
||||
let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());
|
||||
buffer.write(&self.#field_name).unwrap();
|
||||
#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(
|
||||
&#render_path::render_resource::BufferInitDescriptor {
|
||||
label: None,
|
||||
usage: #render_path::render_resource::BufferUsages::COPY_DST | #render_path::render_resource::BufferUsages::UNIFORM,
|
||||
contents: buffer.as_ref(),
|
||||
},
|
||||
))
|
||||
}});
|
||||
|
||||
binding_layouts.push(quote!{
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
ty: #render_path::render_resource::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()),
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
});
|
||||
// multi-field uniform bindings for a given index require an intermediate struct to derive ShaderType
|
||||
} else {
|
||||
let uniform_struct_name = Ident::new(
|
||||
&format!("_{struct_name}AsBindGroupUniformStructBindGroup{binding_index}"),
|
||||
Span::call_site(),
|
||||
);
|
||||
let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap());
|
||||
let field_type = uniform_fields.iter().map(|f| &f.ty);
|
||||
field_struct_impls.push(quote! {
|
||||
#[derive(#render_path::render_resource::ShaderType)]
|
||||
struct #uniform_struct_name<'a> {
|
||||
#(#field_name: &'a #field_type,)*
|
||||
}
|
||||
});
|
||||
|
||||
let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap());
|
||||
binding_impls.push(quote! {{
|
||||
let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());
|
||||
buffer.write(&#uniform_struct_name {
|
||||
#(#field_name: &self.#field_name,)*
|
||||
}).unwrap();
|
||||
#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(
|
||||
&#render_path::render_resource::BufferInitDescriptor {
|
||||
label: None,
|
||||
usage: #render_path::render_resource::BufferUsages::COPY_DST | #render_path::render_resource::BufferUsages::UNIFORM,
|
||||
contents: buffer.as_ref(),
|
||||
},
|
||||
))
|
||||
}});
|
||||
|
||||
binding_layouts.push(quote!{
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
ty: #render_path::render_resource::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(<#uniform_struct_name as #render_path::render_resource::ShaderType>::min_size()),
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let generics = ast.generics;
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let (prepared_data, get_prepared_data) = if let Some(prepared) = attr_prepared_data_ident {
|
||||
let get_prepared_data = quote! { self.into() };
|
||||
(quote! {#prepared}, get_prepared_data)
|
||||
} else {
|
||||
let prepared_data = quote! { () };
|
||||
(prepared_data.clone(), prepared_data)
|
||||
};
|
||||
|
||||
TokenStream::from(quote! {
|
||||
#(#field_struct_impls)*
|
||||
|
||||
impl #impl_generics #render_path::render_resource::AsBindGroup for #struct_name #ty_generics #where_clause {
|
||||
type Data = #prepared_data;
|
||||
fn as_bind_group(
|
||||
&self,
|
||||
layout: &#render_path::render_resource::BindGroupLayout,
|
||||
render_device: &#render_path::renderer::RenderDevice,
|
||||
images: &#render_path::render_asset::RenderAssets<#render_path::texture::Image>,
|
||||
fallback_image: &#render_path::texture::FallbackImage,
|
||||
) -> Result<#render_path::render_resource::PreparedBindGroup<Self>, #render_path::render_resource::AsBindGroupError> {
|
||||
let bindings = vec![#(#binding_impls,)*];
|
||||
|
||||
let bind_group = {
|
||||
let descriptor = #render_path::render_resource::BindGroupDescriptor {
|
||||
entries: &[#(#bind_group_entries,)*],
|
||||
label: None,
|
||||
layout: &layout,
|
||||
};
|
||||
render_device.create_bind_group(&descriptor)
|
||||
};
|
||||
|
||||
Ok(#render_path::render_resource::PreparedBindGroup {
|
||||
bindings,
|
||||
bind_group,
|
||||
data: #get_prepared_data,
|
||||
})
|
||||
}
|
||||
|
||||
fn bind_group_layout(render_device: &#render_path::renderer::RenderDevice) -> #render_path::render_resource::BindGroupLayout {
|
||||
render_device.create_bind_group_layout(&#render_path::render_resource::BindGroupLayoutDescriptor {
|
||||
entries: &[#(#binding_layouts,)*],
|
||||
label: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod as_bind_group;
|
||||
mod extract_resource;
|
||||
|
||||
use bevy_macro_utils::BevyManifest;
|
||||
|
@ -14,3 +15,8 @@ pub(crate) fn bevy_render_path() -> syn::Path {
|
|||
pub fn derive_extract_resource(input: TokenStream) -> TokenStream {
|
||||
extract_resource::derive_extract_resource(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(AsBindGroup, attributes(uniform, texture, sampler, bind_group_data))]
|
||||
pub fn derive_as_bind_group(input: TokenStream) -> TokenStream {
|
||||
as_bind_group::derive_as_bind_group(input)
|
||||
}
|
||||
|
|
|
@ -1149,6 +1149,73 @@ impl MulAssign<[f32; 3]> for Color {
|
|||
}
|
||||
}
|
||||
|
||||
impl encase::ShaderType for Color {
|
||||
type ExtraMetadata = ();
|
||||
|
||||
const METADATA: encase::private::Metadata<Self::ExtraMetadata> = {
|
||||
let size = encase::private::SizeValue::from(<f32 as encase::private::Size>::SIZE).mul(4);
|
||||
let alignment = encase::private::AlignmentValue::from_next_power_of_two_size(size);
|
||||
|
||||
encase::private::Metadata {
|
||||
alignment,
|
||||
has_uniform_min_alignment: false,
|
||||
min_size: size,
|
||||
extra: (),
|
||||
}
|
||||
};
|
||||
|
||||
const UNIFORM_COMPAT_ASSERT: fn() = || {};
|
||||
}
|
||||
|
||||
impl encase::private::WriteInto for Color {
|
||||
fn write_into<B: encase::private::BufferMut>(&self, writer: &mut encase::private::Writer<B>) {
|
||||
let linear = self.as_linear_rgba_f32();
|
||||
for el in &linear {
|
||||
encase::private::WriteInto::write_into(el, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl encase::private::ReadFrom for Color {
|
||||
fn read_from<B: encase::private::BufferRef>(
|
||||
&mut self,
|
||||
reader: &mut encase::private::Reader<B>,
|
||||
) {
|
||||
let mut buffer = [0.0f32; 4];
|
||||
for el in &mut buffer {
|
||||
encase::private::ReadFrom::read_from(el, reader);
|
||||
}
|
||||
|
||||
*self = Color::RgbaLinear {
|
||||
red: buffer[0],
|
||||
green: buffer[1],
|
||||
blue: buffer[2],
|
||||
alpha: buffer[3],
|
||||
}
|
||||
}
|
||||
}
|
||||
impl encase::private::CreateFrom for Color {
|
||||
fn create_from<B>(reader: &mut encase::private::Reader<B>) -> Self
|
||||
where
|
||||
B: encase::private::BufferRef,
|
||||
{
|
||||
// These are intentionally not inlined in the constructor to make this
|
||||
// resilient to internal Color refactors / implicit type changes.
|
||||
let red: f32 = encase::private::CreateFrom::create_from(reader);
|
||||
let green: f32 = encase::private::CreateFrom::create_from(reader);
|
||||
let blue: f32 = encase::private::CreateFrom::create_from(reader);
|
||||
let alpha: f32 = encase::private::CreateFrom::create_from(reader);
|
||||
Color::RgbaLinear {
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
alpha,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl encase::Size for Color {}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum HexColorError {
|
||||
#[error("Unexpected length of hex string")]
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
pub use bevy_render_macros::AsBindGroup;
|
||||
use encase::ShaderType;
|
||||
|
||||
use crate::{
|
||||
prelude::Image,
|
||||
render_asset::RenderAssets,
|
||||
render_resource::{BindGroupLayout, Buffer, Sampler, TextureView},
|
||||
renderer::RenderDevice,
|
||||
texture::FallbackImage,
|
||||
};
|
||||
use bevy_reflect::Uuid;
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
use wgpu::BindingResource;
|
||||
|
||||
/// A [`BindGroup`] identifier.
|
||||
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
|
@ -42,3 +53,257 @@ impl Deref for BindGroup {
|
|||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a value to a [`BindGroup`] with a given [`BindGroupLayout`], which can then be used in Bevy shaders.
|
||||
/// This trait can be derived (and generally should be). Read on for details and examples.
|
||||
///
|
||||
/// This is an opinionated trait that is intended to make it easy to generically
|
||||
/// convert a type into a [`BindGroup`]. It provides access to specific render resources,
|
||||
/// such as [`RenderAssets<Image>`] and [`FallbackImage`]. If a type has a [`Handle<Image>`](bevy_asset::Handle),
|
||||
/// these can be used to retrieve the corresponding [`Texture`](crate::render_resource::Texture) resource.
|
||||
///
|
||||
/// [`AsBindGroup::as_bind_group`] is intended to be called once, then the result cached somewhere. It is generally
|
||||
/// ok to do "expensive" work here, such as creating a [`Buffer`] for a uniform.
|
||||
///
|
||||
/// If for some reason a [`BindGroup`] cannot be created yet (for example, the [`Texture`](crate::render_resource::Texture)
|
||||
/// for an [`Image`] hasn't loaded yet), just return [`AsBindGroupError::RetryNextUpdate`], which signals that the caller
|
||||
/// should retry again later.
|
||||
///
|
||||
/// # Deriving
|
||||
///
|
||||
/// This trait can be derived. Field attributes like `uniform` and `texture` are used to define which fields should be bindings,
|
||||
/// what their binding type is, and what index they should be bound at:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_render::{color::Color, render_resource::*, texture::Image};
|
||||
/// # use bevy_asset::Handle;
|
||||
/// #[derive(AsBindGroup)]
|
||||
/// struct CoolMaterial {
|
||||
/// #[uniform(0)]
|
||||
/// color: Color,
|
||||
/// #[texture(1)]
|
||||
/// #[sampler(2)]
|
||||
/// color_texture: Handle<Image>,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// In WGSL shaders, the binding would look like this:
|
||||
///
|
||||
/// ```wgsl
|
||||
/// struct CoolMaterial {
|
||||
/// color: vec4<f32>;
|
||||
/// };
|
||||
///
|
||||
/// [[group(1), binding(0)]]
|
||||
/// var<uniform> material: CoolMaterial;
|
||||
/// [[group(1), binding(1)]]
|
||||
/// var color_texture: texture_2d<f32>;
|
||||
/// [[group(1), binding(2)]]
|
||||
/// var color_sampler: sampler;
|
||||
/// ```
|
||||
/// Note that the "group" index is determined by the usage context. It is not defined in [`AsBindGroup`]. For example, in Bevy material bind groups
|
||||
/// are generally bound to group 1.
|
||||
///
|
||||
/// The following field-level attributes are supported:
|
||||
/// * `uniform(BINDING_INDEX)`
|
||||
/// * The field will be converted to a shader-compatible type using the [`ShaderType`] trait, written to a [`Buffer`], and bound as a uniform.
|
||||
/// [`ShaderType`] is implemented for most math types already, such as [`f32`], [`Vec4`](bevy_math::Vec4), and
|
||||
/// [`Color`](crate::color::Color). It can also be derived for custom structs.
|
||||
/// * `texture(BINDING_INDEX)`
|
||||
/// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture)
|
||||
/// GPU resource, which will be bound as a texture in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
|
||||
/// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
|
||||
/// [`None`], the [`FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `sampler` binding attribute
|
||||
/// (with a different binding index) if a binding of the sampler for the [`Image`] is also required.
|
||||
/// * `sampler(BINDING_INDEX)`
|
||||
/// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Sampler`](crate::render_resource::Sampler) GPU
|
||||
/// resource, which will be bound as a sampler in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
|
||||
/// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
|
||||
/// [`None`], the [`FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `texture` binding attribute
|
||||
/// (with a different binding index) if a binding of the texture for the [`Image`] is also required.
|
||||
///
|
||||
/// Note that fields without field-level binding attributes will be ignored.
|
||||
/// ```
|
||||
/// # use bevy_render::{color::Color, render_resource::AsBindGroup};
|
||||
/// # use bevy_asset::Handle;
|
||||
/// #[derive(AsBindGroup)]
|
||||
/// struct CoolMaterial {
|
||||
/// #[uniform(0)]
|
||||
/// color: Color,
|
||||
/// this_field_is_ignored: String,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// As mentioned above, [`Option<Handle<Image>>`] is also supported:
|
||||
/// ```
|
||||
/// # use bevy_render::{color::Color, render_resource::AsBindGroup, texture::Image};
|
||||
/// # use bevy_asset::Handle;
|
||||
/// #[derive(AsBindGroup)]
|
||||
/// struct CoolMaterial {
|
||||
/// #[uniform(0)]
|
||||
/// color: Color,
|
||||
/// #[texture(1)]
|
||||
/// #[sampler(2)]
|
||||
/// color_texture: Option<Handle<Image>>,
|
||||
/// }
|
||||
/// ```
|
||||
/// This is useful if you want a texture to be optional. When the value is [`None`], the [`FallbackImage`] will be used for the binding instead, which defaults
|
||||
/// to "pure white".
|
||||
///
|
||||
/// Field uniforms with the same index will be combined into a single binding:
|
||||
/// ```
|
||||
/// # use bevy_render::{color::Color, render_resource::AsBindGroup};
|
||||
/// #[derive(AsBindGroup)]
|
||||
/// struct CoolMaterial {
|
||||
/// #[uniform(0)]
|
||||
/// color: Color,
|
||||
/// #[uniform(0)]
|
||||
/// roughness: f32,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// In WGSL shaders, the binding would look like this:
|
||||
/// ```wgsl
|
||||
/// struct CoolMaterial {
|
||||
/// color: vec4<f32>;
|
||||
/// roughness: f32;
|
||||
/// };
|
||||
///
|
||||
/// [[group(1), binding(0)]]
|
||||
/// var<uniform> material: CoolMaterial;
|
||||
/// ```
|
||||
///
|
||||
/// Some less common scenarios will require "struct-level" attributes. These are the currently supported struct-level attributes:
|
||||
/// * `uniform(BINDING_INDEX, ConvertedShaderType)`
|
||||
/// * This also creates a [`Buffer`] using [`ShaderType`] and binds it as a uniform, much
|
||||
/// much like the field-level `uniform` attribute. The difference is that the entire [`AsBindGroup`] value is converted to `ConvertedShaderType`,
|
||||
/// which must implement [`ShaderType`], instead of a specific field implementing [`ShaderType`]. This is useful if more complicated conversion
|
||||
/// logic is required. The conversion is done using the [`AsBindGroupShaderType<ConvertedShaderType>`] trait, which is automatically implemented
|
||||
/// if `&Self` implements [`Into<ConvertedShaderType>`]. Only use [`AsBindGroupShaderType`] if access to resources like [`RenderAssets<Image>`] is
|
||||
/// required.
|
||||
/// * `bind_group_data(DataType)`
|
||||
/// * The [`AsBindGroup`] type will be converted to some `DataType` using [`Into<DataType>`] and stored
|
||||
/// as [`AsBindGroup::Data`] as part of the [`AsBindGroup::as_bind_group`] call. This is useful if data needs to be stored alongside
|
||||
/// the generated bind group, such as a unique identifier for a material's bind group. The most common use case for this attribute
|
||||
/// is "shader pipeline specialization". See [`SpecializedRenderPipeline`](crate::render_resource::SpecializedRenderPipeline).
|
||||
///
|
||||
/// The previous `CoolMaterial` example illustrating "combining multiple field-level uniform attributes with the same binding index" can
|
||||
/// also be equivalently represented with a single struct-level uniform attribute:
|
||||
/// ```
|
||||
/// # use bevy_render::{color::Color, render_resource::{AsBindGroup, ShaderType}};
|
||||
/// #[derive(AsBindGroup)]
|
||||
/// #[uniform(0, CoolMaterialUniform)]
|
||||
/// struct CoolMaterial {
|
||||
/// color: Color,
|
||||
/// roughness: f32,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(ShaderType)]
|
||||
/// struct CoolMaterialUniform {
|
||||
/// color: Color,
|
||||
/// roughness: f32,
|
||||
/// }
|
||||
///
|
||||
/// impl From<&CoolMaterial> for CoolMaterialUniform {
|
||||
/// fn from(material: &CoolMaterial) -> CoolMaterialUniform {
|
||||
/// CoolMaterialUniform {
|
||||
/// color: material.color,
|
||||
/// roughness: material.roughness,
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Setting `bind_group_data` looks like this:
|
||||
/// ```
|
||||
/// # use bevy_render::{color::Color, render_resource::AsBindGroup};
|
||||
/// #[derive(AsBindGroup)]
|
||||
/// #[bind_group_data(CoolMaterialKey)]
|
||||
/// struct CoolMaterial {
|
||||
/// #[uniform(0)]
|
||||
/// color: Color,
|
||||
/// is_shaded: bool,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Copy, Clone, Hash, Eq, PartialEq)]
|
||||
/// struct CoolMaterialKey {
|
||||
/// is_shaded: bool,
|
||||
/// }
|
||||
///
|
||||
/// impl From<&CoolMaterial> for CoolMaterialKey {
|
||||
/// fn from(material: &CoolMaterial) -> CoolMaterialKey {
|
||||
/// CoolMaterialKey {
|
||||
/// is_shaded: material.is_shaded,
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait AsBindGroup: Sized {
|
||||
/// Data that will be stored alongside the "prepared" bind group.
|
||||
type Data: Send + Sync;
|
||||
|
||||
/// Creates a bind group for `self` matching the layout defined in [`AsBindGroup::bind_group_layout`].
|
||||
fn as_bind_group(
|
||||
&self,
|
||||
layout: &BindGroupLayout,
|
||||
render_device: &RenderDevice,
|
||||
images: &RenderAssets<Image>,
|
||||
fallback_image: &FallbackImage,
|
||||
) -> Result<PreparedBindGroup<Self>, AsBindGroupError>;
|
||||
|
||||
/// Creates the bind group layout matching all bind groups returned by [`AsBindGroup::as_bind_group`]
|
||||
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout;
|
||||
}
|
||||
|
||||
/// An error that occurs during [`AsBindGroup::as_bind_group`] calls.
|
||||
pub enum AsBindGroupError {
|
||||
/// The bind group could not be generated. Try again next frame.
|
||||
RetryNextUpdate,
|
||||
}
|
||||
|
||||
/// A prepared bind group returned as a result of [`AsBindGroup::as_bind_group`].
|
||||
pub struct PreparedBindGroup<T: AsBindGroup> {
|
||||
pub bindings: Vec<OwnedBindingResource>,
|
||||
pub bind_group: BindGroup,
|
||||
pub data: T::Data,
|
||||
}
|
||||
|
||||
/// An owned binding resource of any type (ex: a [`Buffer`], [`TextureView`], etc).
|
||||
/// This is used by types like [`PreparedBindGroup`] to hold a single list of all
|
||||
/// render resources used by bindings.
|
||||
pub enum OwnedBindingResource {
|
||||
Buffer(Buffer),
|
||||
TextureView(TextureView),
|
||||
Sampler(Sampler),
|
||||
}
|
||||
|
||||
impl OwnedBindingResource {
|
||||
pub fn get_binding(&self) -> BindingResource {
|
||||
match self {
|
||||
OwnedBindingResource::Buffer(buffer) => buffer.as_entire_binding(),
|
||||
OwnedBindingResource::TextureView(view) => BindingResource::TextureView(view),
|
||||
OwnedBindingResource::Sampler(sampler) => BindingResource::Sampler(sampler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a value to a [`ShaderType`] for use in a bind group.
|
||||
/// This is automatically implemented for references that implement [`Into`].
|
||||
/// Generally normal [`Into`] / [`From`] impls should be preferred, but
|
||||
/// sometimes additional runtime metadata is required.
|
||||
/// This exists largely to make some [`AsBindGroup`] use cases easier.
|
||||
pub trait AsBindGroupShaderType<T: ShaderType> {
|
||||
/// Return the `T` [`ShaderType`] for `self`. When used in [`AsBindGroup`]
|
||||
/// derives, it is safe to assume that all images in `self` exist.
|
||||
fn as_bind_group_shader_type(&self, images: &RenderAssets<Image>) -> T;
|
||||
}
|
||||
|
||||
impl<T, U: ShaderType> AsBindGroupShaderType<U> for T
|
||||
where
|
||||
for<'a> &'a T: Into<U>,
|
||||
{
|
||||
#[inline]
|
||||
fn as_bind_group_shader_type(&self, _images: &RenderAssets<Image>) -> U {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use bevy_asset::{AssetLoader, Handle, LoadContext, LoadedAsset};
|
||||
use bevy_asset::{AssetLoader, AssetPath, Handle, LoadContext, LoadedAsset};
|
||||
use bevy_reflect::{TypeUuid, Uuid};
|
||||
use bevy_utils::{tracing::error, BoxedFuture, HashMap};
|
||||
use naga::back::wgsl::WriterFlags;
|
||||
|
@ -518,6 +518,34 @@ impl ShaderProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
/// A reference to a shader asset.
|
||||
pub enum ShaderRef {
|
||||
/// Use the "default" shader for the current context.
|
||||
Default,
|
||||
/// A handle to a shader stored in the [`Assets<Shader>`](bevy_asset::Assets) resource
|
||||
Handle(Handle<Shader>),
|
||||
/// An asset path leading to a shader
|
||||
Path(AssetPath<'static>),
|
||||
}
|
||||
|
||||
impl From<Handle<Shader>> for ShaderRef {
|
||||
fn from(handle: Handle<Shader>) -> Self {
|
||||
Self::Handle(handle)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AssetPath<'static>> for ShaderRef {
|
||||
fn from(path: AssetPath<'static>) -> Self {
|
||||
Self::Path(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for ShaderRef {
|
||||
fn from(path: &'static str) -> Self {
|
||||
Self::Path(AssetPath::from(path))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_asset::{Handle, HandleUntyped};
|
||||
|
|
52
crates/bevy_render/src/texture/fallback_image.rs
Normal file
52
crates/bevy_render/src/texture/fallback_image.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use crate::{render_resource::*, texture::DefaultImageSampler};
|
||||
use bevy_derive::Deref;
|
||||
use bevy_ecs::prelude::FromWorld;
|
||||
use bevy_math::Vec2;
|
||||
use wgpu::{Extent3d, TextureDimension, TextureFormat};
|
||||
|
||||
use crate::{
|
||||
prelude::Image,
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
texture::{BevyDefault, GpuImage, ImageSampler},
|
||||
};
|
||||
|
||||
/// A [`RenderApp`](crate::RenderApp) resource that contains the default "fallback image",
|
||||
/// which can be used in situations where an image was not explicitly defined. The most common
|
||||
/// use case is [`AsBindGroup`] implementations (such as materials) that support optional textures.
|
||||
/// [`FallbackImage`] defaults to a 1x1 fully white texture, making blending colors with it a no-op.
|
||||
#[derive(Deref)]
|
||||
pub struct FallbackImage(GpuImage);
|
||||
|
||||
impl FromWorld for FallbackImage {
|
||||
fn from_world(world: &mut bevy_ecs::prelude::World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
let render_queue = world.resource::<RenderQueue>();
|
||||
let default_sampler = world.resource::<DefaultImageSampler>();
|
||||
let image = Image::new_fill(
|
||||
Extent3d::default(),
|
||||
TextureDimension::D2,
|
||||
&[255u8; 4],
|
||||
TextureFormat::bevy_default(),
|
||||
);
|
||||
let texture = render_device.create_texture_with_data(
|
||||
render_queue,
|
||||
&image.texture_descriptor,
|
||||
&image.data,
|
||||
);
|
||||
let texture_view = texture.create_view(&TextureViewDescriptor::default());
|
||||
let sampler = match image.sampler_descriptor {
|
||||
ImageSampler::Default => (**default_sampler).clone(),
|
||||
ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor),
|
||||
};
|
||||
Self(GpuImage {
|
||||
texture,
|
||||
texture_view,
|
||||
texture_format: image.texture_descriptor.format,
|
||||
sampler,
|
||||
size: Vec2::new(
|
||||
image.texture_descriptor.size.width as f32,
|
||||
image.texture_descriptor.size.height as f32,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
mod basis;
|
||||
#[cfg(feature = "dds")]
|
||||
mod dds;
|
||||
mod fallback_image;
|
||||
#[cfg(feature = "hdr")]
|
||||
mod hdr_texture_loader;
|
||||
#[allow(clippy::module_inception)]
|
||||
|
@ -21,6 +22,7 @@ pub use dds::*;
|
|||
#[cfg(feature = "hdr")]
|
||||
pub use hdr_texture_loader::*;
|
||||
|
||||
pub use fallback_image::*;
|
||||
pub use image_texture_loader::*;
|
||||
pub use texture_cache::*;
|
||||
|
||||
|
@ -77,6 +79,7 @@ impl Plugin for ImagePlugin {
|
|||
render_app
|
||||
.insert_resource(DefaultImageSampler(default_sampler))
|
||||
.init_resource::<TextureCache>()
|
||||
.init_resource::<FallbackImage>()
|
||||
.add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use bevy::{
|
||||
asset::LoadState,
|
||||
ecs::system::{lifetimeless::SRes, SystemParamItem},
|
||||
pbr::MaterialPipeline,
|
||||
prelude::*,
|
||||
reflect::TypeUuid,
|
||||
render::{
|
||||
render_asset::{PrepareAssetError, RenderAsset, RenderAssets},
|
||||
render_asset::RenderAssets,
|
||||
render_resource::{
|
||||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
|
||||
AsBindGroup, AsBindGroupError, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
|
||||
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType,
|
||||
SamplerBindingType, ShaderStages, TextureSampleType, TextureViewDimension,
|
||||
OwnedBindingResource, PreparedBindGroup, SamplerBindingType, ShaderRef, ShaderStages,
|
||||
TextureSampleType, TextureViewDimension,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
texture::FallbackImage,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -104,62 +104,48 @@ struct ArrayTextureMaterial {
|
|||
array_texture: Handle<Image>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GpuArrayTextureMaterial {
|
||||
bind_group: BindGroup,
|
||||
impl Material for ArrayTextureMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/array_texture.wgsl".into()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
impl AsBindGroup for ArrayTextureMaterial {
|
||||
type Data = ();
|
||||
|
||||
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));
|
||||
};
|
||||
fn as_bind_group(
|
||||
&self,
|
||||
layout: &BindGroupLayout,
|
||||
render_device: &RenderDevice,
|
||||
images: &RenderAssets<Image>,
|
||||
_fallback_image: &FallbackImage,
|
||||
) -> Result<PreparedBindGroup<Self>, AsBindGroupError> {
|
||||
let image = images
|
||||
.get(&self.array_texture)
|
||||
.ok_or(AsBindGroupError::RetryNextUpdate)?;
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureView(array_texture_texture_view),
|
||||
resource: BindingResource::TextureView(&image.texture_view),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::Sampler(array_texture_sampler),
|
||||
resource: BindingResource::Sampler(&image.sampler),
|
||||
},
|
||||
],
|
||||
label: Some("array_texture_material_bind_group"),
|
||||
layout: &material_pipeline.material_layout,
|
||||
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
|
||||
Ok(PreparedBindGroup {
|
||||
bind_group,
|
||||
bindings: vec![
|
||||
OwnedBindingResource::TextureView(image.texture_view.clone()),
|
||||
OwnedBindingResource::Sampler(image.sampler.clone()),
|
||||
],
|
||||
data: (),
|
||||
})
|
||||
}
|
||||
|
||||
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||
|
|
|
@ -1,20 +1,15 @@
|
|||
//! A shader that reads a mesh's custom vertex attribute.
|
||||
|
||||
use bevy::{
|
||||
ecs::system::{lifetimeless::SRes, SystemParamItem},
|
||||
pbr::MaterialPipeline,
|
||||
pbr::{MaterialPipeline, MaterialPipelineKey},
|
||||
prelude::*,
|
||||
reflect::TypeUuid,
|
||||
render::{
|
||||
mesh::{MeshVertexAttribute, MeshVertexBufferLayout},
|
||||
render_asset::{PrepareAssetError, RenderAsset},
|
||||
render_resource::{
|
||||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
|
||||
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, Buffer,
|
||||
BufferBindingType, BufferInitDescriptor, BufferUsages, RenderPipelineDescriptor,
|
||||
ShaderSize, ShaderStages, ShaderType, SpecializedMeshPipelineError, VertexFormat,
|
||||
AsBindGroup, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError,
|
||||
VertexFormat,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -62,90 +57,26 @@ fn setup(
|
|||
}
|
||||
|
||||
// This is the struct that will be passed to your shader
|
||||
#[derive(Debug, Clone, TypeUuid)]
|
||||
#[derive(AsBindGroup, Debug, Clone, TypeUuid)]
|
||||
#[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"]
|
||||
pub struct CustomMaterial {
|
||||
#[uniform(0)]
|
||||
color: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GpuCustomMaterial {
|
||||
_buffer: Buffer,
|
||||
bind_group: BindGroup,
|
||||
}
|
||||
|
||||
// The implementation of [`Material`] needs this impl to work properly.
|
||||
impl RenderAsset for CustomMaterial {
|
||||
type ExtractedAsset = CustomMaterial;
|
||||
type PreparedAsset = GpuCustomMaterial;
|
||||
type Param = (SRes<RenderDevice>, SRes<MaterialPipeline<Self>>);
|
||||
fn extract_asset(&self) -> Self::ExtractedAsset {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn prepare_asset(
|
||||
extracted_asset: Self::ExtractedAsset,
|
||||
(render_device, material_pipeline): &mut SystemParamItem<Self::Param>,
|
||||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
|
||||
let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32());
|
||||
|
||||
let byte_buffer = [0u8; Vec4::SIZE.get() as usize];
|
||||
let mut buffer = bevy::render::render_resource::encase::UniformBuffer::new(byte_buffer);
|
||||
buffer.write(&color).unwrap();
|
||||
|
||||
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
|
||||
contents: buffer.as_ref(),
|
||||
label: None,
|
||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||
});
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: buffer.as_entire_binding(),
|
||||
}],
|
||||
label: None,
|
||||
layout: &material_pipeline.material_layout,
|
||||
});
|
||||
|
||||
Ok(GpuCustomMaterial {
|
||||
_buffer: buffer,
|
||||
bind_group,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for CustomMaterial {
|
||||
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
Some(asset_server.load("shaders/custom_vertex_attribute.wgsl"))
|
||||
fn vertex_shader() -> ShaderRef {
|
||||
"shaders/custom_vertex_attribute.wgsl".into()
|
||||
}
|
||||
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
Some(asset_server.load("shaders/custom_vertex_attribute.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: &[BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(Vec4::min_size()),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: None,
|
||||
})
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/custom_vertex_attribute.wgsl".into()
|
||||
}
|
||||
|
||||
fn specialize(
|
||||
_pipeline: &MaterialPipeline<Self>,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
_key: MaterialPipelineKey<Self>,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
let vertex_layout = layout.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
|
|
|
@ -1,81 +1,52 @@
|
|||
//! A shader that uses "shaders defs" (a bevy tool to selectively toggle parts of a shader)
|
||||
//! A shader that uses "shaders defs", which selectively toggle parts of a shader.
|
||||
|
||||
use bevy::{
|
||||
core_pipeline::core_3d::Transparent3d,
|
||||
pbr::{
|
||||
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup,
|
||||
SetMeshViewBindGroup,
|
||||
},
|
||||
pbr::{MaterialPipeline, MaterialPipelineKey},
|
||||
prelude::*,
|
||||
reflect::TypeUuid,
|
||||
render::{
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::{
|
||||
PipelineCache, RenderPipelineDescriptor, SpecializedMeshPipeline,
|
||||
SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||
AsBindGroup, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError,
|
||||
},
|
||||
view::ExtractedView,
|
||||
RenderApp, RenderStage,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct IsRedPlugin;
|
||||
|
||||
impl Plugin for IsRedPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugin(ExtractComponentPlugin::<IsRed>::default());
|
||||
app.sub_app_mut(RenderApp)
|
||||
.add_render_command::<Transparent3d, DrawIsRed>()
|
||||
.init_resource::<IsRedPipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<IsRedPipeline>>()
|
||||
.add_system_to_stage(RenderStage::Queue, queue_custom);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_plugin(IsRedPlugin)
|
||||
.add_plugin(MaterialPlugin::<CustomMaterial>::default())
|
||||
.add_startup_system(setup)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Component, Hash, PartialEq, Eq, Copy, Clone)]
|
||||
struct IsRed(bool);
|
||||
|
||||
impl ExtractComponent for IsRed {
|
||||
type Query = &'static IsRed;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
fn extract_component(item: bevy::ecs::query::QueryItem<Self::Query>) -> Self {
|
||||
*item
|
||||
}
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
|
||||
// red cube
|
||||
commands.spawn().insert_bundle((
|
||||
meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
IsRed(true),
|
||||
Transform::from_xyz(-1.0, 0.5, 0.0),
|
||||
GlobalTransform::default(),
|
||||
Visibility::default(),
|
||||
ComputedVisibility::default(),
|
||||
));
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<CustomMaterial>>,
|
||||
) {
|
||||
// blue cube
|
||||
commands.spawn().insert_bundle((
|
||||
meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
IsRed(false),
|
||||
Transform::from_xyz(1.0, 0.5, 0.0),
|
||||
GlobalTransform::default(),
|
||||
Visibility::default(),
|
||||
ComputedVisibility::default(),
|
||||
));
|
||||
commands.spawn().insert_bundle(MaterialMeshBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
transform: Transform::from_xyz(-1.0, 0.5, 0.0),
|
||||
material: materials.add(CustomMaterial {
|
||||
color: Color::BLUE,
|
||||
is_red: false,
|
||||
}),
|
||||
..default()
|
||||
});
|
||||
|
||||
// red cube (with green color overridden by the IS_RED "shader def")
|
||||
commands.spawn().insert_bundle(MaterialMeshBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
transform: Transform::from_xyz(1.0, 0.5, 0.0),
|
||||
material: materials.add(CustomMaterial {
|
||||
color: Color::GREEN,
|
||||
is_red: true,
|
||||
}),
|
||||
..default()
|
||||
});
|
||||
|
||||
// camera
|
||||
commands.spawn_bundle(Camera3dBundle {
|
||||
|
@ -84,94 +55,48 @@ fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
|
|||
});
|
||||
}
|
||||
|
||||
struct IsRedPipeline {
|
||||
mesh_pipeline: MeshPipeline,
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for IsRedPipeline {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let asset_server = world.resource::<AssetServer>();
|
||||
let mesh_pipeline = world.resource::<MeshPipeline>();
|
||||
let shader = asset_server.load("shaders/shader_defs.wgsl");
|
||||
IsRedPipeline {
|
||||
mesh_pipeline: mesh_pipeline.clone(),
|
||||
shader,
|
||||
impl Material for CustomMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/shader_defs.wgsl".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for IsRedPipeline {
|
||||
type Key = (IsRed, MeshPipelineKey);
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
(is_red, pbr_pipeline_key): Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
let mut shader_defs = Vec::new();
|
||||
if is_red.0 {
|
||||
shader_defs.push("IS_RED".to_string());
|
||||
}
|
||||
let mut descriptor = self.mesh_pipeline.specialize(pbr_pipeline_key, layout)?;
|
||||
descriptor.vertex.shader = self.shader.clone();
|
||||
descriptor.vertex.shader_defs = shader_defs.clone();
|
||||
_pipeline: &MaterialPipeline<Self>,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
_layout: &MeshVertexBufferLayout,
|
||||
key: MaterialPipelineKey<Self>,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
if key.bind_group_data.is_red {
|
||||
let fragment = descriptor.fragment.as_mut().unwrap();
|
||||
fragment.shader = self.shader.clone();
|
||||
fragment.shader_defs = shader_defs;
|
||||
descriptor.layout = Some(vec![
|
||||
self.mesh_pipeline.view_layout.clone(),
|
||||
self.mesh_pipeline.mesh_layout.clone(),
|
||||
]);
|
||||
Ok(descriptor)
|
||||
fragment.shader_defs.push("IS_RED".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
type DrawIsRed = (
|
||||
SetItemPipeline,
|
||||
SetMeshViewBindGroup<0>,
|
||||
SetMeshBindGroup<1>,
|
||||
DrawMesh,
|
||||
);
|
||||
// This is the struct that will be passed to your shader
|
||||
#[derive(AsBindGroup, TypeUuid, Debug, Clone)]
|
||||
#[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"]
|
||||
#[bind_group_data(CustomMaterialKey)]
|
||||
pub struct CustomMaterial {
|
||||
#[uniform(0)]
|
||||
color: Color,
|
||||
is_red: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn queue_custom(
|
||||
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
custom_pipeline: Res<IsRedPipeline>,
|
||||
msaa: Res<Msaa>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<IsRedPipeline>>,
|
||||
mut pipeline_cache: ResMut<PipelineCache>,
|
||||
material_meshes: Query<(Entity, &Handle<Mesh>, &MeshUniform, &IsRed)>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
|
||||
) {
|
||||
let draw_custom = transparent_3d_draw_functions
|
||||
.read()
|
||||
.get_id::<DrawIsRed>()
|
||||
.unwrap();
|
||||
let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
|
||||
for (view, mut transparent_phase) in views.iter_mut() {
|
||||
let view_matrix = view.transform.compute_matrix();
|
||||
let view_row_2 = view_matrix.row(2);
|
||||
for (entity, mesh_handle, mesh_uniform, is_red) in material_meshes.iter() {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let key =
|
||||
msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline = pipelines
|
||||
.specialize(
|
||||
&mut pipeline_cache,
|
||||
&custom_pipeline,
|
||||
(*is_red, key),
|
||||
&mesh.layout,
|
||||
)
|
||||
.unwrap();
|
||||
transparent_phase.add(Transparent3d {
|
||||
entity,
|
||||
pipeline,
|
||||
draw_function: draw_custom,
|
||||
distance: view_row_2.dot(mesh_uniform.transform.col(3)),
|
||||
});
|
||||
}
|
||||
// This key is used to identify a specific permutation of this material pipeline.
|
||||
// In this case, we specialize on whether or not to configure the "IS_RED" shader def.
|
||||
// Specialization keys should be kept as small / cheap to hash as possible,
|
||||
// as they will be used to look up the pipeline for each drawn entity with this material type.
|
||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||
pub struct CustomMaterialKey {
|
||||
is_red: bool,
|
||||
}
|
||||
|
||||
impl From<&CustomMaterial> for CustomMaterialKey {
|
||||
fn from(material: &CustomMaterial) -> Self {
|
||||
Self {
|
||||
is_red: material.is_red,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,9 @@
|
|||
//! A shader and a material that uses it.
|
||||
|
||||
use bevy::{
|
||||
ecs::system::{lifetimeless::SRes, SystemParamItem},
|
||||
pbr::MaterialPipeline,
|
||||
prelude::*,
|
||||
reflect::TypeUuid,
|
||||
render::{
|
||||
render_asset::{PrepareAssetError, RenderAsset},
|
||||
render_resource::{
|
||||
encase, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
|
||||
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, Buffer,
|
||||
BufferBindingType, BufferInitDescriptor, BufferUsages, ShaderSize, ShaderStages,
|
||||
ShaderType,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
},
|
||||
render::render_resource::{AsBindGroup, ShaderRef},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
|
@ -30,13 +19,16 @@ fn setup(
|
|||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<CustomMaterial>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
// cube
|
||||
commands.spawn().insert_bundle(MaterialMeshBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
material: materials.add(CustomMaterial {
|
||||
color: Color::GREEN,
|
||||
color: Color::BLUE,
|
||||
color_texture: Some(asset_server.load("branding/icon.png")),
|
||||
alpha_mode: AlphaMode::Blend,
|
||||
}),
|
||||
..default()
|
||||
});
|
||||
|
@ -48,91 +40,26 @@ fn setup(
|
|||
});
|
||||
}
|
||||
|
||||
/// The Material trait is very configurable, but comes with sensible defaults for all methods.
|
||||
/// You only need to implement functions for features that need non-default behavior. See the Material api docs for details!
|
||||
impl Material for CustomMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/custom_material.wgsl".into()
|
||||
}
|
||||
|
||||
fn alpha_mode(&self) -> AlphaMode {
|
||||
self.alpha_mode
|
||||
}
|
||||
}
|
||||
|
||||
// This is the struct that will be passed to your shader
|
||||
#[derive(Debug, Clone, TypeUuid)]
|
||||
#[derive(AsBindGroup, TypeUuid, Debug, Clone)]
|
||||
#[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"]
|
||||
pub struct CustomMaterial {
|
||||
#[uniform(0)]
|
||||
color: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GpuCustomMaterial {
|
||||
_buffer: Buffer,
|
||||
bind_group: BindGroup,
|
||||
}
|
||||
|
||||
// The implementation of [`Material`] needs this impl to work properly.
|
||||
impl RenderAsset for CustomMaterial {
|
||||
type ExtractedAsset = CustomMaterial;
|
||||
type PreparedAsset = GpuCustomMaterial;
|
||||
type Param = (SRes<RenderDevice>, SRes<MaterialPipeline<Self>>);
|
||||
fn extract_asset(&self) -> Self::ExtractedAsset {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn prepare_asset(
|
||||
extracted_asset: Self::ExtractedAsset,
|
||||
(render_device, material_pipeline): &mut SystemParamItem<Self::Param>,
|
||||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
|
||||
let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32());
|
||||
|
||||
let byte_buffer = [0u8; Vec4::SIZE.get() as usize];
|
||||
let mut buffer = encase::UniformBuffer::new(byte_buffer);
|
||||
buffer.write(&color).unwrap();
|
||||
|
||||
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
|
||||
contents: buffer.as_ref(),
|
||||
label: None,
|
||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||
});
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: buffer.as_entire_binding(),
|
||||
}],
|
||||
label: None,
|
||||
layout: &material_pipeline.material_layout,
|
||||
});
|
||||
|
||||
Ok(GpuCustomMaterial {
|
||||
_buffer: buffer,
|
||||
bind_group,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for CustomMaterial {
|
||||
// When creating a custom material, you need to define either a vertex shader, a fragment shader or both.
|
||||
// If you don't define one of them it will use the default mesh shader which can be found at
|
||||
// <https://github.com/bevyengine/bevy/blob/latest/crates/bevy_pbr/src/render/mesh.wgsl>
|
||||
|
||||
// For this example we don't need a vertex shader
|
||||
// fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
// // Use the same path as the fragment shader since wgsl let's you define both shader in the same file
|
||||
// Some(asset_server.load("shaders/custom_material.wgsl"))
|
||||
// }
|
||||
|
||||
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
Some(asset_server.load("shaders/custom_material.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: &[BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(Vec4::min_size()),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: None,
|
||||
})
|
||||
}
|
||||
#[texture(1)]
|
||||
#[sampler(2)]
|
||||
color_texture: Option<Handle<Image>>,
|
||||
alpha_mode: AlphaMode,
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
//! A shader that uses the GLSL shading language.
|
||||
|
||||
use bevy::{
|
||||
ecs::system::{lifetimeless::SRes, SystemParamItem},
|
||||
pbr::{MaterialPipeline, SpecializedMaterial},
|
||||
pbr::{MaterialPipeline, MaterialPipelineKey},
|
||||
prelude::*,
|
||||
reflect::TypeUuid,
|
||||
render::{
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_asset::{PrepareAssetError, RenderAsset},
|
||||
render_resource::*,
|
||||
renderer::RenderDevice,
|
||||
render_resource::{
|
||||
AsBindGroup, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -44,98 +43,33 @@ fn setup(
|
|||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, TypeUuid)]
|
||||
#[derive(AsBindGroup, Clone, TypeUuid)]
|
||||
#[uuid = "4ee9c363-1124-4113-890e-199d81b00281"]
|
||||
pub struct CustomMaterial {
|
||||
#[uniform(0)]
|
||||
color: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GpuCustomMaterial {
|
||||
_buffer: Buffer,
|
||||
bind_group: BindGroup,
|
||||
}
|
||||
|
||||
impl RenderAsset for CustomMaterial {
|
||||
type ExtractedAsset = CustomMaterial;
|
||||
type PreparedAsset = GpuCustomMaterial;
|
||||
type Param = (SRes<RenderDevice>, SRes<MaterialPipeline<Self>>);
|
||||
fn extract_asset(&self) -> Self::ExtractedAsset {
|
||||
self.clone()
|
||||
impl Material for CustomMaterial {
|
||||
fn vertex_shader() -> ShaderRef {
|
||||
"shaders/custom_material.vert".into()
|
||||
}
|
||||
|
||||
fn prepare_asset(
|
||||
extracted_asset: Self::ExtractedAsset,
|
||||
(render_device, material_pipeline): &mut SystemParamItem<Self::Param>,
|
||||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
|
||||
let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32());
|
||||
|
||||
let byte_buffer = [0u8; Vec4::SIZE.get() as usize];
|
||||
let mut buffer = encase::UniformBuffer::new(byte_buffer);
|
||||
buffer.write(&color).unwrap();
|
||||
|
||||
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
|
||||
contents: buffer.as_ref(),
|
||||
label: None,
|
||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||
});
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: buffer.as_entire_binding(),
|
||||
}],
|
||||
label: None,
|
||||
layout: &material_pipeline.material_layout,
|
||||
});
|
||||
|
||||
Ok(GpuCustomMaterial {
|
||||
_buffer: buffer,
|
||||
bind_group,
|
||||
})
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/custom_material.frag".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMaterial for CustomMaterial {
|
||||
type Key = ();
|
||||
|
||||
fn key(_: &<CustomMaterial as RenderAsset>::PreparedAsset) -> Self::Key {}
|
||||
|
||||
// Bevy assumes by default that vertex shaders use the "vertex" entry point
|
||||
// and fragment shaders use the "fragment" entry point (for WGSL shaders).
|
||||
// GLSL uses "main" as the entry point, so we must override the defaults here
|
||||
fn specialize(
|
||||
_pipeline: &MaterialPipeline<Self>,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
_: Self::Key,
|
||||
_layout: &MeshVertexBufferLayout,
|
||||
_key: MaterialPipelineKey<Self>,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
descriptor.vertex.entry_point = "main".into();
|
||||
descriptor.fragment.as_mut().unwrap().entry_point = "main".into();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
Some(asset_server.load("shaders/custom_material.vert"))
|
||||
}
|
||||
|
||||
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
Some(asset_server.load("shaders/custom_material.frag"))
|
||||
}
|
||||
|
||||
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: &[BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(Vec4::min_size()),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,9 @@
|
|||
//! A shader that samples a texture with view-independent UV coordinates.
|
||||
|
||||
use bevy::{
|
||||
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,
|
||||
},
|
||||
render::render_resource::{AsBindGroup, ShaderRef},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
|
@ -75,88 +65,16 @@ fn rotate_camera(mut camera: Query<&mut Transform, With<MainCamera>>, time: Res<
|
|||
cam_transform.look_at(Vec3::ZERO, Vec3::Y);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, TypeUuid)]
|
||||
#[derive(AsBindGroup, Debug, Clone, TypeUuid)]
|
||||
#[uuid = "b62bb455-a72c-4b56-87bb-81e0554e234f"]
|
||||
pub struct CustomMaterial {
|
||||
#[texture(0)]
|
||||
#[sampler(1)]
|
||||
texture: Handle<Image>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GpuCustomMaterial {
|
||||
bind_group: BindGroup,
|
||||
}
|
||||
|
||||
impl RenderAsset for CustomMaterial {
|
||||
type ExtractedAsset = CustomMaterial;
|
||||
type PreparedAsset = GpuCustomMaterial;
|
||||
type Param = (
|
||||
SRes<RenderDevice>,
|
||||
SRes<RenderAssets<Image>>,
|
||||
SRes<MaterialPipeline<Self>>,
|
||||
);
|
||||
fn extract_asset(&self) -> Self::ExtractedAsset {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn prepare_asset(
|
||||
extracted_asset: Self::ExtractedAsset,
|
||||
(render_device, gpu_images, material_pipeline): &mut SystemParamItem<Self::Param>,
|
||||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
|
||||
let gpu_image = match gpu_images.get(&extracted_asset.texture) {
|
||||
Some(gpu_image) => gpu_image,
|
||||
// if the image isn't loaded yet, try next frame
|
||||
None => return Err(PrepareAssetError::RetryNextUpdate(extracted_asset)),
|
||||
};
|
||||
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureView(&gpu_image.texture_view),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::Sampler(&gpu_image.sampler),
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
layout: &material_pipeline.material_layout,
|
||||
});
|
||||
|
||||
Ok(GpuCustomMaterial { bind_group })
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for CustomMaterial {
|
||||
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
|
||||
Some(asset_server.load("shaders/custom_material_screenspace_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: &[
|
||||
BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
sample_type: TextureSampleType::Float { filterable: true },
|
||||
view_dimension: TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
})
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/custom_material_screenspace_texture.wgsl".into()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue