render: add MSAA support

This commit is contained in:
Carter Anderson 2020-07-29 18:15:15 -07:00
parent a2c1a90695
commit ca87359c6e
10 changed files with 212 additions and 25 deletions

View File

@ -63,6 +63,10 @@ path = "examples/2d/texture_atlas.rs"
name = "load_model" name = "load_model"
path = "examples/3d/load_model.rs" path = "examples/3d/load_model.rs"
[[example]]
name = "msaa"
path = "examples/3d/msaa.rs"
[[example]] [[example]]
name = "parenting" name = "parenting"
path = "examples/3d/parenting.rs" path = "examples/3d/parenting.rs"

View File

@ -15,6 +15,7 @@ pub use once_cell;
pub mod prelude { pub mod prelude {
pub use crate::{ pub use crate::{
base::Msaa,
color::Color, color::Color,
draw::Draw, draw::Draw,
entity::*, entity::*,
@ -26,6 +27,7 @@ pub mod prelude {
} }
use crate::prelude::*; use crate::prelude::*;
use base::Msaa;
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_asset::AddAsset; use bevy_asset::AddAsset;
use bevy_ecs::{IntoQuerySystem, IntoThreadLocalSystem}; use bevy_ecs::{IntoQuerySystem, IntoThreadLocalSystem};
@ -131,10 +133,15 @@ impl AppPlugin for RenderPlugin {
shader::clear_shader_defs_system.system(), shader::clear_shader_defs_system.system(),
); );
if app.resources().get::<Msaa>().is_none() {
app.init_resource::<Msaa>();
}
if let Some(ref config) = self.base_render_graph_config { if let Some(ref config) = self.base_render_graph_config {
let resources = app.resources(); let resources = app.resources();
let mut render_graph = resources.get_mut::<RenderGraph>().unwrap(); let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
render_graph.add_base_graph(config); let msaa = resources.get::<Msaa>().unwrap();
render_graph.add_base_graph(config, &msaa);
let mut active_cameras = resources.get_mut::<ActiveCameras>().unwrap(); let mut active_cameras = resources.get_mut::<ActiveCameras>().unwrap();
if config.add_3d_camera { if config.add_3d_camera {
active_cameras.add(base::camera::CAMERA3D); active_cameras.add(base::camera::CAMERA3D);

View File

@ -7,11 +7,23 @@ use bevy_asset::{Assets, Handle};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
#[derive(Clone, Eq, PartialEq, Debug, Default)] #[derive(Clone, Eq, PartialEq, Debug)]
pub struct PipelineSpecialization { pub struct PipelineSpecialization {
pub shader_specialization: ShaderSpecialization, pub shader_specialization: ShaderSpecialization,
pub primitive_topology: PrimitiveTopology, pub primitive_topology: PrimitiveTopology,
pub dynamic_bindings: Vec<DynamicBinding>, pub dynamic_bindings: Vec<DynamicBinding>,
pub sample_count: u32,
}
impl Default for PipelineSpecialization {
fn default() -> Self {
Self {
sample_count: 1,
shader_specialization: Default::default(),
primitive_topology: Default::default(),
dynamic_bindings: Default::default(),
}
}
} }
impl PipelineSpecialization { impl PipelineSpecialization {
@ -145,6 +157,8 @@ impl PipelineCompiler {
Some(vertex_buffer_descriptors), Some(vertex_buffer_descriptors),
&pipeline_specialization.dynamic_bindings, &pipeline_specialization.dynamic_bindings,
); );
specialized_descriptor.sample_count = pipeline_specialization.sample_count;
specialized_descriptor.primitive_topology = pipeline_specialization.primitive_topology; specialized_descriptor.primitive_topology = pipeline_specialization.primitive_topology;
let specialized_pipeline_handle = pipelines.add(specialized_descriptor); let specialized_pipeline_handle = pipelines.add(specialized_descriptor);

View File

@ -1,10 +1,10 @@
use super::{PipelineDescriptor, PipelineSpecialization}; use super::{PipelineDescriptor, PipelineSpecialization};
use crate::{ use crate::{
draw::{Draw, DrawContext, DrawError, Drawable}, draw::{Draw, DrawContext, DrawError, Drawable},
renderer::RenderResourceBindings, renderer::RenderResourceBindings, prelude::Msaa,
}; };
use bevy_asset::Handle; use bevy_asset::Handle;
use bevy_ecs::{Query, ResMut}; use bevy_ecs::{Query, ResMut, Res};
use bevy_property::Properties; use bevy_property::Properties;
#[derive(Properties, Default, Clone)] #[derive(Properties, Default, Clone)]
pub struct RenderPipeline { pub struct RenderPipeline {
@ -104,9 +104,14 @@ impl<'a> Drawable for DrawableRenderPipelines<'a> {
pub fn draw_render_pipelines_system( pub fn draw_render_pipelines_system(
mut draw_context: DrawContext, mut draw_context: DrawContext,
mut render_resource_bindings: ResMut<RenderResourceBindings>, mut render_resource_bindings: ResMut<RenderResourceBindings>,
msaa: Res<Msaa>,
mut query: Query<(&mut Draw, &mut RenderPipelines)>, mut query: Query<(&mut Draw, &mut RenderPipelines)>,
) { ) {
for (mut draw, mut render_pipelines) in &mut query.iter() { for (mut draw, mut render_pipelines) in &mut query.iter() {
for pipeline in render_pipelines.pipelines.iter_mut() {
pipeline.specialization.sample_count = msaa.samples;
}
let mut drawable = DrawableRenderPipelines { let mut drawable = DrawableRenderPipelines {
render_pipelines: &mut render_pipelines, render_pipelines: &mut render_pipelines,
render_resource_bindings: &mut render_resource_bindings, render_resource_bindings: &mut render_resource_bindings,

View File

@ -13,6 +13,18 @@ use crate::{
}; };
use bevy_window::WindowId; use bevy_window::WindowId;
pub struct Msaa {
pub samples: u32,
}
impl Default for Msaa {
fn default() -> Self {
Self {
samples: 4,
}
}
}
pub struct BaseRenderGraphConfig { pub struct BaseRenderGraphConfig {
pub add_2d_camera: bool, pub add_2d_camera: bool,
pub add_3d_camera: bool, pub add_3d_camera: bool,
@ -28,6 +40,7 @@ pub mod node {
pub const CAMERA2D: &str = "camera2d"; pub const CAMERA2D: &str = "camera2d";
pub const TEXTURE_COPY: &str = "texture_copy"; pub const TEXTURE_COPY: &str = "texture_copy";
pub const MAIN_DEPTH_TEXTURE: &str = "main_pass_depth_texture"; pub const MAIN_DEPTH_TEXTURE: &str = "main_pass_depth_texture";
pub const MAIN_SAMPLED_COLOR_ATTACHMENT: &str = "main_pass_sampled_color_attachment";
pub const MAIN_PASS: &str = "main_pass"; pub const MAIN_PASS: &str = "main_pass";
pub const SHARED_BUFFERS: &str = "shared_buffers"; pub const SHARED_BUFFERS: &str = "shared_buffers";
} }
@ -53,11 +66,11 @@ impl Default for BaseRenderGraphConfig {
/// By itself this graph doesn't do much, but it allows Render plugins to interop with each other by having a common /// By itself this graph doesn't do much, but it allows Render plugins to interop with each other by having a common
/// set of nodes. It can be customized using `BaseRenderGraphConfig`. /// set of nodes. It can be customized using `BaseRenderGraphConfig`.
pub trait BaseRenderGraphBuilder { pub trait BaseRenderGraphBuilder {
fn add_base_graph(&mut self, config: &BaseRenderGraphConfig) -> &mut Self; fn add_base_graph(&mut self, config: &BaseRenderGraphConfig, msaa: &Msaa) -> &mut Self;
} }
impl BaseRenderGraphBuilder for RenderGraph { impl BaseRenderGraphBuilder for RenderGraph {
fn add_base_graph(&mut self, config: &BaseRenderGraphConfig) -> &mut Self { fn add_base_graph(&mut self, config: &BaseRenderGraphConfig, msaa: &Msaa) -> &mut Self {
self.add_node(node::TEXTURE_COPY, TextureCopyNode::default()); self.add_node(node::TEXTURE_COPY, TextureCopyNode::default());
if config.add_3d_camera { if config.add_3d_camera {
self.add_system_node(node::CAMERA3D, CameraNode::new(camera::CAMERA3D)); self.add_system_node(node::CAMERA3D, CameraNode::new(camera::CAMERA3D));
@ -80,7 +93,7 @@ impl BaseRenderGraphBuilder for RenderGraph {
height: 1, height: 1,
}, },
mip_level_count: 1, mip_level_count: 1,
sample_count: 1, sample_count: msaa.samples,
dimension: TextureDimension::D2, dimension: TextureDimension::D2,
format: TextureFormat::Depth32Float, // PERF: vulkan docs recommend using 24 bit depth for better performance format: TextureFormat::Depth32Float, // PERF: vulkan docs recommend using 24 bit depth for better performance
usage: TextureUsage::OUTPUT_ATTACHMENT, usage: TextureUsage::OUTPUT_ATTACHMENT,
@ -90,15 +103,29 @@ impl BaseRenderGraphBuilder for RenderGraph {
} }
if config.add_main_pass { if config.add_main_pass {
let mut main_pass_node = PassNode::<&MainPass>::new(PassDescriptor { let color_attachment = if msaa.samples > 1 {
color_attachments: vec![RenderPassColorAttachmentDescriptor { RenderPassColorAttachmentDescriptor {
attachment: TextureAttachment::Input("color".to_string()), attachment: TextureAttachment::Input("color_attachment".to_string()),
resolve_target: Some(TextureAttachment::Input(
"color_resolve_target".to_string(),
)),
ops: Operations {
load: LoadOp::Clear(Color::rgb(0.1, 0.1, 0.1)),
store: true,
},
}
} else {
RenderPassColorAttachmentDescriptor {
attachment: TextureAttachment::Input("color_attachment".to_string()),
resolve_target: None, resolve_target: None,
ops: Operations { ops: Operations {
load: LoadOp::Clear(Color::rgb(0.1, 0.1, 0.1)), load: LoadOp::Clear(Color::rgb(0.1, 0.1, 0.1)),
store: true, store: true,
}, },
}], }
};
let mut main_pass_node = PassNode::<&MainPass>::new(PassDescriptor {
color_attachments: vec![color_attachment],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachmentDescriptor { depth_stencil_attachment: Some(RenderPassDepthStencilAttachmentDescriptor {
attachment: TextureAttachment::Input("depth".to_string()), attachment: TextureAttachment::Input("depth".to_string()),
depth_ops: Some(Operations { depth_ops: Some(Operations {
@ -107,7 +134,7 @@ impl BaseRenderGraphBuilder for RenderGraph {
}), }),
stencil_ops: None, stencil_ops: None,
}), }),
sample_count: 1, sample_count: msaa.samples,
}); });
main_pass_node.use_default_clear_color(0); main_pass_node.use_default_clear_color(0);
@ -146,7 +173,40 @@ impl BaseRenderGraphBuilder for RenderGraph {
node::PRIMARY_SWAP_CHAIN, node::PRIMARY_SWAP_CHAIN,
WindowSwapChainNode::OUT_TEXTURE, WindowSwapChainNode::OUT_TEXTURE,
node::MAIN_PASS, node::MAIN_PASS,
"color", if msaa.samples > 1 {
"color_resolve_target"
} else {
"color_attachment"
},
)
.unwrap();
}
if msaa.samples > 1 {
self.add_node(
node::MAIN_SAMPLED_COLOR_ATTACHMENT,
WindowTextureNode::new(
WindowId::primary(),
TextureDescriptor {
size: Extent3d {
depth: 1,
width: 1,
height: 1,
},
mip_level_count: 1,
sample_count: msaa.samples,
dimension: TextureDimension::D2,
format: TextureFormat::Bgra8UnormSrgb,
usage: TextureUsage::OUTPUT_ATTACHMENT,
},
),
);
self.add_slot_edge(
node::MAIN_SAMPLED_COLOR_ATTACHMENT,
WindowSwapChainNode::OUT_TEXTURE,
node::MAIN_PASS,
"color_attachment",
) )
.unwrap(); .unwrap();
} }

View File

@ -24,6 +24,7 @@ pub struct PassNode<Q: HecsQuery> {
inputs: Vec<ResourceSlotInfo>, inputs: Vec<ResourceSlotInfo>,
cameras: Vec<CameraInfo>, cameras: Vec<CameraInfo>,
color_attachment_input_indices: Vec<Option<usize>>, color_attachment_input_indices: Vec<Option<usize>>,
color_resolve_target_indices: Vec<Option<usize>>,
depth_stencil_attachment_input_index: Option<usize>, depth_stencil_attachment_input_index: Option<usize>,
default_clear_color_inputs: Vec<usize>, default_clear_color_inputs: Vec<usize>,
camera_bind_group_descriptor: BindGroupDescriptor, camera_bind_group_descriptor: BindGroupDescriptor,
@ -34,26 +35,37 @@ impl<Q: HecsQuery> PassNode<Q> {
pub fn new(descriptor: PassDescriptor) -> Self { pub fn new(descriptor: PassDescriptor) -> Self {
let mut inputs = Vec::new(); let mut inputs = Vec::new();
let mut color_attachment_input_indices = Vec::new(); let mut color_attachment_input_indices = Vec::new();
let mut color_resolve_target_indices = Vec::new();
for color_attachment in descriptor.color_attachments.iter() { for color_attachment in descriptor.color_attachments.iter() {
if let TextureAttachment::Input(ref name) = color_attachment.attachment { if let TextureAttachment::Input(ref name) = color_attachment.attachment {
color_attachment_input_indices.push(Some(inputs.len()));
inputs.push(ResourceSlotInfo::new( inputs.push(ResourceSlotInfo::new(
name.to_string(), name.to_string(),
RenderResourceType::Texture, RenderResourceType::Texture,
)); ));
color_attachment_input_indices.push(Some(inputs.len() - 1));
} else { } else {
color_attachment_input_indices.push(None); color_attachment_input_indices.push(None);
} }
if let Some(TextureAttachment::Input(ref name)) = color_attachment.resolve_target {
color_resolve_target_indices.push(Some(inputs.len()));
inputs.push(ResourceSlotInfo::new(
name.to_string(),
RenderResourceType::Texture,
));
} else {
color_resolve_target_indices.push(None);
}
} }
let mut depth_stencil_attachment_input_index = None; let mut depth_stencil_attachment_input_index = None;
if let Some(ref depth_stencil_attachment) = descriptor.depth_stencil_attachment { if let Some(ref depth_stencil_attachment) = descriptor.depth_stencil_attachment {
if let TextureAttachment::Input(ref name) = depth_stencil_attachment.attachment { if let TextureAttachment::Input(ref name) = depth_stencil_attachment.attachment {
depth_stencil_attachment_input_index = Some(inputs.len());
inputs.push(ResourceSlotInfo::new( inputs.push(ResourceSlotInfo::new(
name.to_string(), name.to_string(),
RenderResourceType::Texture, RenderResourceType::Texture,
)); ));
depth_stencil_attachment_input_index = Some(inputs.len() - 1);
} }
} }
@ -74,6 +86,7 @@ impl<Q: HecsQuery> PassNode<Q> {
inputs, inputs,
cameras: Vec::new(), cameras: Vec::new(),
color_attachment_input_indices, color_attachment_input_indices,
color_resolve_target_indices,
depth_stencil_attachment_input_index, depth_stencil_attachment_input_index,
default_clear_color_inputs: Vec::new(), default_clear_color_inputs: Vec::new(),
camera_bind_group_descriptor, camera_bind_group_descriptor,
@ -120,6 +133,10 @@ impl<Q: HecsQuery + Send + Sync + 'static> Node for PassNode<Q> {
color_attachment.attachment = color_attachment.attachment =
TextureAttachment::Id(input.get(input_index).unwrap().get_texture().unwrap()); TextureAttachment::Id(input.get(input_index).unwrap().get_texture().unwrap());
} }
if let Some(input_index) = self.color_resolve_target_indices[i] {
color_attachment.resolve_target =
Some(TextureAttachment::Id(input.get(input_index).unwrap().get_texture().unwrap()));
}
} }
if let Some(input_index) = self.depth_stencil_attachment_input_index { if let Some(input_index) = self.depth_stencil_attachment_input_index {

View File

@ -10,7 +10,7 @@ use bevy_render::{
renderer::{ renderer::{
AssetRenderResourceBindings, BindGroup, BufferUsage, RenderResourceBindings, AssetRenderResourceBindings, BindGroup, BufferUsage, RenderResourceBindings,
RenderResourceId, RenderResourceId,
}, }, prelude::Msaa,
}; };
use bevy_sprite::{TextureAtlas, TextureAtlasSprite}; use bevy_sprite::{TextureAtlas, TextureAtlasSprite};
@ -38,6 +38,7 @@ pub struct DrawableText<'a> {
pub container_size: Vec2, pub container_size: Vec2,
pub style: &'a TextStyle, pub style: &'a TextStyle,
pub text: &'a str, pub text: &'a str,
pub msaa: &'a Msaa,
} }
impl<'a> Drawable for DrawableText<'a> { impl<'a> Drawable for DrawableText<'a> {
@ -45,8 +46,10 @@ impl<'a> Drawable for DrawableText<'a> {
context.set_pipeline( context.set_pipeline(
draw, draw,
bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE, bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE,
// TODO: remove this shader def specialization when its easier to manually bind global render resources to specific bind groups &PipelineSpecialization {
&PipelineSpecialization::default(), sample_count: self.msaa.samples,
..Default::default()
},
)?; )?;
let render_resource_context = &**context.render_resource_context; let render_resource_context = &**context.render_resource_context;

View File

@ -8,6 +8,7 @@ use bevy_render::{
RenderPassDepthStencilAttachmentDescriptor, TextureAttachment, RenderPassDepthStencilAttachmentDescriptor, TextureAttachment,
}, },
pipeline::*, pipeline::*,
prelude::Msaa,
render_graph::{ render_graph::{
base, CameraNode, PassNode, RenderGraph, RenderResourcesNode, WindowSwapChainNode, base, CameraNode, PassNode, RenderGraph, RenderResourcesNode, WindowSwapChainNode,
WindowTextureNode, WindowTextureNode,
@ -82,17 +83,31 @@ impl UiRenderGraphBuilder for RenderGraph {
fn add_ui_graph(&mut self, resources: &Resources) -> &mut Self { fn add_ui_graph(&mut self, resources: &Resources) -> &mut Self {
let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap(); let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
let mut shaders = resources.get_mut::<Assets<Shader>>().unwrap(); let mut shaders = resources.get_mut::<Assets<Shader>>().unwrap();
let msaa = resources.get::<Msaa>().unwrap();
pipelines.set(UI_PIPELINE_HANDLE, build_ui_pipeline(&mut shaders)); pipelines.set(UI_PIPELINE_HANDLE, build_ui_pipeline(&mut shaders));
let mut ui_pass_node = PassNode::<&Node>::new(PassDescriptor { let color_attachment = if msaa.samples > 1 {
color_attachments: vec![RenderPassColorAttachmentDescriptor { RenderPassColorAttachmentDescriptor {
attachment: TextureAttachment::Input("color".to_string()), attachment: TextureAttachment::Input("color_attachment".to_string()),
resolve_target: Some(TextureAttachment::Input("color_resolve_target".to_string())),
ops: Operations {
load: LoadOp::Load,
store: true,
},
}
} else {
RenderPassColorAttachmentDescriptor {
attachment: TextureAttachment::Input("color_attachment".to_string()),
resolve_target: None, resolve_target: None,
ops: Operations { ops: Operations {
load: LoadOp::Load, load: LoadOp::Load,
store: true, store: true,
}, },
}], }
};
let mut ui_pass_node = PassNode::<&Node>::new(PassDescriptor {
color_attachments: vec![color_attachment],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachmentDescriptor { depth_stencil_attachment: Some(RenderPassDepthStencilAttachmentDescriptor {
attachment: TextureAttachment::Input("depth".to_string()), attachment: TextureAttachment::Input("depth".to_string()),
depth_ops: Some(Operations { depth_ops: Some(Operations {
@ -101,7 +116,7 @@ impl UiRenderGraphBuilder for RenderGraph {
}), }),
stencil_ops: None, stencil_ops: None,
}), }),
sample_count: 1, sample_count: msaa.samples,
}); });
ui_pass_node.add_camera(camera::UI_CAMERA); ui_pass_node.add_camera(camera::UI_CAMERA);
self.add_node(node::UI_PASS, ui_pass_node); self.add_node(node::UI_PASS, ui_pass_node);
@ -110,7 +125,11 @@ impl UiRenderGraphBuilder for RenderGraph {
base::node::PRIMARY_SWAP_CHAIN, base::node::PRIMARY_SWAP_CHAIN,
WindowSwapChainNode::OUT_TEXTURE, WindowSwapChainNode::OUT_TEXTURE,
node::UI_PASS, node::UI_PASS,
"color", if msaa.samples > 1 {
"color_resolve_target"
} else {
"color_attachment"
},
) )
.unwrap(); .unwrap();
@ -122,6 +141,17 @@ impl UiRenderGraphBuilder for RenderGraph {
) )
.unwrap(); .unwrap();
if msaa.samples > 1 {
self.add_slot_edge(
base::node::MAIN_SAMPLED_COLOR_ATTACHMENT,
WindowSwapChainNode::OUT_TEXTURE,
node::UI_PASS,
"color_attachment",
)
.unwrap();
}
// ensure ui pass runs after main pass // ensure ui pass runs after main pass
self.add_node_edge(base::node::MAIN_PASS, node::UI_PASS) self.add_node_edge(base::node::MAIN_PASS, node::UI_PASS)
.unwrap(); .unwrap();

View File

@ -5,7 +5,7 @@ use bevy_math::Size;
use bevy_render::{ use bevy_render::{
draw::{Draw, DrawContext, Drawable}, draw::{Draw, DrawContext, Drawable},
renderer::{AssetRenderResourceBindings, RenderResourceBindings}, renderer::{AssetRenderResourceBindings, RenderResourceBindings},
texture::Texture, texture::Texture, prelude::Msaa,
}; };
use bevy_sprite::TextureAtlas; use bevy_sprite::TextureAtlas;
use bevy_text::{DrawableText, Font, FontAtlasSet, TextStyle}; use bevy_text::{DrawableText, Font, FontAtlasSet, TextStyle};
@ -51,6 +51,7 @@ pub fn text_system(
pub fn draw_text_system( pub fn draw_text_system(
mut draw_context: DrawContext, mut draw_context: DrawContext,
fonts: Res<Assets<Font>>, fonts: Res<Assets<Font>>,
msaa: Res<Msaa>,
font_atlas_sets: Res<Assets<FontAtlasSet>>, font_atlas_sets: Res<Assets<FontAtlasSet>>,
texture_atlases: Res<Assets<TextureAtlas>>, texture_atlases: Res<Assets<TextureAtlas>>,
mut render_resource_bindings: ResMut<RenderResourceBindings>, mut render_resource_bindings: ResMut<RenderResourceBindings>,
@ -69,6 +70,7 @@ pub fn draw_text_system(
render_resource_bindings: &mut render_resource_bindings, render_resource_bindings: &mut render_resource_bindings,
asset_render_resource_bindings: &mut asset_render_resource_bindings, asset_render_resource_bindings: &mut asset_render_resource_bindings,
position, position,
msaa: &msaa,
style: &text.style, style: &text.style,
text: &text.value, text: &text.value,
container_size: node.size, container_size: node.size,

45
examples/3d/msaa.rs Normal file
View File

@ -0,0 +1,45 @@
use bevy::prelude::*;
/// This example shows how to configure Multi-Sample Anti-Aliasing. Setting the sample count higher will result in smoother edges,
/// but it will also increase the cost to render those edges. The range should generally be somewhere between 1 (no multi sampling,
/// but cheap) to 8 (crisp but expensive)
fn main() {
App::build()
.add_resource(Msaa { samples: 4 })
.add_default_plugins()
.add_startup_system(setup.system())
.run();
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// add entities to the world
commands
// cube
.spawn(PbrComponents {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(StandardMaterial {
albedo: Color::rgb(0.5, 0.4, 0.3),
..Default::default()
}),
..Default::default()
})
// light
.spawn(LightComponents {
translation: Translation::new(4.0, 8.0, 4.0),
..Default::default()
})
// camera
.spawn(Camera3dComponents {
transform: Transform::new_sync_disabled(Mat4::face_toward(
Vec3::new(-3.0, 3.0, 5.0),
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
)),
..Default::default()
});
}