From 82d849d3dc4f83a8a02a369b9e636139d96e710f Mon Sep 17 00:00:00 2001 From: Dusty DeWeese Date: Thu, 5 May 2022 00:46:32 +0000 Subject: [PATCH] Add support for vertex colors (#4528) # Objective Add support for vertex colors ## Solution This change is modeled after how vertex tangents are handled, so the shader is conditionally compiled with vertex color support if the mesh has the corresponding attribute set. Vertex colors are multiplied by the base color. I'm not sure if this is the best for all cases, but may be useful for modifying vertex colors without creating a new mesh. I chose `VertexFormat::Float32x4`, but I'd prefer 16-bit floats if/when support is added. ## Changelog ### Added - Vertex colors can be specified using the `Mesh::ATTRIBUTE_COLOR` mesh attribute. --- Cargo.toml | 12 +++-- crates/bevy_gltf/src/loader.rs | 12 ++--- crates/bevy_pbr/src/render/mesh.rs | 9 +++- crates/bevy_pbr/src/render/mesh.wgsl | 22 +++++++-- crates/bevy_pbr/src/render/pbr.wgsl | 6 +++ crates/bevy_render/src/mesh/mesh/mod.rs | 2 +- crates/bevy_sprite/src/mesh2d/mesh.rs | 5 +++ examples/2d/mesh2d_manual.rs | 7 ++- examples/3d/vertex_colors.rs | 59 +++++++++++++++++++++++++ examples/README.md | 1 + 10 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 examples/3d/vertex_colors.rs diff --git a/Cargo.toml b/Cargo.toml index 60b424bf56..e19ca3575a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -196,6 +196,10 @@ path = "examples/3d/parenting.rs" name = "pbr" path = "examples/3d/pbr.rs" +[[example]] +name = "render_to_texture" +path = "examples/3d/render_to_texture.rs" + [[example]] name = "shadow_biases" path = "examples/3d/shadow_biases.rs" @@ -216,10 +220,6 @@ path = "examples/3d/spherical_area_lights.rs" name = "texture" path = "examples/3d/texture.rs" -[[example]] -name = "render_to_texture" -path = "examples/3d/render_to_texture.rs" - [[example]] name = "two_passes" path = "examples/3d/two_passes.rs" @@ -228,6 +228,10 @@ path = "examples/3d/two_passes.rs" name = "update_gltf_scene" path = "examples/3d/update_gltf_scene.rs" +[[example]] +name = "vertex_colors" +path = "examples/3d/vertex_colors.rs" + [[example]] name = "wireframe" path = "examples/3d/wireframe.rs" diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 2b95f3e554..9233d619a8 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -269,12 +269,12 @@ async fn load_gltf<'a, 'b>( mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); } - // if let Some(vertex_attribute) = reader - // .read_colors(0) - // .map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect())) - // { - // mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute); - // } + if let Some(vertex_attribute) = reader + .read_colors(0) + .map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect())) + { + mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute); + } if let Some(iter) = reader.read_joints(0) { let vertex_attribute = VertexAttributeValues::Uint16x4(iter.into_u16().collect()); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 2ccc6fde6c..f90d3894bb 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -560,6 +560,11 @@ impl SpecializedMeshPipeline for MeshPipeline { vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } + if layout.contains(Mesh::ATTRIBUTE_COLOR) { + shader_defs.push(String::from("VERTEX_COLORS")); + vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4)); + } + // TODO: consider exposing this in shaders in a more generally useful way, such as: // # if AVAILABLE_STORAGE_BUFFER_BINDINGS == 3 // /* use storage buffers here */ @@ -577,8 +582,8 @@ impl SpecializedMeshPipeline for MeshPipeline { && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) { shader_defs.push(String::from("SKINNED")); - vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(4)); - vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(5)); + vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(5)); + vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(6)); bind_group_layout.push(self.skinned_mesh_layout.clone()); } else { bind_group_layout.push(self.mesh_layout.clone()); diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index ffa26f4b94..2adaab7c08 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -8,9 +8,12 @@ struct Vertex { #ifdef VERTEX_TANGENTS [[location(3)]] tangent: vec4; #endif +#ifdef VERTEX_COLORS + [[location(4)]] color: vec4; +#endif #ifdef SKINNED - [[location(4)]] joint_indices: vec4; - [[location(5)]] joint_weights: vec4; + [[location(5)]] joint_indices: vec4; + [[location(6)]] joint_weights: vec4; #endif }; @@ -22,6 +25,9 @@ struct VertexOutput { #ifdef VERTEX_TANGENTS [[location(3)]] world_tangent: vec4; #endif +#ifdef VERTEX_COLORS + [[location(4)]] color: vec4; +#endif }; [[group(2), binding(0)]] @@ -60,6 +66,9 @@ fn vertex(vertex: Vertex) -> VertexOutput { ); #endif #endif +#ifdef VERTEX_COLORS + out.color = vertex.color; +#endif out.uv = vertex.uv; out.clip_position = view.view_proj * out.world_position; @@ -74,9 +83,16 @@ struct FragmentInput { #ifdef VERTEX_TANGENTS [[location(3)]] world_tangent: vec4; #endif +#ifdef VERTEX_COLORS + [[location(4)]] color: vec4; +#endif }; [[stage(fragment)]] fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { +#ifdef VERTEX_COLORS + return in.color; +#else return vec4(1.0, 0.0, 1.0, 1.0); -} \ No newline at end of file +#endif +} diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 8e365cdd38..ee38107169 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -465,11 +465,17 @@ struct FragmentInput { #ifdef VERTEX_TANGENTS [[location(3)]] world_tangent: vec4; #endif +#ifdef VERTEX_COLORS + [[location(4)]] color: vec4; +#endif }; [[stage(fragment)]] fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { var output_color: vec4 = material.base_color; + #ifdef VERTEX_COLORS + output_color = output_color * in.color; + #endif if ((material.flags & STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { output_color = output_color * textureSample(base_color_texture, base_color_sampler, in.uv); } diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 5618ebba20..bcd0084cab 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -74,7 +74,7 @@ impl Mesh { /// Per vertex coloring. Use in conjunction with [`Mesh::insert_attribute`] pub const ATTRIBUTE_COLOR: MeshVertexAttribute = - MeshVertexAttribute::new("Vertex_Color", 4, VertexFormat::Uint32); + MeshVertexAttribute::new("Vertex_Color", 4, VertexFormat::Float32x4); /// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::insert_attribute`] pub const ATTRIBUTE_JOINT_WEIGHT: MeshVertexAttribute = diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 47c4efbbb2..eb7f40b751 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -295,6 +295,11 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } + if layout.contains(Mesh::ATTRIBUTE_COLOR) { + shader_defs.push(String::from("VERTEX_COLORS")); + vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4)); + } + #[cfg(feature = "webgl")] shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT")); diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 201e32571f..e585018aa5 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -3,7 +3,7 @@ use bevy::{ prelude::*, reflect::TypeUuid, render::{ - mesh::Indices, + mesh::{Indices, MeshVertexAttribute}, render_asset::RenderAssets, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ @@ -72,7 +72,10 @@ fn star( // And a RGB color attribute as well let mut v_color: Vec = vec![Color::BLACK.as_linear_rgba_u32()]; v_color.extend_from_slice(&[Color::YELLOW.as_linear_rgba_u32(); 10]); - star.insert_attribute(Mesh::ATTRIBUTE_COLOR, v_color); + star.insert_attribute( + MeshVertexAttribute::new("Vertex_Color", 1, VertexFormat::Uint32), + v_color, + ); // Now, we specify the indices of the vertex that are going to compose the // triangles in our star. Vertices in triangles have to be specified in CCW diff --git a/examples/3d/vertex_colors.rs b/examples/3d/vertex_colors.rs new file mode 100644 index 0000000000..4050230ae7 --- /dev/null +++ b/examples/3d/vertex_colors.rs @@ -0,0 +1,59 @@ +use bevy::{prelude::*, render::mesh::VertexAttributeValues}; + +fn main() { + App::new() + .insert_resource(Msaa { samples: 4 }) + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .run(); +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // 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()), + ..default() + }); + // cube + // Assign vertex colors based on vertex positions + let mut colorful_cube = Mesh::from(shape::Cube { size: 1.0 }); + if let Some(VertexAttributeValues::Float32x3(positions)) = + colorful_cube.attribute(Mesh::ATTRIBUTE_POSITION) + { + let colors: Vec<[f32; 4]> = positions + .iter() + .map(|[r, g, b]| [(1. - *r) / 2., (1. - *g) / 2., (1. - *b) / 2., 1.]) + .collect(); + colorful_cube.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors); + } + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(colorful_cube), + // This is the default color, but note that vertex colors are + // multiplied by the base color, so you'll likely want this to be + // white if using vertex colors. + material: materials.add(Color::rgb(1., 1., 1.).into()), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }); + // light + commands.spawn_bundle(PointLightBundle { + point_light: PointLight { + intensity: 1500.0, + shadows_enabled: true, + ..default() + }, + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + // camera + commands.spawn_bundle(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); +} diff --git a/examples/README.md b/examples/README.md index 041d048f35..2b928cf49a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -115,6 +115,7 @@ Example | File | Description `spherical_area_lights` | [`3d/spherical_area_lights.rs`](./3d/spherical_area_lights.rs) | Demonstrates how point light radius values affect light behavior. `texture` | [`3d/texture.rs`](./3d/texture.rs) | Shows configuration of texture materials `update_gltf_scene` | [`3d/update_gltf_scene.rs`](./3d/update_gltf_scene.rs) | Update a scene from a gltf file, either by spawning the scene as a child of another entity, or by accessing the entities of the scene +`vertex_colors` | [`3d/vertex_colors.rs`](./3d/vertex_colors.rs) | Shows the use of vertex colors `wireframe` | [`3d/wireframe.rs`](./3d/wireframe.rs) | Showcases wireframe rendering `3d_shapes` | [`3d/shapes.rs`](./3d/shapes.rs) | A scene showcasing the built-in 3D shapes