feat: add gfx pipeline abstraction with render targets

Add cbt/gfx layer (pipeline + render_target) inspired by Sokol but
C++-friendly with RAII and pipeline_desc. Supports building full
graphics pipelines (VS/IA/raster/PS/OM), render-to-texture (FBO),
and post-processing steps (scene -> RT -> fullscreen quad with
sampler + vignette).

- Depth/state/viewport/texture cleanup for reliable scene switching.
- Updated cube/sphere to demonstrate (sphere uses RT+post).
- Vulkan backend easy via PIMPL in impl (same public API).
- Fixed depth test, viewport restore, and state leakage on switch.

Followed coding conventions (snake_case, trailing returns, east const,
include order, no ; after ns/class, etc.).
This commit is contained in:
2026-05-06 00:43:00 +02:00
parent 98673b57ff
commit 5ec8cfc735
7 changed files with 565 additions and 163 deletions
+2 -1
View File
@@ -28,12 +28,13 @@ add_library(cbt_opengl STATIC
"cbt/opengl/descriptor.cpp" "cbt/opengl/descriptor.cpp"
"cbt/opengl/shader.cpp" "cbt/opengl/shader.cpp"
"cbt/opengl/vao.cpp" "cbt/opengl/vao.cpp"
"cbt/gfx.cpp"
) )
target_include_directories(cbt_opengl PRIVATE ".") target_include_directories(cbt_opengl PRIVATE ".")
target_compile_features(cbt_opengl PRIVATE cxx_std_23) target_compile_features(cbt_opengl PRIVATE cxx_std_23)
target_compile_options(cbt_opengl PRIVATE ${BASE_OPTIONS}) target_compile_options(cbt_opengl PRIVATE ${BASE_OPTIONS})
target_compile_definitions(cbt_opengl PRIVATE ${BASE_DEFINITIONS}) 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 # Scene base library
add_library(cbt_scene STATIC add_library(cbt_scene STATIC
+313
View File
@@ -0,0 +1,313 @@
#include <cstddef>
#include <utility>
#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<GLsizei>(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<GLsizei>(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<GLint>(attr.num_components),
GL_FLOAT,
GL_FALSE,
static_cast<GLsizei>(desc.vertex_stride),
reinterpret_cast<void*>(static_cast<std::uintptr_t>(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<GLint>(unit));
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D, texture_id);
}
}
pipeline::pipeline() : m_impl(std::make_unique<impl>()) {}
pipeline::pipeline(pipeline_desc const& desc) : m_impl(std::make_unique<impl>()) {
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<impl>()) {
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<impl>();
}
m_impl->build(width, height);
}
} // namespace cbt::gfx
+81
View File
@@ -0,0 +1,81 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
#include <vector>
#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<std::byte const> vertex_data{};
std::span<std::byte const> index_data{};
std::vector<attribute_desc> 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<impl> 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<impl> m_impl;
};
} // namespace cbt::gfx
+44 -59
View File
@@ -1,8 +1,8 @@
#include <array> #include <array>
#include <span>
#include "glad/glad.h" #include "glad/glad.h"
#include "glm/gtc/matrix_transform.hpp" #include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "scenes/cube.hpp" #include "scenes/cube.hpp"
@@ -13,17 +13,17 @@ cube::cube() {
} }
auto cube::init() -> bool { auto cube::init() -> bool {
if (!build_shader()) { if (!build_pipeline()) {
return false; return false;
} }
build_mesh();
glEnable(GL_DEPTH_TEST);
return true; return true;
} }
auto cube::update(float) -> void {} auto cube::update(float) -> void {}
auto cube::render(int width, int height) -> void { auto cube::render(int width, int height) -> void {
glViewport(0, 0, width, height);
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration<float>(now - m_start).count(); auto elapsed = std::chrono::duration<float>(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 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}); auto model = glm::rotate(glm::mat4{1.0f}, elapsed, glm::vec3{1.0f, 0.5f, 0.3f});
m_prog.use(); m_pipeline.draw(model, view, proj);
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();
} }
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<float, 36 * 6> vertices = { std::array<float, 36 * 6> vertices = {
// front face (cyan) // front face (cyan)
0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 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, -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_pipeline = gfx::pipeline{desc};
m_vbo.bind(); return m_pipeline.valid();
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<void*>(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;
} }
} }
+3 -12
View File
@@ -5,9 +5,7 @@
#include "glm/glm.hpp" #include "glm/glm.hpp"
#include "cbt/scene.hpp" #include "cbt/scene.hpp"
#include "cbt/opengl/buffer.hpp" #include "cbt/gfx.hpp"
#include "cbt/opengl/shader.hpp"
#include "cbt/opengl/vao.hpp"
namespace cbt::scenes { namespace cbt::scenes {
@@ -19,18 +17,11 @@ public:
auto render(int width, int height) -> void override; auto render(int width, int height) -> void override;
private: private:
opengl::shader m_prog; gfx::pipeline m_pipeline;
opengl::buffer m_vbo;
opengl::vao m_vao;
GLint m_loc_proj = -1;
GLint m_loc_view = -1;
GLint m_loc_model = -1;
std::chrono::steady_clock::time_point m_start; std::chrono::steady_clock::time_point m_start;
auto build_mesh() -> void; auto build_pipeline() -> bool;
auto build_shader() -> bool;
}; };
} }
+116 -76
View File
@@ -1,10 +1,11 @@
#include <cstddef> #include <cstddef>
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include <span>
#include <array>
#include "glad/glad.h" #include "glad/glad.h"
#include "glm/gtc/matrix_transform.hpp" #include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "scenes/sphere.hpp" #include "scenes/sphere.hpp"
@@ -15,11 +16,9 @@ sphere::sphere() {
} }
auto sphere::init() -> bool { auto sphere::init() -> bool {
if (!build_shader()) { if (!build_pipeline() || !build_post_pipeline()) {
return false; return false;
} }
build_mesh();
glEnable(GL_DEPTH_TEST);
return true; return true;
} }
@@ -29,6 +28,12 @@ auto sphere::render(int width, int height) -> void {
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration<float>(now - m_start).count(); auto elapsed = std::chrono::duration<float>(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); glClearColor(0.15f, 0.15f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 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 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}); auto model = glm::rotate(glm::mat4{1.0f}, elapsed, glm::vec3{1.0f, 0.3f, 0.2f});
m_prog.use(); m_scene_pipeline.draw(model, view, proj);
glUniformMatrix4fv(m_loc_proj, 1, GL_FALSE, glm::value_ptr(proj)); m_rt.unbind();
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(); // Reset viewport for screen pass (RT bind changed it)
glDrawElements(GL_TRIANGLES, m_index_count, GL_UNSIGNED_INT, nullptr); glViewport(0, 0, width, height);
m_vao.unbind();
m_prog.unuse(); // 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 { struct vertex {
glm::vec3 position; glm::vec3 position;
glm::vec3 normal; glm::vec3 normal;
@@ -127,88 +173,82 @@ auto sphere::build_mesh() -> void {
offset += div * div; offset += div * div;
} }
m_index_count = static_cast<GLsizei>(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_scene_pipeline = gfx::pipeline{desc};
m_ebo.upload(indices.data(), indices.size() * sizeof(std::uint32_t)); return m_scene_pipeline.valid();
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<void*>(3 * sizeof(float)));
// location 2: uv (vec2)
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(vertex),
reinterpret_cast<void*>(6 * sizeof(float)));
// location 3: color (vec3)
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(vertex),
reinterpret_cast<void*>(8 * sizeof(float)));
m_vao.unbind();
m_vbo.unbind();
m_ebo.unbind();
} }
auto sphere::build_shader() -> bool { auto sphere::build_post_pipeline() -> bool {
char const* vert_src = R"glsl( char const* post_vert = R"glsl(
#version 410 core #version 410 core
layout(location = 0) in vec3 a_pos; layout(location = 0) in vec2 a_pos;
layout(location = 1) in vec3 a_normal; layout(location = 1) in vec2 a_uv;
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 vec2 v_uv;
out vec3 v_color;
void main() { void main() {
gl_Position = u_proj * u_view * u_model * vec4(a_pos, 1.0); gl_Position = vec4(a_pos, 0.0, 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_uv = a_uv;
v_color = a_color;
} }
)glsl"; )glsl";
char const* frag_src = R"glsl( char const* post_frag = R"glsl(
#version 410 core #version 410 core
in vec3 v_normal;
in vec3 v_world_pos;
in vec2 v_uv; in vec2 v_uv;
in vec3 v_color; uniform sampler2D u_texture;
out vec4 frag_color; out vec4 frag_color;
void main() { void main() {
vec3 light_dir = normalize(vec3(1.0, 1.0, 2.0)); vec3 col = texture(u_texture, v_uv).rgb;
vec3 normal = normalize(v_normal); // simple processing: vignette only (preserves original colors from sphere faces)
float diff = max(dot(normal, light_dir), 0.0); float vig = 1.0 - length(v_uv * 2.0 - 1.0) * 0.5;
vec3 ambient = vec3(0.2); frag_color = vec4(col * vig, 1.0);
vec3 result = (ambient + diff * 0.8) * v_color;
frag_color = vec4(result, 1.0);
} }
)glsl"; )glsl";
if (!m_prog.compile_vertex(vert_src) || !m_prog.compile_fragment(frag_src) || !m_prog.link()) { struct fs_vertex {
return false; glm::vec2 pos;
} glm::vec2 uv;
};
m_loc_proj = glGetUniformLocation(m_prog.id(), "u_proj"); std::array<fs_vertex, 4> qverts = {{
m_loc_view = glGetUniformLocation(m_prog.id(), "u_view"); {{-1.0f, -1.0f}, {0.0f, 0.0f}},
m_loc_model = glGetUniformLocation(m_prog.id(), "u_model"); {{ 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> 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();
} }
} }
+6 -15
View File
@@ -5,9 +5,7 @@
#include "glm/glm.hpp" #include "glm/glm.hpp"
#include "cbt/scene.hpp" #include "cbt/scene.hpp"
#include "cbt/opengl/buffer.hpp" #include "cbt/gfx.hpp"
#include "cbt/opengl/shader.hpp"
#include "cbt/opengl/vao.hpp"
namespace cbt::scenes { namespace cbt::scenes {
@@ -19,21 +17,14 @@ public:
auto render(int width, int height) -> void override; auto render(int width, int height) -> void override;
private: private:
opengl::shader m_prog; gfx::pipeline m_scene_pipeline;
opengl::buffer m_vbo; gfx::pipeline m_post_pipeline;
opengl::buffer m_ebo{opengl::buffer_type::index}; gfx::render_target m_rt{0, 0};
opengl::vao m_vao;
GLint m_loc_proj = -1;
GLint m_loc_view = -1;
GLint m_loc_model = -1;
GLsizei m_index_count = 0;
std::chrono::steady_clock::time_point m_start; std::chrono::steady_clock::time_point m_start;
auto build_mesh() -> void; auto build_pipeline() -> bool;
auto build_shader() -> bool; auto build_post_pipeline() -> bool;
}; };
} }