Add optional vertex attribute for outline normals.

This commit is contained in:
Robin KAY 2022-08-07 22:16:39 +01:00
parent 3e8c7e69a2
commit 5c5c4dc5d9
4 changed files with 85 additions and 12 deletions

View File

@ -15,6 +15,7 @@ bevy = { version = "0.8", default-features = false, features = [
"bevy_asset", "bevy_asset",
"render", "render",
] } ] }
thiserror = "1.0"
[dev-dependencies] [dev-dependencies]
bevy = { version = "0.8", default-features = false, features = [ bevy = { version = "0.8", default-features = false, features = [

View File

@ -1,6 +1,9 @@
use std::f32::consts::{PI, TAU}; use std::f32::consts::{PI, TAU};
use bevy::prelude::{shape::Torus, *}; use bevy::prelude::{
shape::{Cube, Torus},
*,
};
use bevy_mod_outline::*; use bevy_mod_outline::*;
@ -33,14 +36,11 @@ fn setup(
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default() ..default()
}); });
let mut cube_mesh = Mesh::from(Cube { size: 1.0 });
cube_mesh.generate_outline_normals().unwrap();
commands commands
.spawn_bundle(PbrBundle { .spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(Torus { mesh: meshes.add(cube_mesh),
radius: 0.6,
ring_radius: 0.2,
subdivisions_segments: 20,
subdivisions_sides: 10,
})),
material: materials.add(Color::rgb(0.1, 0.1, 0.9).into()), material: materials.add(Color::rgb(0.1, 0.1, 0.9).into()),
transform: Transform::from_xyz(0.0, 1.0, 0.0), transform: Transform::from_xyz(0.0, 1.0, 0.0),
..default() ..default()

View File

@ -4,10 +4,12 @@ use bevy::prelude::*;
use bevy::render::extract_component::{ use bevy::render::extract_component::{
ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
}; };
use bevy::render::mesh::{MeshVertexAttribute, VertexAttributeValues};
use bevy::render::render_graph::RenderGraph; use bevy::render::render_graph::RenderGraph;
use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}; use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions};
use bevy::render::render_resource::SpecializedMeshPipelines; use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat};
use bevy::render::{RenderApp, RenderStage}; use bevy::render::{RenderApp, RenderStage};
use bevy::utils::{FloatOrd, HashMap};
use crate::draw::{queue_outline_mesh, queue_outline_stencil_mesh, DrawOutline, DrawStencil}; use crate::draw::{queue_outline_mesh, queue_outline_stencil_mesh, DrawOutline, DrawStencil};
use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline}; use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline};
@ -27,7 +29,14 @@ mod view_uniforms;
// See https://alexanderameye.github.io/notes/rendering-outlines/ // See https://alexanderameye.github.io/notes/rendering-outlines/
/// A component for stenciling meshes during outline rendering /// The direction to extrude the vertex when rendering the outline.
pub const ATTRIBUTE_OUTLINE_NORMAL: MeshVertexAttribute = MeshVertexAttribute::new(
"Outline_Normal",
1585570526414773879,
VertexFormat::Float32x3,
);
/// A component for stenciling meshes during outline rendering.
#[derive(Component, Default)] #[derive(Component, Default)]
pub struct OutlineStencil; pub struct OutlineStencil;
@ -49,6 +58,61 @@ pub struct Outline {
pub colour: Color, pub colour: Color,
} }
/// Failed to generate outline normals for the mesh.
#[derive(thiserror::Error, Debug)]
pub enum GenerateOutlineNormalsError {
#[error("missing vertex attributes '{0}'")]
MissingVertexAttribute(&'static str),
#[error("the '{0}' vertex attribute should have {1:?} format, but had {2:?} format")]
InvalidVertexAttributeFormat(&'static str, VertexFormat, VertexFormat),
}
pub trait OutlineMeshExt {
/// Generates outline normals for the mesh by normalising the sum of the regular normals.
fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError>;
}
impl OutlineMeshExt for Mesh {
fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError> {
let positions = match self.attribute(Mesh::ATTRIBUTE_POSITION).ok_or(
GenerateOutlineNormalsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
)? {
VertexAttributeValues::Float32x3(p) => Ok(p),
v => Err(GenerateOutlineNormalsError::InvalidVertexAttributeFormat(
Mesh::ATTRIBUTE_POSITION.name,
VertexFormat::Float32x3,
v.into(),
)),
}?;
let normals = match self.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or(
GenerateOutlineNormalsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
)? {
VertexAttributeValues::Float32x3(n) => Ok(n),
v => Err(GenerateOutlineNormalsError::InvalidVertexAttributeFormat(
Mesh::ATTRIBUTE_NORMAL.name,
VertexFormat::Float32x3,
v.into(),
)),
}?;
let mut map = HashMap::with_capacity(positions.len());
for (p, n) in positions.iter().zip(normals.iter()) {
let key = [FloatOrd(p[0]), FloatOrd(p[1]), FloatOrd(p[2])];
let value = Vec3::from_array(*n);
map.entry(key).and_modify(|e| *e += value).or_insert(value);
}
let mut outlines = Vec::with_capacity(positions.len());
for p in positions.iter() {
let key = [FloatOrd(p[0]), FloatOrd(p[1]), FloatOrd(p[2])];
outlines.push(map.get(&key).unwrap().normalize_or_zero().to_array());
}
self.insert_attribute(
ATTRIBUTE_OUTLINE_NORMAL,
VertexAttributeValues::Float32x3(outlines),
);
Ok(())
}
}
/// Adds support for rendering outlines. /// Adds support for rendering outlines.
pub struct OutlinePlugin; pub struct OutlinePlugin;

View File

@ -22,6 +22,7 @@ use bevy::{
use crate::uniforms::{OutlineFragmentUniform, OutlineVertexUniform}; use crate::uniforms::{OutlineFragmentUniform, OutlineVertexUniform};
use crate::view_uniforms::OutlineViewUniform; use crate::view_uniforms::OutlineViewUniform;
use crate::ATTRIBUTE_OUTLINE_NORMAL;
pub const COMMON_SHADER_HANDLE: HandleUntyped = pub const COMMON_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 9448276477068917228); HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 9448276477068917228);
@ -108,7 +109,7 @@ impl SpecializedMeshPipeline for OutlinePipeline {
fn specialize( fn specialize(
&self, &self,
(key, pass_type): Self::Key, (key, pass_type): Self::Key,
layout: &MeshVertexBufferLayout, mesh_layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> { ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut targets = vec![]; let mut targets = vec![];
let mut bind_layouts = vec![ let mut bind_layouts = vec![
@ -135,10 +136,17 @@ impl SpecializedMeshPipeline for OutlinePipeline {
bind_layouts.push(self.outline_view_bind_group_layout.clone()); bind_layouts.push(self.outline_view_bind_group_layout.clone());
bind_layouts.push(self.outline_bind_group_layout.clone()); bind_layouts.push(self.outline_bind_group_layout.clone());
buffer_attrs.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1)); buffer_attrs.push(
if mesh_layout.contains(ATTRIBUTE_OUTLINE_NORMAL) {
ATTRIBUTE_OUTLINE_NORMAL
} else {
Mesh::ATTRIBUTE_NORMAL
}
.at_shader_location(1),
);
} }
} }
let buffers = vec![layout.get_layout(&buffer_attrs)?]; let buffers = vec![mesh_layout.get_layout(&buffer_attrs)?];
Ok(RenderPipelineDescriptor { Ok(RenderPipelineDescriptor {
vertex: VertexState { vertex: VertexState {
shader: shader.clone().typed::<Shader>(), shader: shader.clone().typed::<Shader>(),