StandardMaterial flat values (#3)

StandardMaterial flat values
This commit is contained in:
Robert Swain 2021-06-18 20:21:18 +02:00 committed by Carter Anderson
parent 579c769f7c
commit 01116b1fdb
16 changed files with 339 additions and 103 deletions

View file

@ -59,9 +59,7 @@ impl<'a> Commands<'a> {
}
pub fn get_or_spawn(&mut self, entity: Entity) -> EntityCommands<'a, '_> {
self.add(GetOrSpawn {
entity,
});
self.add(GetOrSpawn { entity });
EntityCommands {
entity,
commands: self,
@ -292,8 +290,7 @@ pub struct GetOrSpawn {
entity: Entity,
}
impl Command for GetOrSpawn
{
impl Command for GetOrSpawn {
fn write(self: Box<Self>, world: &mut World) {
world.get_or_spawn(self.entity);
}

View file

@ -1,4 +1,8 @@
use crate::{archetype::ArchetypeGeneration, system::{check_system_change_tick, BoxedSystem, IntoSystem, System, SystemId}, world::World};
use crate::{
archetype::ArchetypeGeneration,
system::{check_system_change_tick, BoxedSystem, IntoSystem, System, SystemId},
world::World,
};
use std::borrow::Cow;
pub trait ExclusiveSystem: Send + Sync + 'static {

View file

@ -100,7 +100,6 @@ impl World {
&mut self.entities
}
/// Retrieves this world's [Archetypes] collection
#[inline]
pub fn archetypes(&self) -> &Archetypes {

View file

@ -37,14 +37,24 @@ fn setup(
// plane
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
material: materials.add(StandardMaterial {
color: Color::INDIGO,
roughness: 1.0,
..Default::default()
}),
..Default::default()
});
// cube
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
material: materials.add(StandardMaterial {
color: Color::PINK,
roughness: 0.0,
metallic: 1.0,
reflectance: 1.0,
..Default::default()
}),
transform: Transform::from_xyz(0.0, 1.0, 0.0),
..Default::default()
})
@ -52,8 +62,14 @@ fn setup(
// sphere
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::UVSphere { radius: 0.5, ..Default::default() })),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
mesh: meshes.add(Mesh::from(shape::UVSphere {
radius: 0.5,
..Default::default()
})),
material: materials.add(StandardMaterial {
color: Color::LIME_GREEN,
..Default::default()
}),
transform: Transform::from_xyz(1.5, 1.0, 1.5),
..Default::default()
})

View file

