portersky 6bfde6c6fb 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
2026-05-11 16:35:26 +02:00

cuber

cuber is an OpenGL 3D renderer with multiple scenes.

Screenshot of sphere

Requirements

  • CMake 3.21+
  • Ninja
  • C++23 compiler

All dependencies (fmt, GLFW, GLAD, asio, GLM, stb) are fetched automatically via CMake FetchContent.

Development

Configure:

cmake -S . -B build -GNinja

Build:

ninja -C build

Run:

./build/cuber

Usage

--duration <seconds>  Auto-terminate after N seconds (for testing/CI)
--scene <cube|sphere> Select initial scene (default: cube)
--screenshot          Render one frame, save screenshot, and exit
S key                 Take screenshot (saved as screenshot.png)
1/2 key               Switch between cube/sphere scene
Q key                 Quit

Scenes

  • cube — spinning colored cube with per-face colors
  • sphere — cube-to-sphere mapped mesh with per-face colors and diffuse lighting (uses the new pipeline with render-to-texture + post-processing)

Pipeline Abstraction (cbt::gfx)

The project now has a clean, easy-to-use graphics pipeline layer in cbt/gfx.hpp. Think of it like a simple "drawing recipe" that hides all the messy OpenGL details (shaders, buffers, VAOs, uniforms, framebuffers). It's inspired by libraries like Sokol but made for C++ with RAII classes, pipeline_desc structs, and beginner-friendly methods.

Why it exists (for dummies)

  • No more copy-paste GL calls in your scenes.
  • Easy to add effects: Render to a texture (offscreen), then do post-processing (blur, vignette, color grading, bloom, SSAO, etc.).
  • Future-proof: The same code works if we add a Vulkan backend later (just swap the internal impl—no changes to your scene code).
  • Switching scenes works smoothly (no more glitches from leftover GL state like depth test or bound textures).

It abstracts the typical graphics pipeline stages you might see in diagrams (vertex shader, rasterizer, pixel shader, output merger) into one simple object. Post-processing is a second "step" after the main draw.

How to use it (step-by-step for dummies)

  1. Prepare your data (in init() or a build_*() method):

    • Vertex data (positions, normals, colors, UVs).
    • Index data (optional, for triangles).
    • Attribute description (where each piece of data lives in the vertex, e.g. location 0 = position at offset 0).
    • Shader source code (vertex + fragment as raw strings).
  2. Create a pipeline_desc (the recipe):

    gfx::pipeline_desc desc {
        .vertex_data = std::as_bytes(std::span{my_vertices}),
        .index_data = std::as_bytes(std::span{my_indices}),
        .attributes = {
            {.location = 0, .num_components = 3, .offset = 0},   // pos
            {.location = 1, .num_components = 3, .offset = 12},  // normal
            // ... more
        },
        .vertex_stride = sizeof(MyVertex),
        .vertex_shader_src = my_vert_src,
        .fragment_shader_src = my_frag_src,
        .depth_test = true,  // on for 3D, off for 2D post-process
    };
    
  3. Build the pipeline:

    m_pipeline = gfx::pipeline{desc};
    if (!m_pipeline.valid()) { /* error */ }
    
  4. Draw it (in render()):

    m_pipeline.draw(model_matrix, view_matrix, proj_matrix);
    

Render-to-Texture + Post-Processing (the cool part)

Use gfx::render_target for offscreen rendering (like a temporary canvas):

// In class
gfx::render_target m_rt{0, 0};
gfx::pipeline m_post_pipeline;  // second pipeline for effects

// In init()
m_rt.resize(width, height);  // or call in render()
// build your main pipeline + a post pipeline (fullscreen quad +
// sampler2D shader)

In render():

m_rt.resize(width, height);
m_rt.bind();                    // draw to texture instead of screen
// ... clear, draw main scene pipeline ...
m_rt.unbind();

// Post-processing step
m_post_pipeline.bind_texture("u_texture", m_rt.color_id(), 0);
m_post_pipeline.draw(...);      // fullscreen quad that samples the
                                // texture + applies effect

The sphere scene demonstrates this: main 3D pass → render target → post-process (vignette on the colored faces).

For advanced users / extending

  • Add more uniforms/samplers with set_mat4(name, mat) or bind_texture(name, id) (cached locations).
  • To add Vulkan: implement a new impl in gfx.cpp that uses VkPipeline, VkFramebuffer, etc. (the public API stays identical).
  • See scenes/sphere.cpp for a full example (including fullscreen quad for post-processing).

This keeps your scene code tiny and clean while giving you powerful graphics features.

Scenes

  • cube — spinning colored cube with per-face colors
  • sphere — cube-to-sphere mapped mesh with per-face colors and diffuse lighting (uses the new pipeline with render-to-texture + post-processing)
S
Description
No description provided
Readme 425 KiB
Languages
C++ 95.1%
CMake 4.9%