6bfde6c6fb
- 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
301 lines
10 KiB
C++
301 lines
10 KiB
C++
#include <cstddef>
|
||
#include <cmath>
|
||
#include <vector>
|
||
#include <span>
|
||
#include <array>
|
||
|
||
#include "glad/glad.h"
|
||
#include "glm/gtc/matrix_transform.hpp"
|
||
|
||
#include "scenes/cornell_box.hpp"
|
||
|
||
namespace cbt::scenes {
|
||
|
||
auto cornell_box::init() -> bool {
|
||
return build_scene_pipeline() && build_light_pipeline() && build_post_pipeline();
|
||
}
|
||
|
||
auto cornell_box::update(float) -> void {}
|
||
|
||
auto cornell_box::on_mouse_drag(double dx, double dy) -> void {
|
||
float const sensitivity = 0.3f;
|
||
m_yaw += float(dx) * sensitivity;
|
||
m_pitch += float(dy) * sensitivity;
|
||
m_pitch = glm::clamp(m_pitch, -80.0f, 80.0f);
|
||
}
|
||
|
||
auto cornell_box::render(int width, int height) -> void {
|
||
m_rt.resize(width, height);
|
||
|
||
glViewport(0, 0, width, height);
|
||
|
||
// Offscreen pass
|
||
m_rt.bind();
|
||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||
|
||
float const aspect = float(width) / float(height);
|
||
float const yaw_rad = glm::radians(m_yaw);
|
||
float const pitch_rad = glm::radians(m_pitch);
|
||
glm::vec3 const eye{
|
||
m_radius * sinf(yaw_rad) * cosf(pitch_rad),
|
||
m_radius * sinf(pitch_rad),
|
||
m_radius * cosf(yaw_rad) * cosf(pitch_rad),
|
||
};
|
||
auto proj = glm::perspective(glm::radians(40.0f), aspect, 0.1f, 20.0f);
|
||
auto view = glm::lookAt(eye, glm::vec3{0.0f}, glm::vec3{0.0f, 1.0f, 0.0f});
|
||
|
||
m_scene_pipeline.bind_vec3("u_light_pos", {0.0f, 0.9f, 0.0f});
|
||
m_scene_pipeline.bind_vec3("u_light_color", {1.0f, 1.0f, 1.0f});
|
||
m_scene_pipeline.draw(glm::mat4{1.0f}, view, proj);
|
||
|
||
m_light_pipeline.draw(glm::mat4{1.0f}, view, proj);
|
||
m_rt.unbind();
|
||
|
||
// Screen pass
|
||
glViewport(0, 0, width, height);
|
||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||
glClear(GL_COLOR_BUFFER_BIT);
|
||
m_post_pipeline.bind_texture("u_texture", m_rt.color_id(), 0);
|
||
m_post_pipeline.draw(glm::mat4{1.0f}, glm::mat4{1.0f}, glm::mat4{1.0f});
|
||
}
|
||
|
||
auto cornell_box::build_scene_pipeline() -> bool {
|
||
char const* vert_src = R"glsl(
|
||
#version 410 core
|
||
layout(location = 0) in vec3 a_pos;
|
||
layout(location = 1) in vec3 a_normal;
|
||
layout(location = 2) in vec3 a_color;
|
||
uniform mat4 u_model;
|
||
uniform mat4 u_view;
|
||
uniform mat4 u_proj;
|
||
out vec3 v_normal;
|
||
out vec3 v_world_pos;
|
||
out vec3 v_color;
|
||
void main() {
|
||
gl_Position = u_proj * u_view * u_model * vec4(a_pos, 1.0);
|
||
v_normal = a_normal;
|
||
v_world_pos = a_pos;
|
||
v_color = a_color;
|
||
}
|
||
)glsl";
|
||
|
||
char const* frag_src = R"glsl(
|
||
#version 410 core
|
||
in vec3 v_normal;
|
||
in vec3 v_world_pos;
|
||
in vec3 v_color;
|
||
uniform vec3 u_light_pos;
|
||
uniform vec3 u_light_color;
|
||
out vec4 frag_color;
|
||
void main() {
|
||
vec3 n = normalize(v_normal);
|
||
vec3 l = normalize(u_light_pos - v_world_pos);
|
||
float diff = max(dot(n, l), 0.0);
|
||
vec3 ambient = 0.12 * v_color;
|
||
vec3 diffuse = diff * 0.88 * v_color * u_light_color;
|
||
frag_color = vec4(ambient + diffuse, 1.0);
|
||
}
|
||
)glsl";
|
||
|
||
struct vertex {
|
||
glm::vec3 position;
|
||
glm::vec3 normal;
|
||
glm::vec3 color;
|
||
};
|
||
|
||
glm::vec3 const white{0.73f, 0.71f, 0.68f};
|
||
glm::vec3 const red{0.65f, 0.05f, 0.05f};
|
||
glm::vec3 const green{0.12f, 0.45f, 0.09f};
|
||
|
||
std::vector<vertex> verts;
|
||
std::vector<std::uint32_t> inds;
|
||
|
||
auto add_quad = [&](glm::vec3 p0, glm::vec3 p1, glm::vec3 p2, glm::vec3 p3,
|
||
glm::vec3 normal, glm::vec3 color) {
|
||
auto base = static_cast<std::uint32_t>(verts.size());
|
||
verts.push_back({p0, normal, color});
|
||
verts.push_back({p1, normal, color});
|
||
verts.push_back({p2, normal, color});
|
||
verts.push_back({p3, normal, color});
|
||
inds.insert(inds.end(), {base, base + 1, base + 2, base, base + 2, base + 3});
|
||
};
|
||
|
||
// Room walls
|
||
add_quad({-1,-1, 1}, { 1,-1, 1}, { 1,-1,-1}, {-1,-1,-1}, { 0, 1, 0}, white); // floor
|
||
add_quad({-1, 1,-1}, { 1, 1,-1}, { 1, 1, 1}, {-1, 1, 1}, { 0,-1, 0}, white); // ceiling
|
||
add_quad({-1,-1,-1}, { 1,-1,-1}, { 1, 1,-1}, {-1, 1,-1}, { 0, 0, 1}, white); // back
|
||
add_quad({-1,-1, 1}, {-1, 1, 1}, {-1, 1,-1}, {-1,-1,-1}, { 1, 0, 0}, red); // left
|
||
add_quad({ 1,-1,-1}, { 1, 1,-1}, { 1, 1, 1}, { 1,-1, 1}, {-1, 0, 0}, green); // right
|
||
|
||
// Boxes: vertices are pre-transformed into world space so the model matrix
|
||
// stays identity and normals need no runtime adjustment.
|
||
auto add_box = [&](glm::vec3 center, glm::vec3 half, float y_deg, glm::vec3 color) {
|
||
float const rad = glm::radians(y_deg);
|
||
float const cy = cosf(rad);
|
||
float const sy = sinf(rad);
|
||
auto ry = [&](glm::vec3 v) -> glm::vec3 {
|
||
return {v.x * cy + v.z * sy, v.y, -v.x * sy + v.z * cy};
|
||
};
|
||
|
||
glm::vec3 c[8] = {
|
||
center + ry({-half.x, -half.y, -half.z}),
|
||
center + ry({ half.x, -half.y, -half.z}),
|
||
center + ry({ half.x, half.y, -half.z}),
|
||
center + ry({-half.x, half.y, -half.z}),
|
||
center + ry({-half.x, -half.y, half.z}),
|
||
center + ry({ half.x, -half.y, half.z}),
|
||
center + ry({ half.x, half.y, half.z}),
|
||
center + ry({-half.x, half.y, half.z}),
|
||
};
|
||
|
||
struct face_def { int vi[4]; glm::vec3 n; };
|
||
face_def const faces[6] = {
|
||
{{4, 5, 6, 7}, { 0, 0, 1}}, // +Z
|
||
{{1, 0, 3, 2}, { 0, 0, -1}}, // -Z
|
||
{{0, 4, 7, 3}, {-1, 0, 0}}, // -X
|
||
{{5, 1, 2, 6}, { 1, 0, 0}}, // +X
|
||
{{7, 6, 2, 3}, { 0, 1, 0}}, // +Y
|
||
{{0, 1, 5, 4}, { 0, -1, 0}}, // -Y
|
||
};
|
||
|
||
for (auto const& f : faces) {
|
||
glm::vec3 const normal = ry(f.n);
|
||
auto const base = static_cast<std::uint32_t>(verts.size());
|
||
for (int i = 0; i < 4; ++i) {
|
||
verts.push_back({c[f.vi[i]], normal, color});
|
||
}
|
||
inds.insert(inds.end(), {base, base + 1, base + 2, base, base + 2, base + 3});
|
||
}
|
||
};
|
||
|
||
// Proportions from the original Cornell Box paper (room = 555 units → [-1, 1]).
|
||
// Tall box: 165×330×165 mm, center at (185, 165, 169), rotated +18°.
|
||
add_box({-0.33f, -0.40f, -0.39f}, {0.30f, 0.60f, 0.30f}, 18.0f, white);
|
||
// Short box: 165×165×165 mm, center at (370, 82.5, 351), rotated -15°.
|
||
add_box({ 0.33f, -0.70f, 0.27f}, {0.30f, 0.30f, 0.30f}, -15.0f, white);
|
||
|
||
gfx::pipeline_desc desc{
|
||
.vertex_data = std::as_bytes(std::span{verts}),
|
||
.index_data = std::as_bytes(std::span{inds}),
|
||
.attributes = {
|
||
{.location = 0, .num_components = 3, .offset = 0},
|
||
{.location = 1, .num_components = 3, .offset = 12},
|
||
{.location = 2, .num_components = 3, .offset = 24},
|
||
},
|
||
.vertex_stride = sizeof(vertex),
|
||
.vertex_shader_src = vert_src,
|
||
.fragment_shader_src = frag_src,
|
||
.depth_test = true,
|
||
.primitive = gfx::primitive_type::triangles,
|
||
.index_type_ = gfx::index_type::uint32
|
||
};
|
||
|
||
m_scene_pipeline = gfx::pipeline{desc};
|
||
return m_scene_pipeline.valid();
|
||
}
|
||
|
||
auto cornell_box::build_light_pipeline() -> bool {
|
||
char const* vert_src = R"glsl(
|
||
#version 410 core
|
||
layout(location = 0) in vec3 a_pos;
|
||
uniform mat4 u_model;
|
||
uniform mat4 u_view;
|
||
uniform mat4 u_proj;
|
||
void main() {
|
||
gl_Position = u_proj * u_view * u_model * vec4(a_pos, 1.0);
|
||
}
|
||
)glsl";
|
||
|
||
char const* frag_src = R"glsl(
|
||
#version 410 core
|
||
out vec4 frag_color;
|
||
void main() {
|
||
frag_color = vec4(1.5, 1.4, 1.2, 1.0);
|
||
}
|
||
)glsl";
|
||
|
||
struct pos_vertex { glm::vec3 position; };
|
||
|
||
// Ceiling light panel inset slightly from y=1 to avoid z-fighting with ceiling.
|
||
std::array<pos_vertex, 4> const light_verts = {{
|
||
{{-0.35f, 0.995f, -0.35f}},
|
||
{{ 0.35f, 0.995f, -0.35f}},
|
||
{{ 0.35f, 0.995f, 0.35f}},
|
||
{{-0.35f, 0.995f, 0.35f}},
|
||
}};
|
||
std::array<std::uint32_t, 6> const light_inds = {0, 1, 2, 0, 2, 3};
|
||
|
||
gfx::pipeline_desc desc{
|
||
.vertex_data = std::as_bytes(std::span{light_verts}),
|
||
.index_data = std::as_bytes(std::span{light_inds}),
|
||
.attributes = {
|
||
{.location = 0, .num_components = 3, .offset = 0},
|
||
},
|
||
.vertex_stride = sizeof(pos_vertex),
|
||
.vertex_shader_src = vert_src,
|
||
.fragment_shader_src = frag_src,
|
||
.depth_test = true,
|
||
.primitive = gfx::primitive_type::triangles,
|
||
.index_type_ = gfx::index_type::uint32
|
||
};
|
||
|
||
m_light_pipeline = gfx::pipeline{desc};
|
||
return m_light_pipeline.valid();
|
||
}
|
||
|
||
auto cornell_box::build_post_pipeline() -> bool {
|
||
char const* post_vert = R"glsl(
|
||
#version 410 core
|
||
layout(location = 0) in vec2 a_pos;
|
||
layout(location = 1) in vec2 a_uv;
|
||
out vec2 v_uv;
|
||
void main() {
|
||
gl_Position = vec4(a_pos, 0.0, 1.0);
|
||
v_uv = a_uv;
|
||
}
|
||
)glsl";
|
||
|
||
char const* post_frag = R"glsl(
|
||
#version 410 core
|
||
in vec2 v_uv;
|
||
uniform sampler2D u_texture;
|
||
out vec4 frag_color;
|
||
void main() {
|
||
vec3 col = texture(u_texture, v_uv).rgb;
|
||
float vig = 1.0 - length(v_uv * 2.0 - 1.0) * 0.35;
|
||
frag_color = vec4(col * vig, 1.0);
|
||
}
|
||
)glsl";
|
||
|
||
struct fs_vertex { glm::vec2 pos; glm::vec2 uv; };
|
||
|
||
std::array<fs_vertex, 4> const qverts = {{
|
||
{{-1.0f, -1.0f}, {0.0f, 0.0f}},
|
||
{{ 1.0f, -1.0f}, {1.0f, 0.0f}},
|
||
{{ 1.0f, 1.0f}, {1.0f, 1.0f}},
|
||
{{-1.0f, 1.0f}, {0.0f, 1.0f}},
|
||
}};
|
||
std::array<std::uint32_t, 6> const qinds = {0, 1, 2, 0, 2, 3};
|
||
|
||
gfx::pipeline_desc desc{
|
||
.vertex_data = std::as_bytes(std::span{qverts}),
|
||
.index_data = std::as_bytes(std::span{qinds}),
|
||
.attributes = {
|
||
{.location = 0, .num_components = 2, .offset = 0},
|
||
{.location = 1, .num_components = 2, .offset = 8},
|
||
},
|
||
.vertex_stride = sizeof(fs_vertex),
|
||
.vertex_shader_src = post_vert,
|
||
.fragment_shader_src = post_frag,
|
||
.depth_test = false,
|
||
.primitive = gfx::primitive_type::triangles,
|
||
.index_type_ = gfx::index_type::uint32
|
||
};
|
||
|
||
m_post_pipeline = gfx::pipeline{desc};
|
||
return m_post_pipeline.valid();
|
||
}
|
||
|
||
}
|