@ -9,7 +9,6 @@ pub use material::*;
pub use render::*;
use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_ecs::prelude::*;
use bevy_render2::{
core_pipeline,
@ -29,7 +28,7 @@ pub struct PbrPlugin;
impl Plugin for PbrPlugin {
fn build(&self, app: &mut App) {
app.add_asset::<StandardMaterial>();
app.add_plugin(StandardMaterialPlugin);
let render_app = app.sub_app_mut(0);
render_app
@ -50,6 +49,7 @@ impl Plugin for PbrPlugin {
.add_system_to_stage(RenderStage::Cleanup, render::cleanup_view_lights.system())
.init_resource::<PbrShaders>()
.init_resource::<ShadowShaders>()
.init_resource::<MaterialMeta>()
.init_resource::<MeshMeta>()
.init_resource::<LightMeta>();

View file

@ -1,10 +1,71 @@
use bevy_reflect::{Reflect, TypeUuid};
use bevy_render2::color::Color;
use bevy_app::{App, CoreStage, EventReader, Plugin};
use bevy_asset::{AddAsset, AssetEvent, Assets, Handle};
use bevy_ecs::prelude::*;
use bevy_math::Vec4;
use bevy_reflect::TypeUuid;
use bevy_render2::{
color::Color,
render_command::RenderCommandQueue,
render_resource::{BufferId, BufferInfo, BufferUsage},
renderer::{RenderResourceContext, RenderResources},
};
use bevy_utils::HashSet;
use crevice::std140::{AsStd140, Std140};
#[derive(Debug, Default, Clone, TypeUuid, Reflect)]
// TODO: this shouldn't live in the StandardMaterial type
#[derive(Debug, Clone, Copy)]
pub struct StandardMaterialGpuData {
pub buffer: BufferId,
}
/// A material with "standard" properties used in PBR lighting
/// Standard property values with pictures here https://google.github.io/filament/Material%20Properties.pdf
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"]
pub struct StandardMaterial {
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
/// in between.
pub color: Color,
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
/// Defaults to minimum of 0.089
pub roughness: f32,
/// From [0.0, 1.0], dielectric to pure metallic
pub metallic: f32,
/// 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,
// 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,
pub gpu_data: Option<StandardMaterialGpuData>,
}
impl StandardMaterial {
pub fn gpu_data(&self) -> Option<&StandardMaterialGpuData> {
self.gpu_data.as_ref()
}
}
impl Default for StandardMaterial {
fn default() -> Self {
StandardMaterial {
color: Color::rgb(1.0, 1.0, 1.0),
// This is the minimum the roughness is clamped to in shader code
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/
// It's the minimum floating point value that won't be rounded down to 0 in the
// calculations used. Although technically for 32-bit floats, 0.045 could be
// used.
roughness: 0.089,
// Few materials are purely dielectric or metallic
// This is just a default for mostly-dielectric
metallic: 0.01,
// Minimum real-world reflectance is 2%, most materials between 2-5%
// Expressed in a linear scale and equivalent to 4% reflectance see https://google.github.io/filament/Material%20Properties.pdf
reflectance: 0.5,
emissive: Color::BLACK,
gpu_data: None,
}
}
}
impl From<Color> for StandardMaterial {
@ -15,3 +76,112 @@ impl From<Color> for StandardMaterial {
}
}
}
#[derive(Clone, AsStd140)]
pub struct StandardMaterialUniformData {
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
/// in between.
pub color: Vec4,
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
/// Defaults to minimum of 0.089
pub roughness: f32,
/// From [0.0, 1.0], dielectric to pure metallic
pub metallic: f32,
/// 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,
// 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: Vec4,
}
pub struct StandardMaterialPlugin;
impl Plugin for StandardMaterialPlugin {
fn build(&self, app: &mut App) {
app.add_asset::<StandardMaterial>().add_system_to_stage(
CoreStage::PostUpdate,
standard_material_resource_system.system(),
);
}
}
pub fn standard_material_resource_system(
render_resource_context: Res<RenderResources>,
mut render_command_queue: ResMut<RenderCommandQueue>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut material_events: EventReader<AssetEvent<StandardMaterial>>,
) {
let mut changed_materials = HashSet::default();
let render_resource_context = &**render_resource_context;
for event in material_events.iter() {
match event {
AssetEvent::Created { ref handle } => {
changed_materials.insert(handle.clone_weak());
}
AssetEvent::Modified { ref handle } => {
changed_materials.insert(handle.clone_weak());
// TODO: uncomment this to support mutated materials
// remove_current_material_resources(render_resource_context, handle, &mut materials);
}
AssetEvent::Removed { ref handle } => {
remove_current_material_resources(render_resource_context, handle, &mut materials);
// if material was modified and removed in the same update, ignore the modification
// events are ordered so future modification events are ok
changed_materials.remove(handle);
}
}
}
// update changed material data
for changed_material_handle in changed_materials.iter() {
if let Some(material) = materials.get_mut(changed_material_handle) {
// TODO: this avoids creating new materials each frame because storing gpu data in the material flags it as
// modified. this prevents hot reloading and therefore can't be used in an actual impl.
if material.gpu_data.is_some() {
continue;
}
let value = StandardMaterialUniformData {
color: material.color.into(),
roughness: material.roughness,
metallic: material.metallic,
reflectance: material.reflectance,
emissive: material.emissive.into(),
};
let value_std140 = value.as_std140();
let size = StandardMaterialUniformData::std140_size_static();
let staging_buffer = render_resource_context.create_buffer_with_data(
BufferInfo {
size,
buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE,
mapped_at_creation: true,
},
value_std140.as_bytes(),
);
let buffer = render_resource_context.create_buffer(BufferInfo {
size,
buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM,
mapped_at_creation: false,
});
render_command_queue.copy_buffer_to_buffer(staging_buffer, 0, buffer, 0, size as u64);
render_command_queue.free_buffer(staging_buffer);
material.gpu_data = Some(StandardMaterialGpuData { buffer });
}
}
}
fn remove_current_material_resources(
render_resource_context: &dyn RenderResourceContext,
handle: &Handle<StandardMaterial>,
materials: &mut Assets<StandardMaterial>,
) {
if let Some(gpu_data) = materials.get_mut(handle).and_then(|t| t.gpu_data.take()) {
render_resource_context.remove_buffer(gpu_data.buffer);
}
}

View file

@ -325,7 +325,7 @@ impl Node for ShadowPassNode {
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
let view_lights = self.main_view_query.get_manual(world, view_entity).unwrap();
if let Some(view_lights) = self.main_view_query.get_manual(world, view_entity).ok() {
for view_light_entity in view_lights.lights.iter().copied() {
let (view_light, shadow_phase) = self
.view_light_query
@ -352,7 +352,8 @@ impl Node for ShadowPassNode {
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for drawable in shadow_phase.drawn_things.iter() {
let draw_function = draw_functions.get_mut(drawable.draw_function).unwrap();
let draw_function =
draw_functions.get_mut(drawable.draw_function).unwrap();
draw_function.draw(
world,
&mut tracked_pass,
@ -364,6 +365,7 @@ impl Node for ShadowPassNode {
},
);
}
}
Ok(())
}

View file

@ -1,7 +1,8 @@
mod light;
use bevy_utils::HashMap;
pub use light::*;
use crate::StandardMaterial;
use crate::{StandardMaterial, StandardMaterialUniformData};
use bevy_asset::{Assets, Handle};
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_math::Mat4;
@ -11,13 +12,16 @@ use bevy_render2::{
pipeline::*,
render_graph::{Node, NodeRunError, RenderGraphContext},
render_phase::{Draw, DrawFunctions, Drawable, RenderPhase, TrackedRenderPass},
render_resource::{BindGroupBuilder, BindGroupId, BufferId, DynamicUniformVec},
render_resource::{
BindGroupBuilder, BindGroupId, BufferId, DynamicUniformVec, RenderResourceBinding,
},
renderer::{RenderContext, RenderResources},
shader::{Shader, ShaderStage, ShaderStages},
texture::{TextureFormat, TextureSampleType},
view::{ViewMeta, ViewUniform},
};
use bevy_transform::components::GlobalTransform;
use crevice::std140::AsStd140;
pub struct PbrShaders {
pipeline: PipelineId,
@ -143,6 +147,7 @@ struct ExtractedMesh {
vertex_buffer: BufferId,
index_info: Option<IndexInfo>,
transform_binding_offset: u32,
material_buffer: BufferId,
}
struct IndexInfo {
@ -157,22 +162,27 @@ pub struct ExtractedMeshes {
pub fn extract_meshes(
mut commands: Commands,
meshes: Res<Assets<Mesh>>,
_materials: Res<Assets<StandardMaterial>>,
materials: Res<Assets<StandardMaterial>>,
query: Query<(&GlobalTransform, &Handle<Mesh>, &Handle<StandardMaterial>)>,
) {
let mut extracted_meshes = Vec::new();
for (transform, mesh_handle, _material_handle) in query.iter() {
for (transform, mesh_handle, material_handle) in query.iter() {
if let Some(mesh) = meshes.get(mesh_handle) {
if let Some(gpu_data) = &mesh.gpu_data() {
if let Some(mesh_gpu_data) = &mesh.gpu_data() {
if let Some(material) = materials.get(material_handle) {
if let Some(material_gpu_data) = &material.gpu_data() {
extracted_meshes.push(ExtractedMesh {
transform: transform.compute_matrix(),
vertex_buffer: gpu_data.vertex_buffer,
index_info: gpu_data.index_buffer.map(|i| IndexInfo {
vertex_buffer: mesh_gpu_data.vertex_buffer,
index_info: mesh_gpu_data.index_buffer.map(|i| IndexInfo {
buffer: i,
count: mesh.indices().unwrap().len() as u32,
}),
transform_binding_offset: 0,
})
material_buffer: material_gpu_data.buffer,
});
}
}
}
}
}
@ -211,6 +221,11 @@ struct MeshViewBindGroups {
mesh_transform_bind_group: BindGroupId,
}
#[derive(Default)]
pub struct MaterialMeta {
material_bind_groups: Vec<BindGroupId>,
}
pub fn queue_meshes(
mut commands: Commands,
draw_functions: Res<DrawFunctions>,
@ -218,9 +233,10 @@ pub fn queue_meshes(
pbr_shaders: Res<PbrShaders>,
shadow_shaders: Res<ShadowShaders>,
mesh_meta: Res<MeshMeta>,
mut material_meta: ResMut<MaterialMeta>,
light_meta: Res<LightMeta>,
view_meta: Res<ViewMeta>,
extracted_meshes: Res<ExtractedMeshes>,
mut extracted_meshes: ResMut<ExtractedMeshes>,
mut views: Query<(Entity, &ViewLights, &mut RenderPhase<Transparent3dPhase>)>,
mut view_light_shadow_phases: Query<&mut RenderPhase<ShadowPhase>>,
) {
@ -249,13 +265,38 @@ pub fn queue_meshes(
mesh_transform_bind_group: mesh_transform_bind_group.id,
});
// TODO: free old bind groups? clear_unused_bind_groups() currently does this for us? Moving to RAII would also do this for us?
material_meta.material_bind_groups.clear();
let mut material_bind_group_indices = HashMap::default();
let draw_pbr = draw_functions.read().get_id::<DrawPbr>().unwrap();
for i in 0..extracted_meshes.meshes.len() {
for (i, mesh) in extracted_meshes.meshes.iter_mut().enumerate() {
let material_bind_group_index = *material_bind_group_indices
.entry(mesh.material_buffer)
.or_insert_with(|| {
let index = material_meta.material_bind_groups.len();
let material_bind_group = BindGroupBuilder::default()
.add_binding(
0,
RenderResourceBinding::Buffer {
buffer: mesh.material_buffer,
range: 0..StandardMaterialUniformData::std140_size_static() as u64,
},
)
.finish();
render_resources
.create_bind_group(layout.bind_group(2).id, &material_bind_group);
material_meta
.material_bind_groups
.push(material_bind_group.id);
index
});
// TODO: currently there is only "transparent phase". this should pick transparent vs opaque according to the mesh material
transparent_phase.add(Drawable {
draw_function: draw_pbr,
draw_key: i,
sort_key: 0, // TODO: sort back-to-front
sort_key: material_bind_group_index, // TODO: sort back-to-front, sorting by material for now
});
}
@ -312,6 +353,7 @@ impl Node for PbrNode {
type DrawPbrParams<'a> = (
Res<'a, PbrShaders>,
Res<'a, MaterialMeta>,
Res<'a, ExtractedMeshes>,
Query<'a, (&'a ViewUniform, &'a MeshViewBindGroups, &'a ViewLights)>,
);
@ -334,9 +376,9 @@ impl Draw for DrawPbr {
pass: &mut TrackedRenderPass,
view: Entity,
draw_key: usize,
_sort_key: usize,
sort_key: usize,
) {
let (pbr_shaders, extracted_meshes, views) = self.params.get(world);
let (pbr_shaders, material_meta, extracted_meshes, views) = self.params.get(world);
let (view_uniforms, mesh_view_bind_groups, view_lights) = views.get(view).unwrap();
let layout = &pbr_shaders.pipeline_descriptor.layout;
let extracted_mesh = &extracted_meshes.meshes[draw_key];
@ -356,6 +398,12 @@ impl Draw for DrawPbr {
mesh_view_bind_groups.mesh_transform_bind_group,
Some(&[extracted_mesh.transform_binding_offset]),
);
pass.set_bind_group(
2,
layout.bind_group(2).id,
material_meta.material_bind_groups[sort_key],
None,
);
pass.set_vertex_buffer(0, extracted_mesh.vertex_buffer, 0);
if let Some(index_info) = &extracted_mesh.index_info {
pass.set_index_buffer(index_info.buffer, 0, IndexFormat::Uint32);

View file

@ -18,6 +18,7 @@ struct PointLight {
// TODO: this can be removed if we move to storage buffers for light arrays
const int MAX_POINT_LIGHTS = 10;
// View bindings - set 0
layout(set = 0, binding = 0) uniform View {
mat4 ViewProj;
vec3 ViewWorldPosition;
@ -29,6 +30,18 @@ layout(std140, set = 0, binding = 1) uniform Lights {
layout(set = 0, binding = 2) uniform texture2DArray t_Shadow;
layout(set = 0, binding = 3) uniform samplerShadow s_Shadow;
// Material bindings - set 2
struct StandardMaterial_t {
vec4 color;
float roughness;
float metallic;
float reflectance;
vec4 emissive;
};
layout(set = 2, binding = 0) uniform StandardMaterial {
StandardMaterial_t Material;
};
# define saturate(x) clamp(x, 0.0, 1.0)
const float PI = 3.141592653589793;
@ -252,11 +265,11 @@ float fetch_shadow(int light_id, vec4 homogeneous_coords) {
}
void main() {
vec4 color = vec4(0.6, 0.6, 0.6, 1.0);
float metallic = 0.01;
float reflectance = 0.5;
float perceptual_roughness = 0.089;
vec3 emissive = vec3(0.0, 0.0, 0.0);
vec4 color = Material.color;
float metallic = Material.metallic;
float reflectance = Material.reflectance;
float perceptual_roughness = Material.roughness;
vec3 emissive = Material.emissive.xyz;
vec3 ambient_color = vec3(0.1, 0.1, 0.1);
float occlusion = 1.0;

View file

@ -8,11 +8,13 @@ layout(location = 0) out vec4 v_WorldPosition;
layout(location = 1) out vec3 v_WorldNormal;
layout(location = 2) out vec2 v_Uv;
// View bindings - set 0
layout(set = 0, binding = 0) uniform View {
mat4 ViewProj;
vec3 ViewWorldPosition;
};
// Object bindings - set 1
layout(set = 1, binding = 0) uniform MeshTransform {
mat4 Model;
};

View file

@ -25,13 +25,11 @@ impl PipelineLayout {
}
pub fn bind_group(&self, index: u32) -> &BindGroupDescriptor {
self.get_bind_group(index)
.expect("bind group exists")
self.get_bind_group(index).expect("bind group exists")
}
pub fn bind_group_mut(&mut self, index: u32) -> &mut BindGroupDescriptor {
self.get_bind_group_mut(index)
.expect("bind group exists")
self.get_bind_group_mut(index).expect("bind group exists")
}
pub fn from_shader_layouts(shader_layouts: &mut [ShaderLayout]) -> Self {

View file

@ -4,8 +4,8 @@ mod draw_state;
pub use draw::*;
pub use draw_state::*;
use std::marker::PhantomData;
use bevy_ecs::prelude::Query;
use std::marker::PhantomData;
// TODO: make this configurable per phase?
pub struct Drawable {
@ -39,7 +39,6 @@ impl<T> RenderPhase<T> {
}
}
pub fn sort_phase_system<T: 'static>(mut render_phases: Query<&mut RenderPhase<T>>) {
for mut phase in render_phases.iter_mut() {
phase.sort();

View file

@ -55,13 +55,7 @@ impl BindGroupBuilder {
}
pub fn add_buffer(self, index: u32, buffer: BufferId, range: Range<u64>) -> Self {
self.add_binding(
index,
RenderResourceBinding::Buffer {
buffer,
range,
},
)
self.add_binding(index, RenderResourceBinding::Buffer { buffer, range })
}
pub fn finish(mut self) -> BindGroup {
@ -75,10 +69,7 @@ impl BindGroupBuilder {
fn hash_binding(&mut self, binding: &RenderResourceBinding) {
match binding {
RenderResourceBinding::Buffer {
buffer,
range,
} => {
RenderResourceBinding::Buffer { buffer, range } => {
RenderResourceId::Buffer(*buffer).hash(&mut self.hasher);
range.hash(&mut self.hasher);
}

View file

@ -3,10 +3,7 @@ use std::ops::Range;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum RenderResourceBinding {
Buffer {
buffer: BufferId,
range: Range<u64>,
},
Buffer { buffer: BufferId, range: Range<u64> },
TextureView(TextureViewId),
Sampler(SamplerId),
}