Skip to content

Commit

Permalink
Add support for custom glTF vertex attributes. (#5370)
Browse files Browse the repository at this point in the history
# Objective

The objective is to be able to load data from "application-specific"
(see glTF spec 3.7.2.1.) vertex attribute semantics from glTF files into
Bevy meshes.

## Solution

Rather than probe the glTF for the specific attributes supported by
Bevy, this PR changes the loader to iterate through all the attributes
and map them onto `MeshVertexAttribute`s. This mapping includes all the
previously supported attributes, plus it is now possible to add mappings
using the `add_custom_vertex_attribute()` method on `GltfPlugin`.

## Changelog

- Add support for loading custom vertex attributes from glTF files.
- Add the `custom_gltf_vertex_attribute.rs` example to illustrate
loading custom vertex attributes.

## Migration Guide

- If you were instantiating `GltfPlugin` using the unit-like struct
syntax, you must instead use `GltfPlugin::default()` as the type is no
longer unit-like.
  • Loading branch information
komadori authored Apr 24, 2023
1 parent 5dec323 commit d74533b
Show file tree
Hide file tree
Showing 9 changed files with 561 additions and 72 deletions.
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@
* Low poly fox [by PixelMannen](https://opengameart.org/content/fox-and-shiba) (CC0 1.0 Universal)
* Rigging and animation [by @tomkranis on Sketchfab](https://sketchfab.com/models/371dea88d7e04a76af5763f2a36866bc) ([CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/))
* FiraMono by The Mozilla Foundation and Telefonica S.A (SIL Open Font License, Version 1.1: assets/fonts/FiraMono-LICENSE)
* Barycentric from [mk_bary_gltf](https://github.com/komadori/mk_bary_gltf) (MIT OR Apache-2.0)
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,16 @@ description = "Renders a rectangle, circle, and hexagon"
category = "2D Rendering"
wasm = true

[[example]]
name = "custom_gltf_vertex_attribute"
path = "examples/2d/custom_gltf_vertex_attribute.rs"

[package.metadata.example.custom_gltf_vertex_attribute]
name = "Custom glTF vertex attribute 2D"
description = "Renders a glTF mesh in 2D with a custom vertex attribute"
category = "2D Rendering"
wasm = true

[[example]]
name = "2d_gizmos"
path = "examples/2d/2d_gizmos.rs"
Expand Down
80 changes: 80 additions & 0 deletions assets/models/barycentric/barycentric.gltf
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"count": 4,
"componentType": 5126,
"type": "VEC3",
"min": [
-1.0,
-1.0,
0.0
],
"max": [
1.0,
1.0,
0.0
]
},
{
"bufferView": 0,
"byteOffset": 12,
"count": 4,
"componentType": 5126,
"type": "VEC4"
},
{
"bufferView": 0,
"byteOffset": 28,
"count": 4,
"componentType": 5126,
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 0,
"count": 6,
"componentType": 5123,
"type": "SCALAR"
}
],
"asset": {
"version": "2.0"
},
"buffers": [
{
"byteLength": 172,
"uri": "data:application/gltf-buffer;base64,AACAvwAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAgD8AAIC/AAAAAAAAAD8AAAA/AAAAAAAAgD8AAAAAAACAPwAAAAAAAIC/AACAPwAAAAAAAAA/AAAAPwAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAQACAAIAAQADAA=="
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 160,
"byteOffset": 0,
"byteStride": 40,
"target": 34962
},
{
"buffer": 0,
"byteLength": 12,
"byteOffset": 160,
"target": 34962
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0,
"COLOR_0": 1,
"__BARYCENTRIC": 2
},
"indices": 3
}
]
}
]
}
36 changes: 36 additions & 0 deletions assets/shaders/custom_gltf_2d.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#import bevy_sprite::mesh2d_view_bindings
#import bevy_sprite::mesh2d_bindings
#import bevy_sprite::mesh2d_functions

struct Vertex {
@location(0) position: vec3<f32>,
@location(1) color: vec4<f32>,
@location(2) barycentric: vec3<f32>,
};

struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) barycentric: vec3<f32>,
};

@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
out.clip_position = mesh2d_position_local_to_clip(mesh.model, vec4<f32>(vertex.position, 1.0));
out.color = vertex.color;
out.barycentric = vertex.barycentric;
return out;
}

