Rendering Pipeline, Depth, Camera, Light, Transform, and bevy_ecs
2024-11-14
Auto translated into English by AI.

Wgpu PBR is a practice project aimed at building a PBR renderer with wgpu and Rust. I may implement various other graphics effects in the future.
It currently uses Egui for the UI and the bevy_ecs library to manage logic.
The code shown here is from an early development version and will most likely be refactored in the future.
Mesh
Loaded models have the following containment hierarchy:
Model → Mesh → Primitive.
Although a Primitive is the smallest unit of a model, it can't be rendered on its own, only when a Mesh is rendered. A Primitive is also the unit that carries a material.
Model data structure
-
Model
-
UploadedMesh (Vec<_>)
-
Vertex buffer
-
Index buffer
-
UploadedPrimitive (Vec<_>)
-
indices_start
-
indices_num
-
material_instance
-
-
-
Currently, loading models in glb format is supported.
Material
The Material programming pattern is essentially about organizing and managing Pipeline and BindGroup (while also storing the associated Layout). The project's material BindGroupLayout currently looks like this:
-
Transform
-
Camera
-
Light
-
[Material‘s]
-
Texture
-
The Material pattern in this project is currently implemented using two structs: Material and Material Instance.
A Material provides the Pipeline and shared BindGroup values (such as Camera and Light), and defines and stores the associated Layout. An Instance provides the BindGroup values that vary between instances (such as Texture).
-
The Transform
BindGroupis provided byMeshRenderer.
Texture
See https://sotrh.github.io/learn-wgpu/beginner/tutorial5-textures/
pub struct UploadedImage {
pub size: wgpu::Extent3d,
pub texture: Texture,
pub view: TextureView,
pub sampler: Sampler,
}
Implementing Camera
I'm using cgmath as the math library. Camera's View and Projection matrices can be calculated quickly with cgmath.
impl Camera{
...
pub fn build_view_projection_matrix(&self) -> Matrix4<f32> {
let view = Matrix4::look_at_rh(self.eye, self.target, self.up);
let proj = perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar);
return OPENGL_TO_WGPU_MATRIX * proj * view;
}
}
#[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.5, 0.5,
0.0, 0.0, 0.0, 1.0,
);
OpenGL's normalized device coordinate (NDC) depth range (z) is [-1, 1], while the normalized device coordinate depth range in wpgu/Vulkan/DX/Mental is [0, 1]. The projection matrix generated by cgmath follows the OpenGL specification, so a conversion matrix is needed to map depth to [0, 1].
RenderCamera
The camera-related Buffer, BindGroupLayout, and BindGroup are stored and managed in RenderCamera, a Bevy Resource. They are created together when RenderCamera is initialized.
#[derive(Resource)]
pub struct RenderCamera {
pub camera: Camera,
pub buffer: Arc<wgpu::Buffer>,
pub bind_group_layout: Arc<BindGroupLayout>,
pub bind_group: Arc<BindGroup>,
}
impl RenderCamera {
pub fn new(device: &wgpu::Device, aspect: f32) -> RenderCamera {
let camera = Camera::new(aspect);
let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
...
});
let camera_bind_group_layout =
Arc::new(device.create_bind_group_layout(&CameraUniform::layout_desc()));
let camera_bind_group = Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
...
}));
RenderCamera {
camera,
buffer: Arc::new(camera_buffer),
bind_group_layout: camera_bind_group_layout,
bind_group: camera_bind_group,
}
}
}
Implementing Transform
This implementation is only for convenience and isn't worth using as a reference. For a reference, see Bevy's Transform logic implementation.
The logical Transform looks like this:
#[derive(Component)]
pub struct Transform {
pub parent: Option<Entity>,
pub children: Vec<Entity>,
pub position: Point3<f32>,
pub rotation: Quaternion<f32>,
pub scale: Vector3<f32>,
}
For rendering, Transform is currently implemented using a uniform. The Uniform Buffer passed to the GPU contains a model matrix (to transform vertex positions) and the model's rotation matrix (to transform vertex normals).
Data size alignment
Data passed to the GPU needs size alignment according to standard layout rules (std140 or std430). In wgsl, its sizes are treated as follows; cases where the type size differs from Rust are marked in brown:
| Data type <f32> | f32 | vec2 | vec3 | vec4 | mat3x3 | mat4x4 |
| Size / bytes | 4 | 8 | 16 | 16 | 48 (3 * vec3) | 64 (4 * vec3) |
Also, the size passed to the Buffer must be a multiple of 16. So padding variables are declared to align the data.
pub struct TransformUniform {
pub model: [[f32; 4]; 4],
pub rotation: [[f32; 3]; 3],
pub padding: [f32; 3],
}
Encapsulation
Each model with a Transform that is to be rendered has its own Transform Buffer and BindGroup, so they are encapsulated together in a component called MeshRenderer.
#[derive(Component, Default)]
pub struct MeshRenderer {
pub mesh: Option<Arc<UploadedMesh>>,
pub transform_bind_group: Option<Arc<BindGroup>>,
pub transform_buffer: Option<Arc<Buffer>>,
}
Implementing a directional light
Similar to Camera, this is managed by a RenderLight. It currently contains only one directional light.
The LightUniform structure passed to the GPU is shown below. Whether intensity needs to be passed still needs investigation.
#[repr(C, align(16))]
#[derive(Debug, Clone, Copy)]
pub struct LightUniform {
pub direction: [f32; 3],
pub padding1: f32,
pub color: [f32; 4],
pub intensity: f32,
pub padding2: [f32; 3],
}
Engine Lifetime
The game currently uses a temporary lifecycle.
input()
handle_redraw()
pre_update() // 更新 Time
update() //游戏逻辑
post_update() // 更新 Unifoms to GPU
render() // 渲染
Other notes
-
The camera's depth map is written to depth_texture for future use.
-
A simple Input records input.
-
A simple Time records delta time.
Related links
【Wgpu introductory material】https://sotrh.github.io/learn-wgpu/
【Vulkan introductory material】https://vulkan-tutorial.com/
【Learning Vulkan by building a game engine】https://vkguide.dev/
【PBR theory】https://learnopengl.com/PBR/Theory
【Bevy Engine】https://bevyengine.org/
【Egui】https://www.egui.rs/
【Model used】https://sketchfab.com/3d-models/mkr-lykken-91b274a40ebe46cd931235ac32ae0492