diff --git a/CMakeLists.txt b/CMakeLists.txt index a1ab0a0..152031e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,12 +28,13 @@ add_library(cbt_opengl STATIC "cbt/opengl/descriptor.cpp" "cbt/opengl/shader.cpp" "cbt/opengl/vao.cpp" + "cbt/gfx.cpp" ) target_include_directories(cbt_opengl PRIVATE ".") target_compile_features(cbt_opengl PRIVATE cxx_std_23) target_compile_options(cbt_opengl PRIVATE ${BASE_OPTIONS}) target_compile_definitions(cbt_opengl PRIVATE ${BASE_DEFINITIONS}) -target_link_libraries(cbt_opengl PUBLIC fmt::fmt glfw::glfw glad::glad stb::stb) +target_link_libraries(cbt_opengl PUBLIC fmt::fmt glfw::glfw glad::glad stb::stb glm::glm) # Scene base library add_library(cbt_scene STATIC diff --git a/cbt/gfx.cpp b/cbt/gfx.cpp new file mode 100644 index 0000000..19064b7 --- /dev/null +++ b/cbt/gfx.cpp @@ -0,0 +1,313 @@ +#include +#include + +#include "cbt/gfx.hpp" + +#include "glad/glad.h" +#include "glm/gtc/type_ptr.hpp" + +#include "cbt/opengl/shader.hpp" +#include "cbt/opengl/buffer.hpp" +#include "cbt/opengl/vao.hpp" +#include "cbt/opengl/texture.hpp" + +namespace cbt::gfx { + +struct pipeline::impl { + opengl::shader m_prog; + opengl::buffer m_vbo; + opengl::buffer m_ebo{opengl::buffer_type::index}; + opengl::vao m_vao; + + GLint m_loc_proj = -1; + GLint m_loc_view = -1; + GLint m_loc_model = -1; + + GLsizei m_index_count = 0; + GLsizei m_vertex_count = 0; + + bool m_valid = false; + bool m_uses_index = false; + bool m_depth_test = true; + GLenum m_draw_mode = GL_TRIANGLES; + GLenum m_index_gl_type = GL_UNSIGNED_INT; + + auto build(pipeline_desc const& desc) -> bool; + auto bind_texture(char const* sampler_name, std::uint32_t texture_id, std::uint32_t unit) const -> void; +}; + +auto pipeline::impl::build(pipeline_desc const& desc) -> bool { + if (!desc.vertex_shader_src || !desc.fragment_shader_src || + desc.vertex_data.empty() || desc.attributes.empty() || desc.vertex_stride == 0) { + return false; + } + + if (!m_prog.compile_vertex(desc.vertex_shader_src) || + !m_prog.compile_fragment(desc.fragment_shader_src) || + !m_prog.link()) { + return false; + } + + m_loc_proj = glGetUniformLocation(m_prog.id(), "u_proj"); + m_loc_view = glGetUniformLocation(m_prog.id(), "u_view"); + m_loc_model = glGetUniformLocation(m_prog.id(), "u_model"); + + m_vbo.upload(desc.vertex_data.data(), desc.vertex_data.size()); + m_vertex_count = static_cast(desc.vertex_data.size() / desc.vertex_stride); + + m_uses_index = !desc.index_data.empty(); + if (m_uses_index) { + m_ebo.upload(desc.index_data.data(), desc.index_data.size()); + std::size_t const idx_size = (desc.index_type_ == index_type::uint16) + ? sizeof(std::uint16_t) + : sizeof(std::uint32_t); + m_index_count = static_cast(desc.index_data.size() / idx_size); + m_index_gl_type = (desc.index_type_ == index_type::uint16) + ? GL_UNSIGNED_SHORT + : GL_UNSIGNED_INT; + } + + m_draw_mode = GL_TRIANGLES; + m_depth_test = desc.depth_test; + + m_vao.bind(); + m_vbo.bind(); + if (m_uses_index) { + m_ebo.bind(); + } + + for (auto const& attr : desc.attributes) { + glEnableVertexAttribArray(attr.location); + glVertexAttribPointer( + attr.location, + static_cast(attr.num_components), + GL_FLOAT, + GL_FALSE, + static_cast(desc.vertex_stride), + reinterpret_cast(static_cast(attr.offset)) + ); + } + + m_vao.unbind(); + m_vbo.unbind(); + if (m_uses_index) { + m_ebo.unbind(); + } + + if (desc.depth_test) { + glEnable(GL_DEPTH_TEST); + } else { + glDisable(GL_DEPTH_TEST); + } + + m_valid = true; + return true; +} + + +auto pipeline::impl::bind_texture(char const* sampler_name, std::uint32_t texture_id, std::uint32_t unit) const -> void { + if (!m_prog.valid()) { + return; + } + GLint loc = glGetUniformLocation(m_prog.id(), sampler_name); + if (loc != -1) { + glUniform1i(loc, static_cast(unit)); + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(GL_TEXTURE_2D, texture_id); + } +} + +pipeline::pipeline() : m_impl(std::make_unique()) {} + +pipeline::pipeline(pipeline_desc const& desc) : m_impl(std::make_unique()) { + m_impl->build(desc); +} + +pipeline::pipeline(pipeline&& other) noexcept + : m_impl(std::move(other.m_impl)) {} + +auto pipeline::operator=(pipeline&& other) noexcept -> pipeline& { + if (this != &other) { + m_impl = std::move(other.m_impl); + } + return *this; +} + +pipeline::~pipeline() = default; + +auto pipeline::valid() const -> bool { + return m_impl && m_impl->m_valid; +} + +auto pipeline::draw( + glm::mat4 const& model, + glm::mat4 const& view, + glm::mat4 const& proj +) const -> void { + if (!valid()) { + return; + } + + auto& impl = *m_impl; + impl.m_prog.use(); + + if (impl.m_loc_proj != -1) { + glUniformMatrix4fv(impl.m_loc_proj, 1, GL_FALSE, glm::value_ptr(proj)); + } + if (impl.m_loc_view != -1) { + glUniformMatrix4fv(impl.m_loc_view, 1, GL_FALSE, glm::value_ptr(view)); + } + if (impl.m_loc_model != -1) { + glUniformMatrix4fv(impl.m_loc_model, 1, GL_FALSE, glm::value_ptr(model)); + } + + if (impl.m_depth_test) { + glEnable(GL_DEPTH_TEST); + } else { + glDisable(GL_DEPTH_TEST); + } + + impl.m_vao.bind(); + if (impl.m_uses_index) { + glDrawElements(impl.m_draw_mode, impl.m_index_count, impl.m_index_gl_type, nullptr); + } else { + glDrawArrays(impl.m_draw_mode, 0, impl.m_vertex_count); + } + impl.m_vao.unbind(); + impl.m_prog.unuse(); + + // Reset texture state to prevent leakage on scene switches or between passes + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); +} + +auto pipeline::bind_texture(char const* sampler_name, std::uint32_t texture_id, std::uint32_t unit) const -> void { + if (m_impl) { + m_impl->bind_texture(sampler_name, texture_id, unit); + } +} + +struct render_target::impl { + GLuint m_fbo = 0; + opengl::texture m_color{opengl::texture_target::_2d}; + GLuint m_depth_rbo = 0; + int m_width = 0; + int m_height = 0; + bool m_valid = false; + + auto build(int width, int height) -> bool; + auto cleanup() -> void; +}; + +auto render_target::impl::build(int width, int height) -> bool { + m_width = width; + m_height = height; + + glGenFramebuffers(1, &m_fbo); + if (m_fbo == 0) { + return false; + } + + // Color texture + m_color = opengl::texture(opengl::texture_target::_2d); + m_color.bind(0); + m_color.upload(nullptr, width, height, opengl::texture_format::rgba, opengl::texture_type::ubyte); + m_color.set_filter(GL_LINEAR, GL_LINEAR); + m_color.set_wrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + m_color.unbind(); + + // Depth renderbuffer + glGenRenderbuffers(1, &m_depth_rbo); + glBindRenderbuffer(GL_RENDERBUFFER, m_depth_rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + // Attach to FBO + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_color.id(), 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depth_rbo); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (status != GL_FRAMEBUFFER_COMPLETE) { + cleanup(); + return false; + } + + m_valid = true; + return true; +} + +auto render_target::impl::cleanup() -> void { + if (m_fbo != 0) { + glDeleteFramebuffers(1, &m_fbo); + m_fbo = 0; + } + if (m_depth_rbo != 0) { + glDeleteRenderbuffers(1, &m_depth_rbo); + m_depth_rbo = 0; + } + m_valid = false; +} + +render_target::render_target(int width, int height) : m_impl(std::make_unique()) { + m_impl->build(width, height); +} + +render_target::render_target(render_target&& other) noexcept + : m_impl(std::move(other.m_impl)) {} + +auto render_target::operator=(render_target&& other) noexcept -> render_target& { + if (this != &other) { + m_impl = std::move(other.m_impl); + } + return *this; +} + +render_target::~render_target() { + if (m_impl) { + m_impl->cleanup(); + } +} + +auto render_target::bind() const -> void { + if (!valid()) return; + glBindFramebuffer(GL_FRAMEBUFFER, m_impl->m_fbo); + glViewport(0, 0, m_impl->m_width, m_impl->m_height); +} + +auto render_target::unbind() const -> void { + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +auto render_target::color_id() const -> std::uint32_t { + return m_impl ? m_impl->m_color.id() : 0; +} + +auto render_target::width() const -> int { + return m_impl ? m_impl->m_width : 0; +} + +auto render_target::height() const -> int { + return m_impl ? m_impl->m_height : 0; +} + +auto render_target::valid() const -> bool { + return m_impl && m_impl->m_valid; +} + +auto render_target::resize(int width, int height) -> void { + if (m_impl && m_impl->m_width == width && m_impl->m_height == height) { + return; + } + if (m_impl) { + m_impl->cleanup(); + } + if (!m_impl) { + m_impl = std::make_unique(); + } + m_impl->build(width, height); +} + +} // namespace cbt::gfx diff --git a/cbt/gfx.hpp b/cbt/gfx.hpp new file mode 100644 index 0000000..2dadfdc --- /dev/null +++ b/cbt/gfx.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "glm/glm.hpp" + +namespace cbt::gfx { + +enum class primitive_type { + triangles +}; + +enum class index_type { + uint16, + uint32 +}; + +struct attribute_desc { + std::uint32_t location = 0; + std::uint32_t num_components = 3; + std::uint32_t offset = 0; +}; + +struct pipeline_desc { + std::span vertex_data{}; + std::span index_data{}; + std::vector attributes{}; + std::uint32_t vertex_stride = 0; + char const* vertex_shader_src = nullptr; + char const* fragment_shader_src = nullptr; + bool depth_test = true; + primitive_type primitive = primitive_type::triangles; + index_type index_type_ = index_type::uint32; +}; + +class pipeline { +public: + pipeline(); + explicit pipeline(pipeline_desc const& desc); + pipeline(pipeline const&) = delete; + pipeline(pipeline&& other) noexcept; + auto operator=(pipeline const&) -> pipeline& = delete; + auto operator=(pipeline&& other) noexcept -> pipeline&; + ~pipeline(); + + auto valid() const -> bool; + auto draw(glm::mat4 const& model, glm::mat4 const& view, glm::mat4 const& proj) const -> void; + auto bind_texture(char const* sampler_name, std::uint32_t texture_id, std::uint32_t unit = 0) const -> void; + +private: + struct impl; + std::unique_ptr m_impl; +}; + +class render_target { +public: + explicit render_target(int width, int height); + render_target(render_target const&) = delete; + render_target(render_target&& other) noexcept; + auto operator=(render_target const&) -> render_target& = delete; + auto operator=(render_target&& other) noexcept -> render_target&; + ~render_target(); + + auto bind() const -> void; + auto unbind() const -> void; + auto color_id() const -> std::uint32_t; + auto width() const -> int; + auto height() const -> int; + auto valid() const -> bool; + auto resize(int width, int height) -> void; + +private: + struct impl; + std::unique_ptr m_impl; +}; + +} // namespace cbt::gfx diff --git a/scenes/cube.cpp b/scenes/cube.cpp index f466509..6eaf50e 100644 --- a/scenes/cube.cpp +++ b/scenes/cube.cpp @@ -1,8 +1,8 @@ #include +#include #include "glad/glad.h" #include "glm/gtc/matrix_transform.hpp" -#include "glm/gtc/type_ptr.hpp" #include "scenes/cube.hpp" @@ -13,17 +13,17 @@ cube::cube() { } auto cube::init() -> bool { - if (!build_shader()) { + if (!build_pipeline()) { return false; } - build_mesh(); - glEnable(GL_DEPTH_TEST); return true; } auto cube::update(float) -> void {} auto cube::render(int width, int height) -> void { + glViewport(0, 0, width, height); + auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration(now - m_start).count(); @@ -35,18 +35,33 @@ auto cube::render(int width, int height) -> void { auto view = glm::translate(glm::mat4{1.0f}, glm::vec3{0.0f, 0.0f, -3.0f}); auto model = glm::rotate(glm::mat4{1.0f}, elapsed, glm::vec3{1.0f, 0.5f, 0.3f}); - m_prog.use(); - glUniformMatrix4fv(m_loc_proj, 1, GL_FALSE, glm::value_ptr(proj)); - glUniformMatrix4fv(m_loc_view, 1, GL_FALSE, glm::value_ptr(view)); - glUniformMatrix4fv(m_loc_model, 1, GL_FALSE, glm::value_ptr(model)); - - m_vao.bind(); - glDrawArrays(GL_TRIANGLES, 0, 36); - m_vao.unbind(); - m_prog.unuse(); + m_pipeline.draw(model, view, proj); } -auto cube::build_mesh() -> void { +auto cube::build_pipeline() -> bool { + char const* vert_src = R"glsl( + #version 410 core + layout(location = 0) in vec3 a_pos; + layout(location = 1) in vec3 a_color; + uniform mat4 u_model; + uniform mat4 u_view; + uniform mat4 u_proj; + out vec3 v_color; + void main() { + gl_Position = u_proj * u_view * u_model * vec4(a_pos, 1.0); + v_color = a_color; + } + )glsl"; + + char const* frag_src = R"glsl( + #version 410 core + in vec3 v_color; + out vec4 frag_color; + void main() { + frag_color = vec4(v_color, 1.0); + } + )glsl"; + std::array vertices = { // front face (cyan) 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, @@ -97,52 +112,22 @@ auto cube::build_mesh() -> void { -0.5f, 0.5f, -0.5f, 1.0f, 0.5f, 0.0f, }; - m_vbo.upload(vertices.data(), vertices.size() * sizeof(float)); + gfx::pipeline_desc desc{ + .vertex_data = std::as_bytes(std::span{vertices}), + .attributes = { + {.location = 0, .num_components = 3, .offset = 0}, + {.location = 1, .num_components = 3, .offset = 12}, + }, + .vertex_stride = 24, + .vertex_shader_src = vert_src, + .fragment_shader_src = frag_src, + .depth_test = true, + .primitive = gfx::primitive_type::triangles + // no index data + }; - m_vao.bind(); - m_vbo.bind(); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), nullptr); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), - reinterpret_cast(3 * sizeof(float))); - m_vbo.unbind(); - m_vao.unbind(); -} - -auto cube::build_shader() -> bool { - char const* vert_src = R"glsl( - #version 410 core - layout(location = 0) in vec3 a_pos; - layout(location = 1) in vec3 a_color; - uniform mat4 u_model; - uniform mat4 u_view; - uniform mat4 u_proj; - out vec3 v_color; - void main() { - gl_Position = u_proj * u_view * u_model * vec4(a_pos, 1.0); - v_color = a_color; - } - )glsl"; - - char const* frag_src = R"glsl( - #version 410 core - in vec3 v_color; - out vec4 frag_color; - void main() { - frag_color = vec4(v_color, 1.0); - } - )glsl"; - - if (!m_prog.compile_vertex(vert_src) || !m_prog.compile_fragment(frag_src) || !m_prog.link()) { - return false; - } - - m_loc_proj = glGetUniformLocation(m_prog.id(), "u_proj"); - m_loc_view = glGetUniformLocation(m_prog.id(), "u_view"); - m_loc_model = glGetUniformLocation(m_prog.id(), "u_model"); - - return true; + m_pipeline = gfx::pipeline{desc}; + return m_pipeline.valid(); } } diff --git a/scenes/cube.hpp b/scenes/cube.hpp index 016faf4..e868414 100644 --- a/scenes/cube.hpp +++ b/scenes/cube.hpp @@ -5,9 +5,7 @@ #include "glm/glm.hpp" #include "cbt/scene.hpp" -#include "cbt/opengl/buffer.hpp" -#include "cbt/opengl/shader.hpp" -#include "cbt/opengl/vao.hpp" +#include "cbt/gfx.hpp" namespace cbt::scenes { @@ -19,18 +17,11 @@ public: auto render(int width, int height) -> void override; private: - opengl::shader m_prog; - opengl::buffer m_vbo; - opengl::vao m_vao; - - GLint m_loc_proj = -1; - GLint m_loc_view = -1; - GLint m_loc_model = -1; + gfx::pipeline m_pipeline; std::chrono::steady_clock::time_point m_start; - auto build_mesh() -> void; - auto build_shader() -> bool; + auto build_pipeline() -> bool; }; } diff --git a/scenes/sphere.cpp b/scenes/sphere.cpp index f81411c..ae50f8b 100644 --- a/scenes/sphere.cpp +++ b/scenes/sphere.cpp @@ -1,10 +1,11 @@ #include #include #include +#include +#include #include "glad/glad.h" #include "glm/gtc/matrix_transform.hpp" -#include "glm/gtc/type_ptr.hpp" #include "scenes/sphere.hpp" @@ -15,11 +16,9 @@ sphere::sphere() { } auto sphere::init() -> bool { - if (!build_shader()) { + if (!build_pipeline() || !build_post_pipeline()) { return false; } - build_mesh(); - glEnable(GL_DEPTH_TEST); return true; } @@ -29,6 +28,12 @@ auto sphere::render(int width, int height) -> void { auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration(now - m_start).count(); + m_rt.resize(width, height); + + glViewport(0, 0, width, height); + + // Step 1: Render scene to texture (offscreen pass) + m_rt.bind(); glClearColor(0.15f, 0.15f, 0.2f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -37,18 +42,59 @@ auto sphere::render(int width, int height) -> void { auto view = glm::translate(glm::mat4{1.0f}, glm::vec3{0.0f, 0.0f, -4.0f}); auto model = glm::rotate(glm::mat4{1.0f}, elapsed, glm::vec3{1.0f, 0.3f, 0.2f}); - m_prog.use(); - glUniformMatrix4fv(m_loc_proj, 1, GL_FALSE, glm::value_ptr(proj)); - glUniformMatrix4fv(m_loc_view, 1, GL_FALSE, glm::value_ptr(view)); - glUniformMatrix4fv(m_loc_model, 1, GL_FALSE, glm::value_ptr(model)); + m_scene_pipeline.draw(model, view, proj); + m_rt.unbind(); - m_vao.bind(); - glDrawElements(GL_TRIANGLES, m_index_count, GL_UNSIGNED_INT, nullptr); - m_vao.unbind(); - m_prog.unuse(); + // Reset viewport for screen pass (RT bind changed it) + glViewport(0, 0, width, height); + + // Step 2: Post-processing step (sample RT texture, apply effect, render to screen) + 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 sphere::build_mesh() -> void { +auto sphere::build_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 vec2 a_uv; + layout(location = 3) 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 vec2 v_uv; + out vec3 v_color; + void main() { + gl_Position = u_proj * u_view * u_model * vec4(a_pos, 1.0); + v_normal = mat3(u_model) * a_normal; + v_world_pos = (u_model * vec4(a_pos, 1.0)).xyz; + v_uv = a_uv; + v_color = a_color; + } + )glsl"; + + char const* frag_src = R"glsl( + #version 410 core + in vec3 v_normal; + in vec3 v_world_pos; + in vec2 v_uv; + in vec3 v_color; + out vec4 frag_color; + void main() { + vec3 light_dir = normalize(vec3(1.0, 1.0, 2.0)); + vec3 normal = normalize(v_normal); + float diff = max(dot(normal, light_dir), 0.0); + vec3 ambient = vec3(0.2); + vec3 result = (ambient + diff * 0.8) * v_color; + frag_color = vec4(result, 1.0); + } + )glsl"; + struct vertex { glm::vec3 position; glm::vec3 normal; @@ -127,88 +173,82 @@ auto sphere::build_mesh() -> void { offset += div * div; } - m_index_count = static_cast(indices.size()); + gfx::pipeline_desc desc{ + .vertex_data = std::as_bytes(std::span{vertices}), + .index_data = std::as_bytes(std::span{indices}), + .attributes = { + {.location = 0, .num_components = 3, .offset = 0}, + {.location = 1, .num_components = 3, .offset = 12}, + {.location = 2, .num_components = 2, .offset = 24}, + {.location = 3, .num_components = 3, .offset = 32}, + }, + .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_vbo.upload(vertices.data(), vertices.size() * sizeof(vertex)); - m_ebo.upload(indices.data(), indices.size() * sizeof(std::uint32_t)); - - m_vao.bind(); - m_vbo.bind(); - m_ebo.bind(); - - // location 0: position (vec3) - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), nullptr); - - // location 1: normal (vec3) - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), - reinterpret_cast(3 * sizeof(float))); - - // location 2: uv (vec2) - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), - reinterpret_cast(6 * sizeof(float))); - - // location 3: color (vec3) - glEnableVertexAttribArray(3); - glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), - reinterpret_cast(8 * sizeof(float))); - - m_vao.unbind(); - m_vbo.unbind(); - m_ebo.unbind(); + m_scene_pipeline = gfx::pipeline{desc}; + return m_scene_pipeline.valid(); } -auto sphere::build_shader() -> bool { - char const* vert_src = R"glsl( +auto sphere::build_post_pipeline() -> bool { + char const* post_vert = R"glsl( #version 410 core - layout(location = 0) in vec3 a_pos; - layout(location = 1) in vec3 a_normal; - layout(location = 2) in vec2 a_uv; - layout(location = 3) 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; + layout(location = 0) in vec2 a_pos; + layout(location = 1) in vec2 a_uv; out vec2 v_uv; - out vec3 v_color; void main() { - gl_Position = u_proj * u_view * u_model * vec4(a_pos, 1.0); - v_normal = mat3(u_model) * a_normal; - v_world_pos = (u_model * vec4(a_pos, 1.0)).xyz; + gl_Position = vec4(a_pos, 0.0, 1.0); v_uv = a_uv; - v_color = a_color; } )glsl"; - char const* frag_src = R"glsl( + char const* post_frag = R"glsl( #version 410 core - in vec3 v_normal; - in vec3 v_world_pos; in vec2 v_uv; - in vec3 v_color; + uniform sampler2D u_texture; out vec4 frag_color; void main() { - vec3 light_dir = normalize(vec3(1.0, 1.0, 2.0)); - vec3 normal = normalize(v_normal); - float diff = max(dot(normal, light_dir), 0.0); - vec3 ambient = vec3(0.2); - vec3 result = (ambient + diff * 0.8) * v_color; - frag_color = vec4(result, 1.0); + vec3 col = texture(u_texture, v_uv).rgb; + // simple processing: vignette only (preserves original colors from sphere faces) + float vig = 1.0 - length(v_uv * 2.0 - 1.0) * 0.5; + frag_color = vec4(col * vig, 1.0); } )glsl"; - if (!m_prog.compile_vertex(vert_src) || !m_prog.compile_fragment(frag_src) || !m_prog.link()) { - return false; - } + struct fs_vertex { + glm::vec2 pos; + glm::vec2 uv; + }; - m_loc_proj = glGetUniformLocation(m_prog.id(), "u_proj"); - m_loc_view = glGetUniformLocation(m_prog.id(), "u_view"); - m_loc_model = glGetUniformLocation(m_prog.id(), "u_model"); + std::array 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 qinds = {0, 1, 2, 0, 2, 3}; - return true; + 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(); } } diff --git a/scenes/sphere.hpp b/scenes/sphere.hpp index 448df3c..7019ce9 100644 --- a/scenes/sphere.hpp +++ b/scenes/sphere.hpp @@ -5,9 +5,7 @@ #include "glm/glm.hpp" #include "cbt/scene.hpp" -#include "cbt/opengl/buffer.hpp" -#include "cbt/opengl/shader.hpp" -#include "cbt/opengl/vao.hpp" +#include "cbt/gfx.hpp" namespace cbt::scenes { @@ -19,21 +17,14 @@ public: auto render(int width, int height) -> void override; private: - opengl::shader m_prog; - opengl::buffer m_vbo; - opengl::buffer m_ebo{opengl::buffer_type::index}; - opengl::vao m_vao; - - GLint m_loc_proj = -1; - GLint m_loc_view = -1; - GLint m_loc_model = -1; - - GLsizei m_index_count = 0; + gfx::pipeline m_scene_pipeline; + gfx::pipeline m_post_pipeline; + gfx::render_target m_rt{0, 0}; std::chrono::steady_clock::time_point m_start; - auto build_mesh() -> void; - auto build_shader() -> bool; + auto build_pipeline() -> bool; + auto build_post_pipeline() -> bool; }; }