mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Add example to demonstrate manual generation and UV mapping of 3D mesh (generate_custom_mesh) solve #4922 (#8909)
# Objective - Fixes #4922 ## Solution - Add an example that maps a custom texture on a 3D mesh. --- ## Changelog > Added the texture itself (confirmed with mod on discord before it should be ok) to the assets folder, added to the README and Cargo.toml. --------- Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Sélène Amanita <134181069+Selene-Amanita@users.noreply.github.com>
This commit is contained in:
parent
8a1f0a2997
commit
75c6641b41
3 changed files with 279 additions and 0 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -447,6 +447,16 @@ description = "A scene showcasing the built-in 3D shapes"
|
|||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "generate_custom_mesh"
|
||||
path = "examples/3d/generate_custom_mesh.rs"
|
||||
|
||||
[package.metadata.example.generate_custom_mesh]
|
||||
name = "Generate Custom Mesh"
|
||||
description = "Simple showcase of how to generate a custom mesh with a custom texture"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "anti_aliasing"
|
||||
path = "examples/3d/anti_aliasing.rs"
|
||||
|
|
268
examples/3d/generate_custom_mesh.rs
Normal file
268
examples/3d/generate_custom_mesh.rs
Normal file
|
@ -0,0 +1,268 @@
|
|||
// ! This example demonstrates how to create a custom mesh,
|
||||
// ! assign a custom UV mapping for a custom texture,
|
||||
// ! and how to change the UV mapping at run-time.
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::mesh::Indices;
|
||||
use bevy::render::mesh::VertexAttributeValues;
|
||||
use bevy::render::render_resource::PrimitiveTopology;
|
||||
|
||||
// Define a "marker" component to mark the custom mesh. Marker components are often used in Bevy for
|
||||
// filtering entities in queries with With, they're usually not queried directly since they don't contain information within them.
|
||||
#[derive(Component)]
|
||||
struct CustomUV;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, input_handler)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
asset_server: ResMut<AssetServer>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
) {
|
||||
// Import the custom texture.
|
||||
let custom_texture_handle: Handle<Image> = asset_server.load("textures/array_texture.png");
|
||||
// Create and save a handle to the mesh.
|
||||
let cube_mesh_handle: Handle<Mesh> = meshes.add(create_cube_mesh());
|
||||
|
||||
// Render the mesh with the custom texture using a PbrBundle, add the marker.
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: cube_mesh_handle,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color_texture: Some(custom_texture_handle),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
},
|
||||
CustomUV,
|
||||
));
|
||||
|
||||
// Transform for the camera and lighting, looking at (0,0,0) (the position of the mesh).
|
||||
let camera_and_light_transform =
|
||||
Transform::from_xyz(1.8, 1.8, 1.8).looking_at(Vec3::ZERO, Vec3::Y);
|
||||
|
||||
// Camera in 3D space.
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: camera_and_light_transform,
|
||||
..default()
|
||||
});
|
||||
|
||||
// Light up the scene.
|
||||
commands.spawn(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
intensity: 1000.0,
|
||||
range: 100.0,
|
||||
..default()
|
||||
},
|
||||
transform: camera_and_light_transform,
|
||||
..default()
|
||||
});
|
||||
|
||||
// Text to describe the controls.
|
||||
commands.spawn(
|
||||
TextBundle::from_section(
|
||||
"Controls:\nSpace: Change UVs\nX/Y/Z: Rotate\nR: Reset orientation",
|
||||
TextStyle {
|
||||
font_size: 20.0,
|
||||
..default()
|
||||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// System to receive input from the user,
|
||||
// check out examples/input/ for more examples about user input.
|
||||
fn input_handler(
|
||||
keyboard_input: Res<Input<KeyCode>>,
|
||||
mesh_query: Query<&Handle<Mesh>, With<CustomUV>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut query: Query<&mut Transform, With<CustomUV>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if keyboard_input.just_pressed(KeyCode::Space) {
|
||||
let mesh_handle = mesh_query.get_single().expect("Query not successful");
|
||||
let mesh = meshes.get_mut(mesh_handle).unwrap();
|
||||
toggle_texture(mesh);
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::X) {
|
||||
for mut transform in &mut query {
|
||||
transform.rotate_x(time.delta_seconds() / 1.2);
|
||||
}
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::Y) {
|
||||
for mut transform in &mut query {
|
||||
transform.rotate_y(time.delta_seconds() / 1.2);
|
||||
}
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::Z) {
|
||||
for mut transform in &mut query {
|
||||
transform.rotate_z(time.delta_seconds() / 1.2);
|
||||
}
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::R) {
|
||||
for mut transform in &mut query {
|
||||
transform.look_to(Vec3::NEG_Z, Vec3::Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_cube_mesh() -> Mesh {
|
||||
let mut cube_mesh = Mesh::new(PrimitiveTopology::TriangleList);
|
||||
|
||||
#[rustfmt::skip]
|
||||
cube_mesh.insert_attribute(
|
||||
Mesh::ATTRIBUTE_POSITION,
|
||||
// Each array is an [x, y, z] coordinate in local space.
|
||||
// Meshes always rotate around their local [0, 0, 0] when a rotation is applied to their Transform.
|
||||
// By centering our mesh around the origin, rotating the mesh preserves its center of mass.
|
||||
vec![
|
||||
// top (facing towards +y)
|
||||
[-0.5, 0.5, -0.5], // vertex with index 0
|
||||
[0.5, 0.5, -0.5], // vertex with index 1
|
||||
[0.5, 0.5, 0.5], // etc. until 23
|
||||
[-0.5, 0.5, 0.5],
|
||||
// bottom (-y)
|
||||
[-0.5, -0.5, -0.5],
|
||||
[0.5, -0.5, -0.5],
|
||||
[0.5, -0.5, 0.5],
|
||||
[-0.5, -0.5, 0.5],
|
||||
// right (+x)
|
||||
[0.5, -0.5, -0.5],
|
||||
[0.5, -0.5, 0.5],
|
||||
[0.5, 0.5, 0.5], // This vertex is at the same position as vertex with index 2, but they'll have different UV and normal
|
||||
[0.5, 0.5, -0.5],
|
||||
// left (-x)
|
||||
[-0.5, -0.5, -0.5],
|
||||
[-0.5, -0.5, 0.5],
|
||||
[-0.5, 0.5, 0.5],
|
||||
[-0.5, 0.5, -0.5],
|
||||
// back (+z)
|
||||
[-0.5, -0.5, 0.5],
|
||||
[-0.5, 0.5, 0.5],
|
||||
[0.5, 0.5, 0.5],
|
||||
[0.5, -0.5, 0.5],
|
||||
// forward (-z)
|
||||
[-0.5, -0.5, -0.5],
|
||||
[-0.5, 0.5, -0.5],
|
||||
[0.5, 0.5, -0.5],
|
||||
[0.5, -0.5, -0.5],
|
||||
],
|
||||
);
|
||||
|
||||
// Set-up UV coordinated to point to the upper (V < 0.5), "dirt+grass" part of the texture.
|
||||
// Take a look at the custom image (assets/textures/array_texture.png)
|
||||
// so the UV coords will make more sense
|
||||
// Note: (0.0, 0.0) = Top-Left in UV mapping, (1.0, 1.0) = Bottom-Right in UV mapping
|
||||
#[rustfmt::skip]
|
||||
cube_mesh.insert_attribute(
|
||||
Mesh::ATTRIBUTE_UV_0,
|
||||
vec![
|
||||
// Assigning the UV coords for the top side.
|
||||
[0.0, 0.2], [0.0, 0.0], [1.0, 0.0], [1.0, 0.25],
|
||||
// Assigning the UV coords for the bottom side.
|
||||
[0.0, 0.45], [0.0, 0.25], [1.0, 0.25], [1.0, 0.45],
|
||||
// Assigning the UV coords for the right side.
|
||||
[1.0, 0.45], [0.0, 0.45], [0.0, 0.2], [1.0, 0.2],
|
||||
// Assigning the UV coords for the left side.
|
||||
[1.0, 0.45], [0.0, 0.45], [0.0, 0.2], [1.0, 0.2],
|
||||
// Assigning the UV coords for the back side.
|
||||
[0.0, 0.45], [0.0, 0.2], [1.0, 0.2], [1.0, 0.45],
|
||||
// Assigning the UV coords for the forward side.
|
||||
[0.0, 0.45], [0.0, 0.2], [1.0, 0.2], [1.0, 0.45],
|
||||
],
|
||||
);
|
||||
|
||||
// For meshes with flat shading, normals are orthogonal (pointing out) from the direction of
|
||||
// the surface.
|
||||
// Normals are required for correct lighting calculations.
|
||||
// Each array represents a normalized vector, which length should be equal to 1.0.
|
||||
#[rustfmt::skip]
|
||||
cube_mesh.insert_attribute(
|
||||
Mesh::ATTRIBUTE_NORMAL,
|
||||
vec![
|
||||
// Normals for the top side (towards +y)
|
||||
[0.0, 1.0, 0.0],
|
||||
[0.0, 1.0, 0.0],
|
||||
[0.0, 1.0, 0.0],
|
||||
[0.0, 1.0, 0.0],
|
||||
// Normals for the bottom side (towards -y)
|
||||
[0.0, -1.0, 0.0],
|
||||
[0.0, -1.0, 0.0],
|
||||
[0.0, -1.0, 0.0],
|
||||
[0.0, -1.0, 0.0],
|
||||
// Normals for the right side (towards +x)
|
||||
[1.0, 0.0, 0.0],
|
||||
[1.0, 0.0, 0.0],
|
||||
[1.0, 0.0, 0.0],
|
||||
[1.0, 0.0, 0.0],
|
||||
// Normals for the left side (towards -x)
|
||||
[-1.0, 0.0, 0.0],
|
||||
[-1.0, 0.0, 0.0],
|
||||
[-1.0, 0.0, 0.0],
|
||||
[-1.0, 0.0, 0.0],
|
||||
// Normals for the back side (towards +z)
|
||||
[0.0, 0.0, 1.0],
|
||||
[0.0, 0.0, 1.0],
|
||||
[0.0, 0.0, 1.0],
|
||||
[0.0, 0.0, 1.0],
|
||||
// Normals for the forward side (towards -z)
|
||||
[0.0, 0.0, -1.0],
|
||||
[0.0, 0.0, -1.0],
|
||||
[0.0, 0.0, -1.0],
|
||||
[0.0, 0.0, -1.0],
|
||||
],
|
||||
);
|
||||
|
||||
// Create the triangles out of the 24 vertices we created.
|
||||
// To construct a square, we need 2 triangles, therefore 12 triangles in total.
|
||||
// To construct a triangle, we need the indices of its 3 defined vertices, adding them one
|
||||
// by one, in a counter-clockwise order (relative to the position of the viewer, the order
|
||||
// should appear counter-clockwise from the front of the triangle, in this case from outside the cube).
|
||||
// Read more about how to correctly build a mesh manually in the Bevy documentation of a Mesh,
|
||||
// further examples and the implementation of the built-in shapes.
|
||||
#[rustfmt::skip]
|
||||
cube_mesh.set_indices(Some(Indices::U32(vec![
|
||||
0,3,1 , 1,3,2, // triangles making up the top (+y) facing side.
|
||||
4,5,7 , 5,6,7, // bottom (-y)
|
||||
8,11,9 , 9,11,10, // right (+x)
|
||||
12,13,15 , 13,14,15, // left (-x)
|
||||
16,19,17 , 17,19,18, // back (+z)
|
||||
20,21,23 , 21,22,23, // forward (-z)
|
||||
])));
|
||||
|
||||
cube_mesh
|
||||
}
|
||||
|
||||
// Function that changes the UV mapping of the mesh, to apply the other texture.
|
||||
fn toggle_texture(mesh_to_change: &mut Mesh) {
|
||||
// Get a mutable reference to the values of the UV attribute, so we can iterate over it.
|
||||
let uv_attribute = mesh_to_change.attribute_mut(Mesh::ATTRIBUTE_UV_0).unwrap();
|
||||
// The format of the UV coordinates should be Float32x2.
|
||||
let VertexAttributeValues::Float32x2(uv_attribute) = uv_attribute else {
|
||||
panic!("Unexpected vertex format, expected Float32x2.");
|
||||
};
|
||||
|
||||
// Iterate over the UV coordinates, and change them as we want.
|
||||
for uv_coord in uv_attribute.iter_mut() {
|
||||
// If the UV coordinate points to the upper, "dirt+grass" part of the texture...
|
||||
if (uv_coord[1] + 0.5) < 1.0 {
|
||||
// ... point to the equivalent lower, "sand+water" part instead,
|
||||
uv_coord[1] += 0.5;
|
||||
} else {
|
||||
// else, point back to the upper, "dirt+grass" part.
|
||||
uv_coord[1] -= 0.5;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -120,6 +120,7 @@ Example | Description
|
|||
[Atmospheric Fog](../examples/3d/atmospheric_fog.rs) | A scene showcasing the atmospheric fog effect
|
||||
[Blend Modes](../examples/3d/blend_modes.rs) | Showcases different blend modes
|
||||
[Fog](../examples/3d/fog.rs) | A scene showcasing the distance fog effect
|
||||
[Generate Custom Mesh](../examples/3d/generate_custom_mesh.rs) | Simple showcase of how to generate a custom mesh with a custom texture
|
||||
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene
|
||||
[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines
|
||||
[Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene
|
||||
|
|
Loading…
Reference in a new issue