struct FragmentInput {
@location(0) color: vec4<f32>,
@location(1) barycentric: vec3<f32>,
};

@fragment
fn fragment(input: FragmentInput) -> @location(0) vec4<f32> {
let d = min(input.barycentric.x, min(input.barycentric.y, input.barycentric.z));
let t = 0.05 * (0.85 + sin(5.0 * globals.time));
return mix(vec4(1.0,1.0,1.0,1.0), input.color, smoothstep(t, t+0.01, d));
}
43 changes: 35 additions & 8 deletions crates/bevy_gltf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,55 @@ use bevy_animation::AnimationClip;
use bevy_utils::HashMap;

mod loader;
mod vertex_attributes;
pub use loader::*;

use bevy_app::prelude::*;
use bevy_asset::{AddAsset, Handle};
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
use bevy_pbr::StandardMaterial;
use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::mesh::Mesh;
use bevy_render::{
mesh::{Mesh, MeshVertexAttribute},
renderer::RenderDevice,
texture::CompressedImageFormats,
};
use bevy_scene::Scene;

/// Adds support for glTF file loading to the app.
#[derive(Default)]
pub struct GltfPlugin;
pub struct GltfPlugin {
custom_vertex_attributes: HashMap<String, MeshVertexAttribute>,
}

impl GltfPlugin {
pub fn add_custom_vertex_attribute(
mut self,
name: &str,
attribute: MeshVertexAttribute,
) -> Self {
self.custom_vertex_attributes
.insert(name.to_string(), attribute);
self
}
}

impl Plugin for GltfPlugin {
fn build(&self, app: &mut App) {
app.init_asset_loader::<GltfLoader>()
.register_type::<GltfExtras>()
.add_asset::<Gltf>()
.add_asset::<GltfNode>()
.add_asset::<GltfPrimitive>()
.add_asset::<GltfMesh>();
let supported_compressed_formats = match app.world.get_resource::<RenderDevice>() {
Some(render_device) => CompressedImageFormats::from_features(render_device.features()),

None => CompressedImageFormats::all(),
};
app.add_asset_loader::<GltfLoader>(GltfLoader {
supported_compressed_formats,
custom_vertex_attributes: self.custom_vertex_attributes.clone(),
})
.register_type::<GltfExtras>()
.add_asset::<Gltf>()
.add_asset::<GltfNode>()
.add_asset::<GltfPrimitive>()
.add_asset::<GltfMesh>();
}
}

Expand Down
92 changes: 28 additions & 64 deletions crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bevy_asset::{
};
use bevy_core::Name;
use bevy_core_pipeline::prelude::Camera3dBundle;
use bevy_ecs::{entity::Entity, prelude::FromWorld, world::World};
use bevy_ecs::{entity::Entity, world::World};
use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder};
use bevy_log::warn;
use bevy_math::{Mat4, Vec3};
Expand All @@ -17,12 +17,11 @@ use bevy_render::{
color::Color,
mesh::{
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
Indices, Mesh, VertexAttributeValues,
Indices, Mesh, MeshVertexAttribute, VertexAttributeValues,
},
prelude::SpatialBundle,
primitives::Aabb,
render_resource::{AddressMode, Face, FilterMode, PrimitiveTopology, SamplerDescriptor},
renderer::RenderDevice,
texture::{CompressedImageFormats, Image, ImageSampler, ImageType, TextureError},
};
use bevy_scene::Scene;
Expand All @@ -32,13 +31,14 @@ use bevy_transform::components::Transform;

use bevy_utils::{HashMap, HashSet};
use gltf::{
mesh::Mode,
mesh::{util::ReadIndices, Mode},
texture::{MagFilter, MinFilter, WrappingMode},
Material, Node, Primitive,
};
use std::{collections::VecDeque, path::Path};
use thiserror::Error;

use crate::vertex_attributes::*;
use crate::{Gltf, GltfExtras, GltfNode};

