Add bevy_fbx, an fbx loader based on ufbx
This commit is contained in:
parent
f1eace62f0
commit
4dc363c10b
14
Cargo.toml
14
Cargo.toml
@ -138,6 +138,7 @@ default = [
|
||||
"bevy_gilrs",
|
||||
"bevy_gizmos",
|
||||
"bevy_gltf",
|
||||
"bevy_fbx",
|
||||
"bevy_input_focus",
|
||||
"bevy_log",
|
||||
"bevy_mesh_picking_backend",
|
||||
@ -230,6 +231,8 @@ bevy_gilrs = ["bevy_internal/bevy_gilrs"]
|
||||
|
||||
# [glTF](https://www.khronos.org/gltf/) support
|
||||
bevy_gltf = ["bevy_internal/bevy_gltf", "bevy_asset", "bevy_scene", "bevy_pbr"]
|
||||
# [FBX](https://www.autodesk.com/products/fbx)
|
||||
bevy_fbx = ["bevy_internal/bevy_fbx", "bevy_asset", "bevy_scene", "bevy_pbr", "bevy_animation"]
|
||||
|
||||
# Adds PBR rendering
|
||||
bevy_pbr = [
|
||||
@ -1196,6 +1199,17 @@ description = "Loads and renders a glTF file as a scene, including the gltf extr
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "load_fbx"
|
||||
path = "examples/3d/load_fbx.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.load_fbx]
|
||||
name = "Load FBX"
|
||||
description = "Loads and renders an FBX file as a scene"
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "query_gltf_primitives"
|
||||
path = "examples/3d/query_gltf_primitives.rs"
|
||||
|
BIN
assets/models/cube/cube.fbx
Normal file
BIN
assets/models/cube/cube.fbx
Normal file
Binary file not shown.
37
crates/bevy_fbx/Cargo.toml
Normal file
37
crates/bevy_fbx/Cargo.toml
Normal file
@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "bevy_fbx"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
description = "Bevy Engine FBX loading"
|
||||
homepage = "https://bevyengine.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_scene = { path = "../bevy_scene", version = "0.16.0-dev", features = ["bevy_render"] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.16.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = ["std"] }
|
||||
bevy_animation = { path = "../bevy_animation", version = "0.16.0-dev" }
|
||||
thiserror = "1"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
ufbx = "0.8"
|
||||
|
||||
[dev-dependencies]
|
||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
|
||||
all-features = true
|
5
crates/bevy_fbx/README.md
Normal file
5
crates/bevy_fbx/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Bevy FBX Loader
|
||||
|
||||
This crate provides basic support for importing FBX files into Bevy using the [`ufbx`](https://github.com/ufbx/ufbx-rust) library.
|
||||
|
||||
The loader converts meshes contained in an FBX scene into Bevy [`Mesh`] assets and groups them into a [`Scene`].
|
41
crates/bevy_fbx/src/label.rs
Normal file
41
crates/bevy_fbx/src/label.rs
Normal file
@ -0,0 +1,41 @@
|
||||
//! Labels that can be used to load part of an FBX asset
|
||||
|
||||
use bevy_asset::AssetPath;
|
||||
|
||||
/// Labels that can be used to load part of an FBX
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FbxAssetLabel {
|
||||
/// `Scene{}`: FBX Scene as a Bevy [`Scene`](bevy_scene::Scene)
|
||||
Scene(usize),
|
||||
/// `Mesh{}`: FBX Mesh as a Bevy [`Mesh`](bevy_mesh::Mesh)
|
||||
Mesh(usize),
|
||||
/// `Material{}`: FBX material as a Bevy [`StandardMaterial`](bevy_pbr::StandardMaterial)
|
||||
Material(usize),
|
||||
/// `Animation{}`: FBX animation as a Bevy [`AnimationClip`](bevy_animation::AnimationClip)
|
||||
Animation(usize),
|
||||
/// `Skeleton{}`: FBX skeleton as a Bevy [`Skeleton`](crate::Skeleton)
|
||||
Skeleton(usize),
|
||||
/// `DefaultMaterial`: fallback material used when no material is present
|
||||
DefaultMaterial,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for FbxAssetLabel {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
FbxAssetLabel::Scene(index) => f.write_str(&format!("Scene{index}")),
|
||||
FbxAssetLabel::Mesh(index) => f.write_str(&format!("Mesh{index}")),
|
||||
FbxAssetLabel::Material(index) => f.write_str(&format!("Material{index}")),
|
||||
FbxAssetLabel::Animation(index) => f.write_str(&format!("Animation{index}")),
|
||||
FbxAssetLabel::Skeleton(index) => f.write_str(&format!("Skeleton{index}")),
|
||||
FbxAssetLabel::DefaultMaterial => f.write_str("DefaultMaterial"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FbxAssetLabel {
|
||||
/// Add this label to an asset path
|
||||
pub fn from_asset(&self, path: impl Into<AssetPath<'static>>) -> AssetPath<'static> {
|
||||
path.into().with_label(self.to_string())
|
||||
}
|
||||
}
|
||||
|
350
crates/bevy_fbx/src/lib.rs
Normal file
350
crates/bevy_fbx/src/lib.rs
Normal file
@ -0,0 +1,350 @@
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![doc(
|
||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
||||
)]
|
||||
|
||||
//!
|
||||
//! Loader for FBX scenes using [`ufbx`](https://github.com/ufbx/ufbx-rust).
|
||||
//! The implementation is intentionally minimal and focuses on importing
|
||||
//! mesh geometry into Bevy.
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{
|
||||
io::Reader, Asset, AssetApp, AssetLoader, Handle, LoadContext, RenderAssetUsages,
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_mesh::{Indices, Mesh, PrimitiveTopology};
|
||||
use bevy_pbr::{MeshMaterial3d, StandardMaterial};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::TypePath;
|
||||
use bevy_render::mesh::Mesh3d;
|
||||
use bevy_render::prelude::Visibility;
|
||||
use bevy_scene::Scene;
|
||||
use std::sync::Arc;
|
||||
use bevy_animation::AnimationClip;
|
||||
use bevy_transform::prelude::*;
|
||||
use bevy_math::{Mat4, Vec3};
|
||||
|
||||
mod label;
|
||||
pub use label::FbxAssetLabel;
|
||||
|
||||
pub mod prelude {
|
||||
//! Commonly used items.
|
||||
pub use crate::{Fbx, FbxAssetLabel, FbxPlugin};
|
||||
}
|
||||
|
||||
/// Type of relationship between two objects in the FBX hierarchy.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FbxConnKind {
|
||||
/// Standard parent-child connection.
|
||||
Parent,
|
||||
/// Connection from an object to one of its properties.
|
||||
ObjectProperty,
|
||||
/// Constraint relationship.
|
||||
Constraint,
|
||||
}
|
||||
|
||||
/// Simplified connection entry extracted from the FBX file.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FbxConnection {
|
||||
/// Source object identifier.
|
||||
pub src: String,
|
||||
/// Destination object identifier.
|
||||
pub dst: String,
|
||||
/// The type of this connection.
|
||||
pub kind: FbxConnKind,
|
||||
}
|
||||
|
||||
/// Handedness of a coordinate system.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Handedness {
|
||||
/// Right handed coordinate system.
|
||||
Right,
|
||||
/// Left handed coordinate system.
|
||||
Left,
|
||||
}
|
||||
|
||||
/// Coordinate axes definition stored in an FBX file.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FbxAxisSystem {
|
||||
/// Up axis.
|
||||
pub up: Vec3,
|
||||
/// Forward axis.
|
||||
pub front: Vec3,
|
||||
/// Coordinate system handedness.
|
||||
pub handedness: Handedness,
|
||||
}
|
||||
|
||||
/// Metadata found in the FBX header.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FbxMeta {
|
||||
/// Creator string.
|
||||
pub creator: Option<String>,
|
||||
/// Timestamp when the file was created.
|
||||
pub creation_time: Option<String>,
|
||||
/// Original application that generated the file.
|
||||
pub original_application: Option<String>,
|
||||
}
|
||||
|
||||
/// Placeholder type for skeleton data.
|
||||
#[derive(Asset, Debug, Clone, TypePath)]
|
||||
pub struct Skeleton;
|
||||
|
||||
/// Resulting asset for an FBX file.
|
||||
#[derive(Asset, Debug, TypePath)]
|
||||
pub struct Fbx {
|
||||
/* ===== Core sub-asset handles ===== */
|
||||
/// Split Bevy scenes. A single FBX may contain many scenes.
|
||||
pub scenes: Vec<Handle<Scene>>,
|
||||
/// Triangulated meshes extracted from the FBX.
|
||||
pub meshes: Vec<Handle<Mesh>>,
|
||||
/// PBR materials or fallbacks converted from FBX materials.
|
||||
pub materials: Vec<Handle<StandardMaterial>>,
|
||||
/// Flattened animation takes.
|
||||
pub animations: Vec<Handle<AnimationClip>>,
|
||||
/// Skinning skeletons.
|
||||
pub skeletons: Vec<Handle<Skeleton>>,
|
||||
|
||||
/* ===== Quick name lookups ===== */
|
||||
pub named_meshes: HashMap<Box<str>, Handle<Mesh>>,
|
||||
pub named_materials: HashMap<Box<str>, Handle<StandardMaterial>>,
|
||||
pub named_animations: HashMap<Box<str>, Handle<AnimationClip>>,
|
||||
pub named_skeletons: HashMap<Box<str>, Handle<Skeleton>>,
|
||||
|
||||
/* ===== FBX specific info ===== */
|
||||
/// Flattened parent/child/constraint relations.
|
||||
pub connections: Vec<FbxConnection>,
|
||||
/// Original axis system of the file.
|
||||
pub axis_system: FbxAxisSystem,
|
||||
/// Conversion factor from the original unit to meters.
|
||||
pub unit_scale: f32,
|
||||
/// Copyright, creator and tool information.
|
||||
pub metadata: FbxMeta,
|
||||
|
||||
/* ===== Optional original scene bytes ===== */
|
||||
#[cfg(debug_assertions)]
|
||||
pub raw_scene_bytes: Option<Arc<[u8]>>,
|
||||
}
|
||||
|
||||
/// Errors that may occur while loading an FBX asset.
|
||||
#[derive(Debug)]
|
||||
pub enum FbxError {
|
||||
/// IO error while reading the file.
|
||||
Io(std::io::Error),
|
||||
/// Error reported by the `ufbx` parser.
|
||||
Parse(String),
|
||||
}
|
||||
|
||||
impl core::fmt::Display for FbxError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
FbxError::Io(err) => write!(f, "{}", err),
|
||||
FbxError::Parse(err) => write!(f, "{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FbxError {}
|
||||
|
||||
impl From<std::io::Error> for FbxError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
FbxError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Loader implementation for FBX files.
|
||||
#[derive(Default)]
|
||||
pub struct FbxLoader;
|
||||
|
||||
impl AssetLoader for FbxLoader {
|
||||
type Asset = Fbx;
|
||||
type Settings = ();
|
||||
type Error = FbxError;
|
||||
|
||||
async fn load(
|
||||
&self,
|
||||
reader: &mut dyn Reader,
|
||||
_settings: &Self::Settings,
|
||||
load_context: &mut LoadContext<'_>,
|
||||
) -> Result<Fbx, FbxError> {
|
||||
// Read the complete file.
|
||||
let mut bytes = Vec::new();
|
||||
reader.read_to_end(&mut bytes).await?;
|
||||
|
||||
// Parse using `ufbx` and normalize the units/axes so that `1.0` equals
|
||||
// one meter and the coordinate system matches Bevy's.
|
||||
let root = ufbx::load_memory(
|
||||
&bytes,
|
||||
ufbx::LoadOpts {
|
||||
target_unit_meters: 1.0,
|
||||
target_axes: ufbx::CoordinateAxes::right_handed_y_up(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.map_err(|e| FbxError::Parse(format!("{:?}", e)))?;
|
||||
let scene: &ufbx::Scene = &*root;
|
||||
|
||||
let mut meshes = Vec::new();
|
||||
let mut named_meshes = HashMap::new();
|
||||
let mut transforms = Vec::new();
|
||||
let mut scratch = Vec::new();
|
||||
|
||||
for (index, node) in scene.nodes.as_ref().iter().enumerate() {
|
||||
let Some(mesh_ref) = node.mesh.as_ref() else { continue };
|
||||
let mesh = mesh_ref.as_ref();
|
||||
|
||||
// Each mesh becomes a Bevy `Mesh` asset.
|
||||
let handle =
|
||||
load_context.labeled_asset_scope::<_, FbxError>(FbxAssetLabel::Mesh(index).to_string(), |_lc| {
|
||||
let positions: Vec<[f32; 3]> = mesh
|
||||
.vertex_position
|
||||
.values
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|v| [v.x as f32, v.y as f32, v.z as f32])
|
||||
.collect();
|
||||
|
||||
let mut bevy_mesh = Mesh::new(
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::default(),
|
||||
);
|
||||
bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||
|
||||
if mesh.vertex_normal.exists {
|
||||
let normals: Vec<[f32; 3]> = (0..mesh.num_vertices)
|
||||
.map(|i| {
|
||||
let n = mesh.vertex_normal[i];
|
||||
[n.x as f32, n.y as f32, n.z as f32]
|
||||
})
|
||||
.collect();
|
||||
bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
|
||||
}
|
||||
|
||||
if mesh.vertex_uv.exists {
|
||||
let uvs: Vec<[f32; 2]> = (0..mesh.num_vertices)
|
||||
.map(|i| {
|
||||
let uv = mesh.vertex_uv[i];
|
||||
[uv.x as f32, uv.y as f32]
|
||||
})
|
||||
.collect();
|
||||
bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
|
||||
}
|
||||
|
||||
let mut indices = Vec::new();
|
||||
for &face in mesh.faces.as_ref() {
|
||||
scratch.clear();
|
||||
ufbx::triangulate_face_vec(&mut scratch, mesh, face);
|
||||
for idx in &scratch {
|
||||
let v = mesh.vertex_indices[*idx as usize];
|
||||
indices.push(v);
|
||||
}
|
||||
}
|
||||
bevy_mesh.insert_indices(Indices::U32(indices));
|
||||
|
||||
Ok(bevy_mesh)
|
||||
})?;
|
||||
if !node.element.name.is_empty() {
|
||||
named_meshes.insert(Box::from(node.element.name.as_ref()), handle.clone());
|
||||
}
|
||||
meshes.push(handle);
|
||||
transforms.push(node.geometry_to_world);
|
||||
}
|
||||
|
||||
// Convert materials. Currently these are simple placeholders.
|
||||
let mut materials = Vec::new();
|
||||
let mut named_materials = HashMap::new();
|
||||
for (index, mat) in scene.materials.as_ref().iter().enumerate() {
|
||||
let handle = load_context.add_labeled_asset(
|
||||
FbxAssetLabel::Material(index).to_string(),
|
||||
StandardMaterial::default(),
|
||||
);
|
||||
if !mat.element.name.is_empty() {
|
||||
named_materials.insert(Box::from(mat.element.name.as_ref()), handle.clone());
|
||||
}
|
||||
materials.push(handle);
|
||||
}
|
||||
|
||||
// Build a simple scene with all meshes at the origin.
|
||||
let mut world = World::new();
|
||||
let default_material = materials.get(0).cloned().unwrap_or_else(|| {
|
||||
load_context.add_labeled_asset(
|
||||
FbxAssetLabel::DefaultMaterial.to_string(),
|
||||
StandardMaterial::default(),
|
||||
)
|
||||
});
|
||||
|
||||
for (mesh_handle, matrix) in meshes.iter().zip(transforms.iter()) {
|
||||
let mat = Mat4::from_cols_array(&[
|
||||
matrix.m00 as f32,
|
||||
matrix.m10 as f32,
|
||||
matrix.m20 as f32,
|
||||
0.0,
|
||||
matrix.m01 as f32,
|
||||
matrix.m11 as f32,
|
||||
matrix.m21 as f32,
|
||||
0.0,
|
||||
matrix.m02 as f32,
|
||||
matrix.m12 as f32,
|
||||
matrix.m22 as f32,
|
||||
0.0,
|
||||
matrix.m03 as f32,
|
||||
matrix.m13 as f32,
|
||||
matrix.m23 as f32,
|
||||
1.0,
|
||||
]);
|
||||
let transform = Transform::from_matrix(mat);
|
||||
world.spawn((
|
||||
Mesh3d(mesh_handle.clone()),
|
||||
MeshMaterial3d(default_material.clone()),
|
||||
transform,
|
||||
GlobalTransform::default(),
|
||||
Visibility::default(),
|
||||
));
|
||||
}
|
||||
|
||||
let scene_handle = load_context.add_labeled_asset(FbxAssetLabel::Scene(0).to_string(), Scene::new(world));
|
||||
|
||||
Ok(Fbx {
|
||||
scenes: vec![scene_handle.clone()],
|
||||
meshes,
|
||||
materials,
|
||||
animations: Vec::new(),
|
||||
skeletons: Vec::new(),
|
||||
named_meshes,
|
||||
named_materials,
|
||||
named_animations: HashMap::new(),
|
||||
named_skeletons: HashMap::new(),
|
||||
connections: Vec::new(),
|
||||
axis_system: FbxAxisSystem {
|
||||
up: Vec3::Y,
|
||||
front: Vec3::Z,
|
||||
handedness: Handedness::Right,
|
||||
},
|
||||
unit_scale: 1.0,
|
||||
metadata: FbxMeta {
|
||||
creator: None,
|
||||
creation_time: None,
|
||||
original_application: None,
|
||||
},
|
||||
#[cfg(debug_assertions)]
|
||||
raw_scene_bytes: Some(bytes.into()),
|
||||
})
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&["fbx"]
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin adding the FBX loader to an [`App`].
|
||||
#[derive(Default)]
|
||||
pub struct FbxPlugin;
|
||||
|
||||
impl Plugin for FbxPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_asset::<Fbx>()
|
||||
.register_asset_loader(FbxLoader::default());
|
||||
}
|
||||
}
|
@ -195,6 +195,7 @@ bevy_core_pipeline = ["dep:bevy_core_pipeline", "bevy_image"]
|
||||
bevy_anti_aliasing = ["dep:bevy_anti_aliasing", "bevy_image"]
|
||||
bevy_gizmos = ["dep:bevy_gizmos", "bevy_image"]
|
||||
bevy_gltf = ["dep:bevy_gltf", "bevy_image"]
|
||||
bevy_fbx = ["dep:bevy_fbx", "bevy_image", "bevy_animation"]
|
||||
bevy_ui = ["dep:bevy_ui", "bevy_image"]
|
||||
bevy_ui_render = ["dep:bevy_ui_render"]
|
||||
bevy_image = ["dep:bevy_image"]
|
||||
@ -430,6 +431,7 @@ bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.17.
|
||||
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.17.0-dev" }
|
||||
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.17.0-dev", default-features = false }
|
||||
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.17.0-dev" }
|
||||
bevy_fbx = { path = "../bevy_fbx", optional = true, version = "0.17.0-dev" }
|
||||
bevy_feathers = { path = "../bevy_feathers", optional = true, version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", optional = true, version = "0.17.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", optional = true, version = "0.17.0-dev" }
|
||||
|
@ -54,6 +54,8 @@ plugin_group! {
|
||||
// compressed texture formats.
|
||||
#[cfg(feature = "bevy_gltf")]
|
||||
bevy_gltf:::GltfPlugin,
|
||||
#[cfg(feature = "bevy_fbx")]
|
||||
bevy_fbx:::FbxPlugin,
|
||||
#[cfg(feature = "bevy_audio")]
|
||||
bevy_audio:::AudioPlugin,
|
||||
#[cfg(feature = "bevy_gilrs")]
|
||||
|
@ -45,6 +45,8 @@ pub use bevy_gilrs as gilrs;
|
||||
pub use bevy_gizmos as gizmos;
|
||||
#[cfg(feature = "bevy_gltf")]
|
||||
pub use bevy_gltf as gltf;
|
||||
#[cfg(feature = "bevy_fbx")]
|
||||
pub use bevy_fbx as fbx;
|
||||
#[cfg(feature = "bevy_image")]
|
||||
pub use bevy_image as image;
|
||||
pub use bevy_input as input;
|
||||
|
@ -83,6 +83,10 @@ pub use crate::state::prelude::*;
|
||||
#[cfg(feature = "bevy_gltf")]
|
||||
pub use crate::gltf::prelude::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "bevy_fbx")]
|
||||
pub use crate::fbx::prelude::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "bevy_picking")]
|
||||
pub use crate::picking::prelude::*;
|
||||
|
62
examples/3d/load_fbx.rs
Normal file
62
examples/3d/load_fbx.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use bevy::{
|
||||
pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap},
|
||||
prelude::*,
|
||||
};
|
||||
use std::f32::consts::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.insert_resource(DirectionalLightShadowMap { size: 4096 })
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, animate_light_direction)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
commands.spawn((
|
||||
Camera3d::default(),
|
||||
Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
|
||||
EnvironmentMapLight {
|
||||
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
|
||||
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
|
||||
intensity: 250.0,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
DirectionalLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
// This is a relatively small scene, so use tighter shadow
|
||||
// cascade bounds than the default for better quality.
|
||||
// We also adjusted the shadow map to be larger since we're
|
||||
// only using a single cascade.
|
||||
CascadeShadowConfigBuilder {
|
||||
num_cascades: 1,
|
||||
maximum_distance: 1.6,
|
||||
..default()
|
||||
}
|
||||
.build(),
|
||||
));
|
||||
// FBX_TODO: the cube doesn't show up
|
||||
commands.spawn(SceneRoot(
|
||||
asset_server.load(FbxAssetLabel::Scene(0).from_asset("models/cube/cube.fbx")),
|
||||
));
|
||||
}
|
||||
|
||||
fn animate_light_direction(
|
||||
time: Res<Time>,
|
||||
mut query: Query<&mut Transform, With<DirectionalLight>>,
|
||||
) {
|
||||
for mut transform in &mut query {
|
||||
transform.rotation = Quat::from_euler(
|
||||
EulerRot::ZYX,
|
||||
0.0,
|
||||
time.elapsed_secs() * PI / 5.0,
|
||||
-FRAC_PI_4,
|
||||
);
|
||||
}
|
||||
}
|
@ -167,6 +167,7 @@ Example | Description
|
||||
[Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene
|
||||
[Load glTF extras](../examples/3d/load_gltf_extras.rs) | Loads and renders a glTF file as a scene, including the gltf extras
|
||||
[Manual Material Implementation](../examples/3d/manual_material.rs) | Demonstrates how to implement a material manually using the mid-level render APIs
|
||||
[Load FBX](../examples/3d/load_fbx.rs) | Loads and renders an FBX file as a scene
|
||||
[Mesh Ray Cast](../examples/3d/mesh_ray_cast.rs) | Demonstrates ray casting with the `MeshRayCast` system parameter
|
||||
[Meshlet](../examples/3d/meshlet.rs) | Meshlet rendering for dense high-poly scenes (experimental)
|
||||
[Mixed lighting](../examples/3d/mixed_lighting.rs) | Demonstrates how to combine baked and dynamic lighting
|
||||
|
@ -82,7 +82,7 @@ fn setup(
|
||||
commands.spawn((
|
||||
Mesh3d(cube_handle),
|
||||
MeshMaterial3d(material_handle.clone()),
|
||||
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||
Transform::from_xyz(0.0, 3.0, 0.0),
|
||||
));
|
||||
// sphere
|
||||
commands.spawn((
|
||||
@ -90,6 +90,11 @@ fn setup(
|
||||
MeshMaterial3d(material_handle),
|
||||
Transform::from_xyz(3.0, 0.0, 0.0),
|
||||
));
|
||||
// TODO: don't touch this example
|
||||
commands.spawn(SceneRoot(
|
||||
asset_server.load(FbxAssetLabel::Scene(0).from_asset("models/cube/cube.fbx")),
|
||||
));
|
||||
|
||||
// light
|
||||
commands.spawn((PointLight::default(), Transform::from_xyz(4.0, 5.0, 4.0)));
|
||||
// camera
|
||||
|
Loading…
Reference in New Issue
Block a user