e5f3636b42
- meshes/cube: procedural checkerboard -> uv_texture.jpg (8x8 UV grid) - meshes/pbr: floor -> ground.jpeg, bump cube -> cave.jpg + caveNormal.jpg (normal map pre-encoded via sRGB OETF to cancel the GPU sRGB decode) - lights/shadow: ground -> ground.jpeg, cube -> uv_texture.jpg - effects/demo: ground -> ground.jpeg, cube -> uv_texture.jpg - effects/fog: ground -> ground.jpeg (tiled 80x80), cubes -> stonewall.jpg - effects/dof: ground -> ground.jpeg, cubes -> uv_texture.jpg - cameras/culling: shared cube mesh -> uv_texture.jpg - add lib/examples/assets/textures/ (19 assets, 6.5 MB) - document assets + usage in examples READMEs, docs/user/examples.md, docs/user/meshes/materials.md (CARGO_MANIFEST_DIR pattern, sRGB caveat)
108 lines
4.8 KiB
Rust
108 lines
4.8 KiB
Rust
//! A lit unit cube that rotates, **textured** with the `uv_texture.jpg` asset (an 8×8 UV atlas
|
||
//! visualization — each labelled cell shows exactly where a face's UVs land) via the diffuse path
|
||
//! (bind group `@group(2)`).
|
||
//!
|
||
//! A 3D mesh with Phong lighting on screen — the library's 3D showcase.
|
||
//! Follows the declarative workflow (like `simple`): `AppBuilder` + automatic scene, **no wgpu import**.
|
||
//! The scene owns its `PipelineCache`: go through `register_shader` +
|
||
//! `add_material_shader`/`add_material_texture` + `create_mesh` + `add_entity`. The mesh
|
||
//! is declared from a **`Geometry`** (positions, normals, indices). A texture is
|
||
//! registered by id (`add_texture`) and a textured material bound to it (`add_material_texture`);
|
||
//! here the texture is a **file asset** (`assets/textures/uv_texture.jpg`, loaded via
|
||
//! `Texture::from_file`) — the path is resolved against `CARGO_MANIFEST_DIR` so the example
|
||
//! works from any working directory.
|
||
//! The default active camera (`Scene::default`, position (0,0,3), fov 45°) frames the cube, and
|
||
//! `AppHandler::update` rotates the entity via `set_entity_transform` each frame.
|
||
use glam::{Quat, Vec3};
|
||
use wsg_lib::AppHandler;
|
||
use wsg_lib::app::AppBuilder;
|
||
use wsg_lib::mesh::cube;
|
||
use wsg_lib::resources::Texture;
|
||
use wsg_lib::utils::WsgError;
|
||
|
||
/// Texture asset directory, resolved against the crate root so the example works from any CWD.
|
||
const TEXTURES: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/assets/textures");
|
||
|
||
/// Demo handler: rotates the textured cube in `update`.
|
||
struct Cube {
|
||
/// Cumulative rotation angle (radians), incremented each frame.
|
||
angle: f32,
|
||
}
|
||
|
||
impl AppHandler for Cube {
|
||
fn setup(&mut self, app: &mut wsg_lib::App) {
|
||
// Phong shader `standard` (carries the frame + object + texture bind groups).
|
||
app.scene
|
||
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
|
||
.unwrap();
|
||
|
||
// Loads the `uv_texture.jpg` asset with the Context's device/queue (via `app.context()`), then
|
||
// registers it in the scene by id; a textured material is then bound to that id.
|
||
let (device, queue) = {
|
||
let ctx = app.context();
|
||
(ctx.device.clone(), ctx.queue.clone())
|
||
};
|
||
let texture = Texture::from_file(
|
||
&device,
|
||
&queue,
|
||
"uv_atlas",
|
||
&format!("{TEXTURES}/uv_texture.jpg"),
|
||
)
|
||
.unwrap();
|
||
app.scene.add_texture("uv_texture", texture).unwrap();
|
||
app.scene
|
||
.add_material_texture("cube_material", "standard", "uv_texture")
|
||
.unwrap();
|
||
|
||
app.scene
|
||
.create_mesh("cube_mesh", cube(1.0), Some("cube_material"))
|
||
.unwrap();
|
||
app.scene.add_entity("cube", "cube_mesh").unwrap();
|
||
|
||
// In addition to the default directional light (+Z), a warm **point** light
|
||
// is added in front of the cube. Its halo (linear attenuation over the
|
||
// radius) is visible on the near face of the cube, on top of the directional lighting.
|
||
app.scene
|
||
.add_point_light(
|
||
Vec3::new(1.0, 0.5, 1.5), // world position, in front/right of the cube
|
||
[1.0, 0.7, 0.3], // warm tint
|
||
1.0, // intensity
|
||
3.0, // attenuation radius
|
||
)
|
||
.unwrap();
|
||
|
||
// A green **spot** light aimed at the cube from the left.
|
||
// The cone (half-angle ~20°) projects a directed beam onto the cube's faces, with a
|
||
// smoothed penumbra at the edge and linear attenuation over the radius.
|
||
app.scene
|
||
.add_spot_light(
|
||
Vec3::new(-2.0, 1.0, 1.5), // world position, left/above/behind the camera
|
||
Vec3::new(2.0, -1.0, -1.5).normalize(), // cone axis, toward the cube (origin)
|
||
[0.3, 1.0, 0.4], // green tint
|
||
1.2, // intensity
|
||
4.0, // attenuation radius
|
||
0.35, // half-angle (~20°) in radians
|
||
)
|
||
.unwrap();
|
||
}
|
||
|
||
fn update(&mut self, app: &mut wsg_lib::App) {
|
||
// Cumulative cube rotation (double axis for a more readable motion).
|
||
self.angle += 0.02;
|
||
let base = *app
|
||
.scene
|
||
.entity_transform("cube")
|
||
.expect("cube entity present");
|
||
let mut transform = base;
|
||
transform.rotation =
|
||
Quat::from_rotation_y(self.angle) * Quat::from_rotation_x(self.angle * 0.3);
|
||
app.scene.set_entity_transform("cube", transform);
|
||
}
|
||
}
|
||
|
||
#[pollster::main]
|
||
async fn main() -> Result<(), WsgError> {
|
||
let app = AppBuilder::new().title("WSG Cube").build().await?;
|
||
app.run(Cube { angle: 0.0 })
|
||
}
|