5b4743ff8f
- Added comprehensive "Pipeline Abstraction" section to README.md (for dummies, with step-by-step examples, render-to-texture, and post-processing). - Added "Documentation (Markdown)" section to AGENTS.md with the max-80-cols rule (exceptions for tables/code blocks). - Applied consistent Markdown styling to AGENTS.md and README.md (wrapped text at 80 cols, bold, code spans, cleaned duplicates). - Moved scenes libraries to (cleaned main CMakeLists.txt significantly). No behavior change. All follows AGENTS.md conventions.
160 lines
4.8 KiB
Markdown
160 lines
4.8 KiB
Markdown
# cuber
|
|
|
|
`cuber` is an OpenGL 3D renderer with multiple scenes.
|
|
|
|

|
|
|
|
## Requirements
|
|
|
|
- CMake 3.21+
|
|
- Ninja
|
|
- C++23 compiler
|
|
|
|
All dependencies (fmt, GLFW, GLAD, asio, GLM, stb) are fetched
|
|
automatically via CMake FetchContent.
|
|
|
|
## Development
|
|
|
|
**Configure**:
|
|
|
|
```sh
|
|
cmake -S . -B build -GNinja
|
|
```
|
|
|
|
**Build**:
|
|
|
|
```sh
|
|
ninja -C build
|
|
```
|
|
|
|
**Run**:
|
|
|
|
```sh
|
|
./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):
|
|
```cpp
|
|
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**:
|
|
```cpp
|
|
m_pipeline = gfx::pipeline{desc};
|
|
if (!m_pipeline.valid()) { /* error */ }
|
|
```
|
|
|
|
4. **Draw it** (in `render()`):
|
|
```cpp
|
|
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):
|
|
|
|
```cpp
|
|
// 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()`:
|
|
```cpp
|
|
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)
|