/// An error that occurs when loading a glTF file.
Expand Down Expand Up @@ -68,7 +68,8 @@ pub enum GltfError {

/// Loads glTF files with all of their data as their corresponding bevy representations.
pub struct GltfLoader {
supported_compressed_formats: CompressedImageFormats,
pub(crate) supported_compressed_formats: CompressedImageFormats,
pub(crate) custom_vertex_attributes: HashMap<String, MeshVertexAttribute>,
}

impl AssetLoader for GltfLoader {
Expand All @@ -77,34 +78,19 @@ impl AssetLoader for GltfLoader {
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<()>> {
Box::pin(async move {
Ok(load_gltf(bytes, load_context, self.supported_compressed_formats).await?)
})
Box::pin(async move { Ok(load_gltf(bytes, load_context, self).await?) })
}

fn extensions(&self) -> &[&str] {
&["gltf", "glb"]
}
}

impl FromWorld for GltfLoader {
fn from_world(world: &mut World) -> Self {
let supported_compressed_formats = match world.get_resource::<RenderDevice>() {
Some(render_device) => CompressedImageFormats::from_features(render_device.features()),

None => CompressedImageFormats::all(),
};
Self {
supported_compressed_formats,
}
}
}

/// Loads an entire glTF file.
async fn load_gltf<'a, 'b>(
bytes: &'a [u8],
load_context: &'a mut LoadContext<'b>,
supported_compressed_formats: CompressedImageFormats,
loader: &GltfLoader,
) -> Result<(), GltfError> {
let gltf = gltf::Gltf::from_slice(bytes)?;
let buffer_data = load_buffers(&gltf, load_context, load_context.path()).await?;
Expand Down Expand Up @@ -233,53 +219,31 @@ async fn load_gltf<'a, 'b>(
let mut primitives = vec![];
for primitive in mesh.primitives() {
let primitive_label = primitive_label(&mesh, &primitive);
let reader = primitive.reader(|buffer| Some(&buffer_data[buffer.index()]));
let primitive_topology = get_primitive_topology(primitive.mode())?;

let mut mesh = Mesh::new(primitive_topology);

if let Some(vertex_attribute) = reader
.read_positions()
.map(|v| VertexAttributeValues::Float32x3(v.collect()))
{
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_attribute);
}

if let Some(vertex_attribute) = reader
.read_normals()
.map(|v| VertexAttributeValues::Float32x3(v.collect()))
{
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute);
}

if let Some(vertex_attribute) = reader
.read_tex_coords(0)
.map(|v| VertexAttributeValues::Float32x2(v.into_f32().collect()))
{
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, 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());
mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_INDEX, vertex_attribute);
}

if let Some(vertex_attribute) = reader
.read_weights(0)
.map(|v| VertexAttributeValues::Float32x4(v.into_f32().collect()))
{
mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_WEIGHT, vertex_attribute);
// Read vertex attributes
for (semantic, accessor) in primitive.attributes() {
match convert_attribute(
semantic,
accessor,
&buffer_data,
&loader.custom_vertex_attributes,
) {
Ok((attribute, values)) => mesh.insert_attribute(attribute, values),
Err(err) => warn!("{}", err),
}
}

// Read vertex indices
let reader = primitive.reader(|buffer| Some(buffer_data[buffer.index()].as_slice()));
if let Some(indices) = reader.read_indices() {
mesh.set_indices(Some(Indices::U32(indices.into_u32().collect())));
mesh.set_indices(Some(match indices {
ReadIndices::U8(is) => Indices::U16(is.map(|x| x as u16).collect()),
ReadIndices::U16(is) => Indices::U16(is.collect()),
ReadIndices::U32(is) => Indices::U32(is.collect()),
}));
};

if mesh.attribute(Mesh::ATTRIBUTE_NORMAL).is_none()
Expand Down Expand Up @@ -403,7 +367,7 @@ async fn load_gltf<'a, 'b>(
&buffer_data,
&linear_textures,
load_context,
supported_compressed_formats,
loader.supported_compressed_formats,
)
.await?;
load_context.set_labeled_asset(&label, LoadedAsset::new(texture));
Expand All @@ -422,7 +386,7 @@ async fn load_gltf<'a, 'b>(
buffer_data,
linear_textures,
load_context,
supported_compressed_formats,
loader.supported_compressed_formats,
)
.await
});
Expand Down
Loading

0 comments on commit d74533b

Please sign in to comment.