feat: add Cornell Box scene with orbit camera

- gfx::pipeline gains bind_vec3/bind_float for passing arbitrary
  uniforms to shaders (used for light position/color)
- scene base gains on_mouse_drag virtual hook; cuber.cpp polls
  left-mouse delta each frame and forwards it to the active scene
- cornell_box scene: 5-wall room (red/green/white), two pre-rotated
  white boxes with proportions from the original paper, an unlit
  ceiling light panel, and a point-light Lambert shader
- left-click drag orbits the camera around the scene origin;
  pitch is clamped to ±80° to prevent gimbal flip
- key 3 / --scene cornell_box selects the scene
This commit is contained in:
2026-05-11 16:34:13 +02:00
parent 5b4743ff8f
commit 6bfde6c6fb
10 changed files with 411 additions and 7 deletions
+27 -6
View File
@@ -14,10 +14,11 @@
#include "cbt/opengl/context.hpp"
#include "scenes/cube.hpp"
#include "scenes/sphere.hpp"
#include "scenes/cornell_box.hpp"
auto main(int argc, char const* argv[]) -> int {
float max_duration_seconds = 0.0f;
std::string scene_name = "cube"; // "cube", "sphere", or "2" (for compatibility with keys)
std::string scene_name = "cube"; // "cube", "sphere", "cornell_box", or "1"/"2"/"3"
bool take_screenshot = false;
for (int i = 1; i < argc; ++i) {
@@ -26,11 +27,11 @@ auto main(int argc, char const* argv[]) -> int {
fmt::print("Usage: {} [options]\n", argv[0]);
fmt::print("Flags:\n");
fmt::print(" --duration <seconds> Auto-terminate after N seconds (for testing/CI)\n");
fmt::print(" --scene <cube|sphere> Select initial scene (default: cube)\n");
fmt::print(" --scene <cube|sphere|cornell_box> Select initial scene (default: cube)\n");
fmt::print(" --screenshot Render one frame, save screenshot, and exit\n");
fmt::print("\nKeys (during runtime):\n");
fmt::print(" S Take screenshot (saved as screenshot.png)\n");
fmt::print(" 1/2 Switch between cube/sphere scene\n");
fmt::print(" 1/2/3 Switch between cube/sphere/cornell_box scene\n");
fmt::print(" Q Quit\n");
return 0;
}
@@ -65,15 +66,18 @@ auto main(int argc, char const* argv[]) -> int {
ctx.set_size(width, height);
});
auto cube_scn = cbt::scenes::cube();
auto sphere_scn = cbt::scenes::sphere();
auto cube_scn = cbt::scenes::cube();
auto sphere_scn = cbt::scenes::sphere();
auto cornell_box_scn = cbt::scenes::cornell_box();
cbt::scene* active_scene = nullptr;
if (!cube_scn.init() || !sphere_scn.init()) {
if (!cube_scn.init() || !sphere_scn.init() || !cornell_box_scn.init()) {
return 1;
}
if (scene_name == "sphere" || scene_name == "2") {
active_scene = &sphere_scn;
} else if (scene_name == "cornell_box" || scene_name == "3") {
active_scene = &cornell_box_scn;
} else {
active_scene = &cube_scn;
}
@@ -103,6 +107,10 @@ auto main(int argc, char const* argv[]) -> int {
};
// render loop
double mouse_x = 0.0, mouse_y = 0.0;
glfwGetCursorPos(win.raw(), &mouse_x, &mouse_y);
bool mouse_was_down = false;
auto prev = std::chrono::steady_clock::now();
while (!win.should_close()) {
@@ -120,6 +128,19 @@ auto main(int argc, char const* argv[]) -> int {
if (glfwGetKey(win.raw(), GLFW_KEY_2) == GLFW_PRESS) {
active_scene = &sphere_scn;
}
if (glfwGetKey(win.raw(), GLFW_KEY_3) == GLFW_PRESS) {
active_scene = &cornell_box_scn;
}
double cur_x = 0.0, cur_y = 0.0;
glfwGetCursorPos(win.raw(), &cur_x, &cur_y);
bool const mouse_down = glfwGetMouseButton(win.raw(), GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;
if (mouse_down && mouse_was_down) {
active_scene->on_mouse_drag(cur_x - mouse_x, cur_y - mouse_y);
}
mouse_x = cur_x;
mouse_y = cur_y;
mouse_was_down = mouse_down;
auto now = std::chrono::steady_clock::now();
auto dt = std::chrono::duration<float>(now - prev).count();