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.
This commit is contained in:
Dusty DeWeese 2022-05-05 00:46:32 +00:00
parent f8e0fc190a
commit 82d849d3dc
10 changed files with 117 additions and 18 deletions

View file

@ -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"

View file

@ -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());

View file

@ -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());

View file

@ -8,9 +8,12 @@ struct Vertex {
#ifdef VERTEX_TANGENTS
[[location(3)]] tangent: vec4<f32>;
#endif
#ifdef VERTEX_COLORS
[[location(4)]] color: vec4<f32>;
#endif
#ifdef SKINNED
[[location(4)]] joint_indices: vec4<u32>;
[[location(5)]] joint_weights: vec4<f32>;
[[location(5)]] joint_indices: vec4<u32>;
[[location(6)]] joint_weights: vec4<f32>;
#endif
};
@ -22,6 +25,9 @@ struct VertexOutput {
#ifdef VERTEX_TANGENTS
[[location(3)]] world_tangent: vec4<f32>;
#endif
#ifdef VERTEX_COLORS
[[location(4)]] color: vec4<f32>;
#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<f32>;
#endif
#ifdef VERTEX_COLORS
[[location(4)]] color: vec4<f32>;
#endif
};
[[stage(fragment)]]
fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
#ifdef VERTEX_COLORS
return in.color;
#else
return vec4<f32>(1.0, 0.0, 1.0, 1.0);
}
#endif
}

View file

@ -465,11 +465,17 @@ struct FragmentInput {
#ifdef VERTEX_TANGENTS
[[location(3)]] world_tangent: vec4<f32>;
#endif
#ifdef VERTEX_COLORS
[[location(4)]] color: vec4<f32>;
#endif
};
[[stage(fragment)]]
fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
var output_color: vec4<f32> = 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);
}

View file

@ -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 =

View file

@ -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"));

View file

@ -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<u32> = 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

View file

@ -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<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 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()
});
}

View file

@ -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