#include #include #include #include #include #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 verts; std::vector 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(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(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 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 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 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 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(); } }