A 3D rendering of a colorful cube floating in a dark space. The cube is semi-transparent, showing its wireframe structure. Each face of the cube is a different vibrant color - red, green, blue, yellow, magenta, and cyan. The cube is slightly rotated to show multiple faces. A soft, directional light source illuminates the cube, creating subtle shadows and highlights. In the background, faint lines of code can be seen, suggesting the programming aspect of the project. The overall image has a sleek, modern, and technical feel, representing the intersection of 3D graphics and Rust programming.

cube3d – A Comprehensive 3D Cube Renderer in Rust

The cube3d project is a sophisticated 3D rendering application written in Rust, utilizing the Druid framework for GUI development. This comprehensive analysis will explore all aspects of the project, from its core structure to the intricate details of its 3D graphics implementation.

Project Structure and State Management

At the heart of the application is the AppState struct, which encapsulates the entire state of the 3D scene:

#[derive(Clone, Data)]
struct AppState {
    angle_x: f64,
    angle_y: f64,
    translation: [f64; 2],
    debug: bool,
    paused: bool,
    wireframe: bool,
    zoom: f64,
}
Rust

This structure maintains the current rotation angles, translation vector, debug mode status, pause state, wireframe rendering mode, and zoom level. The #[derive(Clone, Data)] attribute ensures that the state can be cloned and conforms to Druid’s Data trait for efficient updates.

The CubeWidget

The core of the rendering logic is encapsulated in the CubeWidget struct:

struct CubeWidget {
    frames_since_last_update: usize,
    last_fps_calculation: Instant,
    fps: f64,
    dragging_rotation: bool,
    dragging_translation: bool,
    last_mouse_pos: Point,
}
Rust

This widget handles user interactions, maintains FPS calculations, and manages the rendering loop.

Event Handling and User Interaction

The event method of the Widget trait implementation for CubeWidget processes various events:

fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut AppState, _env: &Env) {
    match event {
        Event::WindowConnected => { /* ... */ }
        Event::Timer(_) => { /* ... */ }
        Event::KeyDown(key_event) => { /* ... */ }
        Event::MouseDown(mouse_event) => { /* ... */ }
        Event::MouseMove(mouse_event) => { /* ... */ }
        Event::MouseUp(mouse_event) => { /* ... */ }
        Event::Wheel(wheel_event) => { /* ... */ }
        _ => {}
    }
}
Rust

This method handles:

  • Window connection and timer events for animation
  • Keyboard inputs for toggling various modes (debug, pause, wireframe)
  • Mouse events for rotation and translation
  • Mouse wheel events for zooming

3D Rendering Pipeline

The paint method implements the core 3D rendering pipeline:

  • Initialization: Sets up the pixel buffer and z-buffer.
  • Vertex Definition: Defines the cube’s vertices and faces in 3D space.
  • Transformation: Applies rotation and translation to the vertices:
let transformed_vertices: Vec<[f64; 3]> = vertices
    .iter()
    .map(|&(x, y, z)| {
        let rotated = multiply_matrix_vector(&rotation_matrix, &[x, y, z]);
        [
            rotated[0] + data.translation[0] / scale,
            rotated[1] + data.translation[1] / scale,
            rotated[2],
        ]
    })
    .collect();
Rust
  • Normal Calculation: Computes vertex normals for lighting calculations:
let mut vertex_normals = vec![[0.0; 3]; vertices.len()];
for &(a, b, c, d) in faces.iter() {
    let normal = calculate_normal(
        &transformed_vertices[a],
        &transformed_vertices[b],
        &transformed_vertices[c],
    );
    // ... normal accumulation and normalization
}
Rust
  • Rasterization: The draw_triangle function implements the rasterization process:
fn draw_triangle(
    v0: &Vertex,
    v1: &Vertex,
    v2: &Vertex,
    pixel_data: &mut [u8],
    z_buffer: &mut [f64],
    width: usize,
    height: usize,
    light_pos_world: &[f64; 3],
    base_color: Color,
) {
    // ... bounding box calculation
    // ... barycentric coordinate interpolation
    // ... depth testing and pixel coloring
}
Rust

This function performs edge walking, barycentric coordinate interpolation, depth testing, and per-pixel lighting calculations.

Lighting Model

The project implements a simple directional lighting model:

fn calculate_light_intensity(
    normal: &[f64; 3],
    position: &[f64; 3],
    light_pos: &[f64; 3],
) -> f64 {
    // ... light direction calculation
    let dot_product =
        normal[0] * light_dir[0] + normal[1] * light_dir[1] + normal[2] * light_dir[2];
    dot_product.max(0.1) // Ensure a minimum ambient light
}
Rust

This function calculates the light intensity based on the dot product between the surface normal and the light direction, ensuring a minimum ambient light level.

Wireframe Rendering

The application supports a wireframe rendering mode:

if data.wireframe {
    // Draw edges
    for &(start, end) in &edges {
        let v0 = &vertices_with_normals[start];
        let v1 = &vertices_with_normals[end];
        draw_line(
            v0.screen_position[0],
            v0.screen_position[1],
            v1.screen_position[0],
            v1.screen_position[1],
            &mut pixel_data,
            width,
            height,
            Color::WHITE,
        );
    }
} else {
    // ... solid face rendering
}
Rust

Debug Information

When debug mode is enabled, the application displays various information on the screen:

if data.debug {
    // Draw program name and version
    // Draw angles, translation, light position, FPS, and zoom level
    // ...
}
Rust

Performance Considerations

The application implements several performance optimizations:

  1. FPS Calculation: Updated every second to avoid unnecessary computations.
  2. Bounding Box Optimization: Rasterization only iterates over the bounding box of each triangle.
  3. Z-Buffer: Ensures correct depth ordering of rendered triangles.
  4. Efficient Matrix Operations: Custom matrix multiplication functions optimized for 3×3 matrices and 3D vectors.

Main Function and Application Launch

The main function sets up the initial application state and launches the Druid application:

pub fn main() -> Result<(), PlatformError> {
    let main_window = WindowDesc::new(CubeWidget::new())
        .title(LocalizedString::new("3D Cube with Per-Pixel Lighting"))
        .window_size((400.0, 400.0));
    let initial_state = AppState {
        angle_x: 0.0,
        angle_y: 0.0,
        translation: [0.0, 0.0],
        debug: false,
        paused: false,
        wireframe: false,
        zoom: 1.0,
    };
    AppLauncher::with_window(main_window).launch(initial_state)?;
    Ok(())
}
Rust

Conclusion

The cube3d project is a comprehensive demonstration of 3D graphics programming in Rust. It showcases key computer graphics concepts such as 3D transformations, rasterization, and lighting calculations, while leveraging Rust’s performance and safety features. The use of the Druid framework for GUI handling allows for a responsive and interactive 3D visualization application.

The project’s structure, with its clear separation of concerns between state management, event handling, and rendering logic, makes it an excellent example of well-organized graphics programming. Its implementation of both solid and wireframe rendering modes, along with debug information display, provides a versatile tool for understanding and experimenting with 3D graphics concepts.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *