Summary

In my approach to the scene graph assignment, I structured the code by creating a hierarchical system where each node represents an object in the scene, and each node contains a mesh and transformation data such as position, scale, and rotation. The camera is attached to one of these nodes, and its transformations are calculated accordingly. I implemented frustum culling to optimize rendering, rejecting meshes whose bounding boxes fall outside the camera’s frustum. What’s cool about this approach is the efficient handling of duplicated objects by storing only one instance of a mesh and reusing its transformation matrices. However, I faced issues with headless mode and a memory overload error, which were resolved by modifying the mesh storage strategy. Some parts, like improving the animation system and handling certain edge cases in frustum culling, were left incomplete.

My Animation

Describe your animation and include a screen recording showing it running in real-time:

I downloaded a free cannon model cannon.fbx from polyhaven.com and created a cannon fire animation using Blender.

Using the Scene Viewer

Provide a short overview of how to use your viewer.

Command-line Arguments

  • –scene scene.s72 (required) – load scene from scene.s72
  • –camera name (optional) – view the scene through the camera name.
  • –physical-device name (optional) – use the physical device matching name
  • –drawing-size w h (optional) – set the initial size of the drawable part of the window in physical pixels.
  • –culling none/frustum/ (optional) – sets the culling mode.
  • –headless events (optional) – if specified, run in headless mode The flag –drawing-size is required when using headless mode

Controls

  • Number key 1 – Switch to/among the scene camera(s).
  • Number key 2 – Switch to the user camera.
  • W/A/S/D – Moves the camera in user camera mode.
  • Mouse – Rotates the camera view in user camera mode.
  • Number key 3 – Switch to the debug/culling camera.
  • Spacebar – Play/Pause the animation.

My Code

Loading Scenes, Mesh Data

In Scene.hpp, define global variable struct S72_scene s72_scene;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct S72_scene {
struct Scene scene;
float animation_duration = 0.f;
std::unordered_map<std::string, Node *> nodes_map;
std::unordered_map<std::string, std::vector<Node *>> cameras_path;
std::unordered_map<Node *, glm::mat4> transforms;
std::unordered_map<Mesh *, MeshVertices> mesh_vertices_map;
std::unordered_map<Mesh *, BBox> mesh_bbox_map;
std::vector<Node> nodes;
std::vector<Mesh> meshes;
std::vector<Camera> cameras;
std::vector<Driver> drivers;
Camera_Mode camera_mode = SCENE;
Camera *current_camera_;
};

struct Mesh {
std::string name;
std::string topology;
uint32_t count;

struct {
std::string src;
uint32_t offset;
std::string format;
} Indices;

struct Attribute {
std::string src;
uint32_t offset;
uint32_t stride;
std::string format;
};

std::map<std::string, Attribute> attributes;
std::string material;
};

Drawing the Scene

The camera is attached to a node, and by continuously updating the node’s position, scale, and rotation, I can compute the camera’s transformations accordingly.

Get transformation, vertex, and texture data to shaders in Tutorial.cpp:

1
2
3
4
5
6
// Update camera
if (playmode.camera_mode == USER) {
move_camera(dt, camera_node_);
}
auto mat_perspective = mat4_perspective(vfov, aspect, near, far);
CLIP_FROM_WORLD_SCENE = mat_perspective * glm::mat4(camera_node_->make_world_to_local());

Handling Interactive Camera and Debug Camera Movement

  • W/A/S/D – Moves the camera in debug camera mode.
  • Mouse – Rotates the camera view in debug camera mode.

Frustum Culling

The idea is to reject drawing a mesh if its bounding box (bbox) is outside of a plane, which is extracted from the camera frustum matrix.

Unresolved bug: In the scene sg-Articulation.s72, the lower plane is unintentionally culled.

Animating the Scene

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
time = std::fmod(playmode.time + dt, s72_scene.animation_duration);

if (playmode.animation_mode == PLAY && !s72_scene.drivers.empty()) {
playmode.time += dt;

if (playmode.time > s72_scene.animation_duration) {
playmode.time -= s72_scene.animation_duration;
}

for (auto &driver : s72_scene.drivers) {
std::string node_name = driver.refnode_name;
Node *node_ = s72_scene.nodes_map[node_name];
driver.make_animation(playmode.time);
s72_scene.transforms[node_] = node_->make_local_to_world();
node_->child_forward_kinematics_transforms(node_);
}
}