Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4ef4adfc7 | |||
| 91fe3c6e8c | |||
| 6f696d377b | |||
| 78d0515e8b | |||
| 4a88c8cc06 | |||
| c3860cc1d3 | |||
| 3f78d0978d | |||
| 22d2bb1c40 | |||
| 7a81b30d32 | |||
| 40ae94788e |
@@ -43,3 +43,6 @@ imgui.ini
|
|||||||
|
|
||||||
# Compiled shaders
|
# Compiled shaders
|
||||||
*.spv
|
*.spv
|
||||||
|
|
||||||
|
# Screenshots
|
||||||
|
*.png
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ To add a new dependency:
|
|||||||
|
|
||||||
The project is split into static libraries:
|
The project is split into static libraries:
|
||||||
|
|
||||||
- **`cbt_opengl`** — OpenGL abstraction (context, buffer, texture, vao, shader, descriptor)
|
- **`cbt_opengl`** — OpenGL abstraction and window management (window, context, buffer, texture, vao, shader, descriptor)
|
||||||
- **`cbt_scene`** — Base scene class
|
- **`cbt_scene`** — Base scene class
|
||||||
- **`scenes_cube`** — Cube scene implementation
|
- **`scenes_cube`** — Cube scene implementation
|
||||||
|
|
||||||
@@ -102,8 +102,10 @@ cuber/
|
|||||||
cbt/ # Project namespace (utilities)
|
cbt/ # Project namespace (utilities)
|
||||||
scene.hpp # Base scene class
|
scene.hpp # Base scene class
|
||||||
scene.cpp # Scene implementation
|
scene.cpp # Scene implementation
|
||||||
|
window.hpp # GLFW window RAII wrapper
|
||||||
|
window.cpp # Window implementation
|
||||||
opengl/ # OpenGL abstraction layer
|
opengl/ # OpenGL abstraction layer
|
||||||
context.hpp # OpenGL context RAII wrapper
|
context.hpp # OpenGL context RAII wrapper (GLAD setup)
|
||||||
context.cpp # Context implementation
|
context.cpp # Context implementation
|
||||||
buffer.hpp # Buffer resource (VBO, EBO, UBO, SSBO)
|
buffer.hpp # Buffer resource (VBO, EBO, UBO, SSBO)
|
||||||
buffer.cpp # Buffer implementation
|
buffer.cpp # Buffer implementation
|
||||||
|
|||||||
+14
-2
@@ -17,9 +17,11 @@ find_package(glfw3 REQUIRED)
|
|||||||
find_package(glad REQUIRED)
|
find_package(glad REQUIRED)
|
||||||
find_package(asio REQUIRED)
|
find_package(asio REQUIRED)
|
||||||
find_package(glm REQUIRED)
|
find_package(glm REQUIRED)
|
||||||
|
find_package(stb REQUIRED)
|
||||||
|
|
||||||
# OpenGL abstraction library
|
# OpenGL abstraction library
|
||||||
add_library(cbt_opengl STATIC
|
add_library(cbt_opengl STATIC
|
||||||
|
"cbt/window.cpp"
|
||||||
"cbt/opengl/context.cpp"
|
"cbt/opengl/context.cpp"
|
||||||
"cbt/opengl/buffer.cpp"
|
"cbt/opengl/buffer.cpp"
|
||||||
"cbt/opengl/texture.cpp"
|
"cbt/opengl/texture.cpp"
|
||||||
@@ -31,7 +33,7 @@ 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)
|
target_link_libraries(cbt_opengl PUBLIC fmt::fmt glfw::glfw glad::glad stb::stb)
|
||||||
|
|
||||||
# Scene base library
|
# Scene base library
|
||||||
add_library(cbt_scene STATIC
|
add_library(cbt_scene STATIC
|
||||||
@@ -53,10 +55,20 @@ target_compile_options(scenes_cube PRIVATE ${BASE_OPTIONS})
|
|||||||
target_compile_definitions(scenes_cube PRIVATE ${BASE_DEFINITIONS})
|
target_compile_definitions(scenes_cube PRIVATE ${BASE_DEFINITIONS})
|
||||||
target_link_libraries(scenes_cube PUBLIC cbt_scene glm::glm)
|
target_link_libraries(scenes_cube PUBLIC cbt_scene glm::glm)
|
||||||
|
|
||||||
|
# Sphere scene
|
||||||
|
add_library(scenes_sphere STATIC
|
||||||
|
"scenes/sphere.cpp"
|
||||||
|
)
|
||||||
|
target_include_directories(scenes_sphere PRIVATE ".")
|
||||||
|
target_compile_features(scenes_sphere PRIVATE cxx_std_23)
|
||||||
|
target_compile_options(scenes_sphere PRIVATE ${BASE_OPTIONS})
|
||||||
|
target_compile_definitions(scenes_sphere PRIVATE ${BASE_DEFINITIONS})
|
||||||
|
target_link_libraries(scenes_sphere PUBLIC cbt_scene glm::glm)
|
||||||
|
|
||||||
# Main executable
|
# Main executable
|
||||||
add_executable(cuber "cuber.cpp")
|
add_executable(cuber "cuber.cpp")
|
||||||
target_include_directories(cuber PRIVATE ".")
|
target_include_directories(cuber PRIVATE ".")
|
||||||
target_compile_features(cuber PRIVATE cxx_std_23)
|
target_compile_features(cuber PRIVATE cxx_std_23)
|
||||||
target_compile_options(cuber PRIVATE ${BASE_OPTIONS})
|
target_compile_options(cuber PRIVATE ${BASE_OPTIONS})
|
||||||
target_compile_definitions(cuber PRIVATE ${BASE_DEFINITIONS})
|
target_compile_definitions(cuber PRIVATE ${BASE_DEFINITIONS})
|
||||||
target_link_libraries(cuber PRIVATE cbt_scene scenes_cube asio::asio ${BASE_LIBRARIES})
|
target_link_libraries(cuber PRIVATE cbt_scene scenes_cube scenes_sphere asio::asio ${BASE_LIBRARIES})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "cbt/opengl/buffer.hpp"
|
|
||||||
|
|
||||||
#include "glad/glad.h"
|
#include "glad/glad.h"
|
||||||
|
|
||||||
|
#include "cbt/opengl/buffer.hpp"
|
||||||
|
|
||||||
namespace cbt::opengl {
|
namespace cbt::opengl {
|
||||||
|
|
||||||
buffer::buffer() {
|
buffer::buffer() {
|
||||||
|
|||||||
+15
-60
@@ -1,27 +1,15 @@
|
|||||||
|
#include <string_view>
|
||||||
|
|
||||||
#define GLFW_INCLUDE_NONE
|
#define GLFW_INCLUDE_NONE
|
||||||
#include "GLFW/glfw3.h"
|
#include "GLFW/glfw3.h"
|
||||||
|
|
||||||
|
#include "fmt/std.h"
|
||||||
|
#include "glad/glad.h"
|
||||||
|
|
||||||
#include "cbt/opengl/context.hpp"
|
#include "cbt/opengl/context.hpp"
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "glad/glad.h"
|
|
||||||
#include "fmt/std.h"
|
|
||||||
|
|
||||||
namespace cbt::opengl {
|
namespace cbt::opengl {
|
||||||
|
|
||||||
auto context::init() -> bool {
|
|
||||||
if (!glfwInit()) {
|
|
||||||
fmt::print("Failed to initialize GLFW\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto context::terminate() -> void {
|
|
||||||
glfwTerminate();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto context::setup_gl() -> bool {
|
auto context::setup_gl() -> bool {
|
||||||
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
|
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
|
||||||
fmt::print("Failed to initialize GLAD\n");
|
fmt::print("Failed to initialize GLAD\n");
|
||||||
@@ -40,58 +28,25 @@ auto context::print_info() -> void {
|
|||||||
fmt::print("\n");
|
fmt::print("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
context::context(std::string title, int width, int height) {
|
context::context(window const& win) {
|
||||||
if (!init()) {
|
glfwMakeContextCurrent(win.raw());
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_initialized = true;
|
|
||||||
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
|
|
||||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
||||||
|
|
||||||
m_window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
|
|
||||||
if (!m_window) {
|
|
||||||
fmt::print("Failed to create window\n");
|
|
||||||
terminate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
glfwMakeContextCurrent(m_window);
|
|
||||||
|
|
||||||
if (!setup_gl()) {
|
if (!setup_gl()) {
|
||||||
terminate();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_valid = true;
|
||||||
|
|
||||||
|
// Set initial viewport to match window size
|
||||||
|
glViewport(0, 0, win.width(), win.height());
|
||||||
}
|
}
|
||||||
|
|
||||||
context::~context() {
|
context::~context() {}
|
||||||
if (m_window) {
|
|
||||||
glfwDestroyWindow(m_window);
|
|
||||||
}
|
|
||||||
if (m_initialized) {
|
|
||||||
terminate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto context::should_close() const -> bool {
|
|
||||||
return m_window && glfwWindowShouldClose(m_window);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto context::valid() const -> bool {
|
auto context::valid() const -> bool {
|
||||||
return m_window != nullptr;
|
return m_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto context::swap_buffers() -> void {
|
auto context::set_size(int width, int height) -> void {
|
||||||
glfwSwapBuffers(m_window);
|
glViewport(0, 0, width, height);
|
||||||
}
|
|
||||||
|
|
||||||
auto context::poll_events() -> void {
|
|
||||||
glfwPollEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto context::raw() const -> GLFWwindow* {
|
|
||||||
return m_window;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-13
@@ -1,29 +1,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include "cbt/window.hpp"
|
||||||
|
|
||||||
#define GLFW_INCLUDE_NONE
|
|
||||||
#include "GLFW/glfw3.h"
|
|
||||||
|
|
||||||
namespace cbt::opengl {
|
namespace cbt::opengl {
|
||||||
|
|
||||||
class context {
|
class context {
|
||||||
public:
|
public:
|
||||||
explicit context(std::string title, int width, int height);
|
explicit context(window const& win);
|
||||||
~context();
|
~context();
|
||||||
|
|
||||||
auto should_close() const -> bool;
|
|
||||||
auto valid() const -> bool;
|
auto valid() const -> bool;
|
||||||
auto swap_buffers() -> void;
|
auto set_size(int width, int height) -> void;
|
||||||
auto poll_events() -> void;
|
|
||||||
auto raw() const -> GLFWwindow*;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GLFWwindow* m_window = nullptr;
|
bool m_valid = false;
|
||||||
bool m_initialized = false;
|
|
||||||
|
|
||||||
static auto init() -> bool;
|
|
||||||
static auto terminate() -> void;
|
|
||||||
static auto setup_gl() -> bool;
|
static auto setup_gl() -> bool;
|
||||||
static auto print_info() -> void;
|
static auto print_info() -> void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "cbt/opengl/descriptor.hpp"
|
|
||||||
|
|
||||||
#include "glad/glad.h"
|
#include "glad/glad.h"
|
||||||
|
|
||||||
|
#include "cbt/opengl/descriptor.hpp"
|
||||||
|
|
||||||
namespace cbt::opengl {
|
namespace cbt::opengl {
|
||||||
|
|
||||||
descriptor_set::descriptor_set() {}
|
descriptor_set::descriptor_set() {}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "cbt/opengl/buffer.hpp"
|
|
||||||
#include "cbt/opengl/texture.hpp"
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cbt/opengl/buffer.hpp"
|
||||||
|
#include "cbt/opengl/texture.hpp"
|
||||||
|
|
||||||
namespace cbt::opengl {
|
namespace cbt::opengl {
|
||||||
|
|
||||||
struct descriptor_binding {
|
struct descriptor_binding {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#include "cbt/opengl/shader.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "fmt/std.h"
|
#include "fmt/std.h"
|
||||||
|
|
||||||
|
#include "cbt/opengl/shader.hpp"
|
||||||
|
|
||||||
namespace cbt::opengl {
|
namespace cbt::opengl {
|
||||||
|
|
||||||
shader::shader() {}
|
shader::shader() {}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "cbt/opengl/texture.hpp"
|
|
||||||
|
|
||||||
#include "glad/glad.h"
|
#include "glad/glad.h"
|
||||||
|
|
||||||
|
#include "cbt/opengl/texture.hpp"
|
||||||
|
|
||||||
namespace cbt::opengl {
|
namespace cbt::opengl {
|
||||||
|
|
||||||
texture::texture() {
|
texture::texture() {
|
||||||
|
|||||||
+1
-1
@@ -8,6 +8,6 @@ auto scene::init() -> bool {
|
|||||||
|
|
||||||
auto scene::update(float) -> void {}
|
auto scene::update(float) -> void {}
|
||||||
|
|
||||||
auto scene::render() -> void {}
|
auto scene::render(int, int) -> void {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ public:
|
|||||||
|
|
||||||
virtual auto init() -> bool;
|
virtual auto init() -> bool;
|
||||||
virtual auto update(float delta_time) -> void;
|
virtual auto update(float delta_time) -> void;
|
||||||
virtual auto render() -> void;
|
virtual auto render(int width, int height) -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
#ifdef _WIN32
|
||||||
|
#include <dwmapi.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define GLFW_INCLUDE_NONE
|
||||||
|
#include "GLFW/glfw3.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||||
|
#include "GLFW/glfw3native.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "fmt/std.h"
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include "stb_image_write.h"
|
||||||
|
#include "glad/glad.h"
|
||||||
|
|
||||||
|
#include "cbt/window.hpp"
|
||||||
|
|
||||||
|
namespace cbt {
|
||||||
|
|
||||||
|
auto window::init_glfw() -> bool {
|
||||||
|
if (!glfwInit()) {
|
||||||
|
fmt::print("Failed to initialize GLFW\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::terminate_glfw() -> void {
|
||||||
|
glfwTerminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
window::window(std::string title, int width, int height) {
|
||||||
|
if (!init_glfw()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_glfw_initialized = true;
|
||||||
|
|
||||||
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
||||||
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
|
||||||
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||||
|
|
||||||
|
m_window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
|
||||||
|
if (!m_window) {
|
||||||
|
fmt::print("Failed to create window\n");
|
||||||
|
terminate_glfw();
|
||||||
|
m_glfw_initialized = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_width = width;
|
||||||
|
m_height = height;
|
||||||
|
|
||||||
|
glfwSetWindowUserPointer(m_window, this);
|
||||||
|
glfwSetWindowSizeCallback(m_window, resize_callback_impl);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
auto hwnd = glfwGetWin32Window(m_window);
|
||||||
|
BOOL use_dark_mode = TRUE;
|
||||||
|
DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &use_dark_mode, sizeof(use_dark_mode));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
window::~window() {
|
||||||
|
if (m_window) {
|
||||||
|
glfwDestroyWindow(m_window);
|
||||||
|
}
|
||||||
|
if (m_glfw_initialized) {
|
||||||
|
terminate_glfw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::should_close() const -> bool {
|
||||||
|
return m_window && glfwWindowShouldClose(m_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::valid() const -> bool {
|
||||||
|
return m_window != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::swap_buffers() -> void {
|
||||||
|
glfwSwapBuffers(m_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::poll_events() -> void {
|
||||||
|
glfwPollEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::raw() const -> GLFWwindow* {
|
||||||
|
return m_window;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::stop() -> void {
|
||||||
|
if (m_window) {
|
||||||
|
glfwSetWindowShouldClose(m_window, GLFW_TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::width() const -> int {
|
||||||
|
return m_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::height() const -> int {
|
||||||
|
return m_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::on_resize(resize_callback cb) -> void {
|
||||||
|
m_resize_cb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::resize_callback_impl(GLFWwindow* glfw_window, int width, int height) -> void {
|
||||||
|
auto* self = static_cast<window*>(glfwGetWindowUserPointer(glfw_window));
|
||||||
|
if (self) {
|
||||||
|
self->m_width = width;
|
||||||
|
self->m_height = height;
|
||||||
|
if (self->m_resize_cb) {
|
||||||
|
self->m_resize_cb(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::screenshot(std::string filepath) const -> bool {
|
||||||
|
if (!m_window || !valid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
glfwGetFramebufferSize(m_window, &width, &height);
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<unsigned char> pixels(static_cast<size_t>(width * height * 4));
|
||||||
|
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||||
|
|
||||||
|
// Flip vertically (OpenGL reads bottom-up; PNG is top-down)
|
||||||
|
for (int y = 0; y < height / 2; ++y) {
|
||||||
|
int y2 = height - 1 - y;
|
||||||
|
for (int x = 0; x < width * 4; ++x) {
|
||||||
|
std::swap(pixels[static_cast<size_t>(y * width * 4 + x)],
|
||||||
|
pixels[static_cast<size_t>(y2 * width * 4 + x)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stbi_write_png(filepath.c_str(), width, height, 4, pixels.data(), width * 4) != 0) {
|
||||||
|
fmt::print("Screenshot saved to {}\n", filepath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt::print("Failed to write screenshot to {}\n", filepath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#define GLFW_INCLUDE_NONE
|
||||||
|
#include "GLFW/glfw3.h"
|
||||||
|
|
||||||
|
namespace cbt {
|
||||||
|
|
||||||
|
class window {
|
||||||
|
public:
|
||||||
|
explicit window(std::string title, int width, int height);
|
||||||
|
~window();
|
||||||
|
|
||||||
|
auto should_close() const -> bool;
|
||||||
|
auto valid() const -> bool;
|
||||||
|
auto swap_buffers() -> void;
|
||||||
|
auto poll_events() -> void;
|
||||||
|
auto raw() const -> GLFWwindow*;
|
||||||
|
auto stop() -> void;
|
||||||
|
auto screenshot(std::string filepath = "screenshot.png") const -> bool;
|
||||||
|
auto width() const -> int;
|
||||||
|
auto height() const -> int;
|
||||||
|
|
||||||
|
using resize_callback = std::function<void(int width, int height)>;
|
||||||
|
auto on_resize(resize_callback cb) -> void;
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLFWwindow* m_window = nullptr;
|
||||||
|
int m_width = 0;
|
||||||
|
int m_height = 0;
|
||||||
|
resize_callback m_resize_cb;
|
||||||
|
bool m_glfw_initialized = false;
|
||||||
|
|
||||||
|
static auto init_glfw() -> bool;
|
||||||
|
static auto terminate_glfw() -> void;
|
||||||
|
static auto resize_callback_impl(GLFWwindow* glfw_window, int width, int height) -> void;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,35 +1,100 @@
|
|||||||
#define GLFW_INCLUDE_NONE
|
#define GLFW_INCLUDE_NONE
|
||||||
#include "GLFW/glfw3.h"
|
|
||||||
|
|
||||||
#include "cbt/opengl/context.hpp"
|
|
||||||
#include "scenes/cube.hpp"
|
|
||||||
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include <asio.hpp>
|
#include "GLFW/glfw3.h"
|
||||||
|
#include "asio.hpp"
|
||||||
|
#include "asio/steady_timer.hpp"
|
||||||
|
#include "fmt/std.h"
|
||||||
|
|
||||||
auto main(int, char const*[]) -> int {
|
#include "cbt/window.hpp"
|
||||||
auto ctx = cbt::opengl::context("cuber", 1280, 720);
|
#include "cbt/opengl/context.hpp"
|
||||||
|
#include "scenes/cube.hpp"
|
||||||
|
#include "scenes/sphere.hpp"
|
||||||
|
|
||||||
|
auto main(int argc, char const* argv[]) -> int {
|
||||||
|
float max_duration_seconds = 0.0f;
|
||||||
|
std::string_view scene_name = "cube";
|
||||||
|
bool take_screenshot = false;
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
std::string_view arg = argv[i];
|
||||||
|
if (arg == "--help" || arg == "-h") {
|
||||||
|
fmt::print("Usage: {} [options]\n", argv[0]);
|
||||||
|
fmt::print(" --duration <seconds> Auto-terminate after N seconds (for testing/CI)\n");
|
||||||
|
fmt::print(" --scene <cube|sphere> Select initial scene (default: cube)\n");
|
||||||
|
fmt::print(" --screenshot Render one frame, save screenshot, and exit\n");
|
||||||
|
fmt::print(" S key Take screenshot (saved as screenshot.png)\n");
|
||||||
|
fmt::print(" 1/2 key Switch between cube/sphere scene\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (arg == "--duration" && i + 1 < argc) {
|
||||||
|
max_duration_seconds = std::stof(std::string(argv[++i]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--scene" && i + 1 < argc) {
|
||||||
|
scene_name = argv[++i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--screenshot") {
|
||||||
|
take_screenshot = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto win = cbt::window("cuber", 1280, 720);
|
||||||
|
|
||||||
|
if (!win.valid()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ctx = cbt::opengl::context(win);
|
||||||
|
|
||||||
if (!ctx.valid()) {
|
if (!ctx.valid()) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto scn = cbt::scenes::cube();
|
// Wire up resize callback
|
||||||
if (!scn.init()) {
|
win.on_resize([&ctx](int width, int height) {
|
||||||
|
ctx.set_size(width, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto cube_scn = cbt::scenes::cube();
|
||||||
|
auto sphere_scn = cbt::scenes::sphere();
|
||||||
|
|
||||||
|
cbt::scene* active_scene = nullptr;
|
||||||
|
if (!cube_scn.init() || !sphere_scn.init()) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (scene_name == "sphere" || scene_name == "2") {
|
||||||
|
active_scene = &sphere_scn;
|
||||||
|
} else {
|
||||||
|
active_scene = &cube_scn;
|
||||||
|
}
|
||||||
|
|
||||||
// signal handling
|
// signal handling + optional duration timer (via ASIO)
|
||||||
asio::io_context io;
|
asio::io_context io;
|
||||||
asio::signal_set signals(io, SIGINT, SIGTERM);
|
asio::signal_set signals(io, SIGINT, SIGTERM);
|
||||||
bool quit = false;
|
|
||||||
|
|
||||||
signals.async_wait([&](auto, auto) {
|
signals.async_wait([&](auto, auto) {
|
||||||
quit = true;
|
win.stop();
|
||||||
io.stop();
|
io.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
asio::steady_timer duration_timer(io);
|
||||||
|
if (max_duration_seconds > 0.0f) {
|
||||||
|
duration_timer.expires_after(std::chrono::milliseconds(
|
||||||
|
static_cast<long long>(max_duration_seconds * 1000.0f)));
|
||||||
|
duration_timer.async_wait([&win](auto ec) {
|
||||||
|
if (!ec) {
|
||||||
|
win.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
auto process_signals = [&]() -> void {
|
auto process_signals = [&]() -> void {
|
||||||
while (io.poll()) {}
|
while (io.poll()) {}
|
||||||
};
|
};
|
||||||
@@ -37,22 +102,36 @@ auto main(int, char const*[]) -> int {
|
|||||||
// render loop
|
// render loop
|
||||||
auto prev = std::chrono::steady_clock::now();
|
auto prev = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
while (!ctx.should_close()) {
|
while (!win.should_close()) {
|
||||||
process_signals();
|
process_signals();
|
||||||
|
|
||||||
if (quit || glfwGetKey(ctx.raw(), GLFW_KEY_Q) == GLFW_PRESS) {
|
if (glfwGetKey(win.raw(), GLFW_KEY_Q) == GLFW_PRESS) {
|
||||||
break;
|
win.stop();
|
||||||
|
}
|
||||||
|
if (glfwGetKey(win.raw(), GLFW_KEY_S) == GLFW_PRESS) {
|
||||||
|
win.screenshot();
|
||||||
|
}
|
||||||
|
if (glfwGetKey(win.raw(), GLFW_KEY_1) == GLFW_PRESS) {
|
||||||
|
active_scene = &cube_scn;
|
||||||
|
}
|
||||||
|
if (glfwGetKey(win.raw(), GLFW_KEY_2) == GLFW_PRESS) {
|
||||||
|
active_scene = &sphere_scn;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
auto dt = std::chrono::duration<float>(now - prev).count();
|
auto dt = std::chrono::duration<float>(now - prev).count();
|
||||||
prev = now;
|
prev = now;
|
||||||
|
|
||||||
scn.update(dt);
|
active_scene->update(dt);
|
||||||
scn.render();
|
active_scene->render(win.width(), win.height());
|
||||||
|
|
||||||
ctx.swap_buffers();
|
win.swap_buffers();
|
||||||
ctx.poll_events();
|
win.poll_events();
|
||||||
|
|
||||||
|
if (take_screenshot) {
|
||||||
|
win.screenshot();
|
||||||
|
win.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Vendored
+13
-39
@@ -1,28 +1,17 @@
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# Find stb
|
# Find stb
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# This module fetches the stb single-file public domain libraries.
|
# This module fetches the stb single-file public domain libraries
|
||||||
|
# (stb_image_write.h for PNG writing, etc.).
|
||||||
#
|
#
|
||||||
# Targets provided:
|
# Targets provided:
|
||||||
# stb::stb - The stb library target
|
# stb::stb - Interface library (add #define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
# in exactly one .cpp before including "stb_image_write.h")
|
||||||
#
|
#
|
||||||
# Variables set:
|
# Variables set:
|
||||||
# stb_FOUND - TRUE if stb is available
|
# stb_FOUND - TRUE if stb is available
|
||||||
# stb_LIBRARIES - The stb library target (stb::stb)
|
|
||||||
# stb_INCLUDE_DIR - Include directories for stb
|
# stb_INCLUDE_DIR - Include directories for stb
|
||||||
# stb_VERSION - Version of stb (commit-based)
|
# stb_VERSION - Version of stb (commit)
|
||||||
#
|
|
||||||
# Usage notes:
|
|
||||||
# stb headers are header-only but require an implementation macro to be
|
|
||||||
# defined in exactly ONE translation unit (.cpp file) before including the
|
|
||||||
# header, e.g.:
|
|
||||||
#
|
|
||||||
# #define STB_IMAGE_IMPLEMENTATION
|
|
||||||
# #include <stb_image.h>
|
|
||||||
#
|
|
||||||
# Each stb header has its own implementation macro. Do NOT define the macro
|
|
||||||
# in header files or in more than one translation unit or you will get
|
|
||||||
# duplicate symbol linker errors.
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
if (DEFINED _FINDSTB_INCLUDED)
|
if (DEFINED _FINDSTB_INCLUDED)
|
||||||
@@ -30,46 +19,31 @@ if (DEFINED _FINDSTB_INCLUDED)
|
|||||||
endif()
|
endif()
|
||||||
set(_FINDSTB_INCLUDED TRUE)
|
set(_FINDSTB_INCLUDED TRUE)
|
||||||
|
|
||||||
# Use the version passed to find_package(), or default to 2.30
|
# Pin to a recent stable commit
|
||||||
if (DEFINED stb_FIND_VERSION AND NOT stb_FIND_VERSION STREQUAL "")
|
set(STB_VERSION "master")
|
||||||
set(STB_VERSION "${stb_FIND_VERSION}")
|
|
||||||
else()
|
|
||||||
set(STB_VERSION "2fb8c5a3deb2110c89669f8d6f36e5833b556b44")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
message(STATUS "Fetching stb ${STB_VERSION}")
|
message(STATUS "Fetching stb ${STB_VERSION}")
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
find_program(GIT_EXECUTABLE git)
|
|
||||||
if (GIT_EXECUTABLE)
|
|
||||||
set(STB_FETCH_METHOD "GIT")
|
|
||||||
else()
|
|
||||||
message(FATAL_ERROR "Fetch with zip not supported.")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (STB_FETCH_METHOD STREQUAL "GIT")
|
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
stb
|
stb
|
||||||
GIT_REPOSITORY https://github.com/nothings/stb.git
|
GIT_REPOSITORY https://github.com/nothings/stb.git
|
||||||
GIT_TAG ${STB_VERSION}
|
GIT_TAG ${STB_VERSION}
|
||||||
)
|
)
|
||||||
endif()
|
|
||||||
|
|
||||||
FetchContent_MakeAvailable(stb)
|
FetchContent_MakeAvailable(stb)
|
||||||
|
|
||||||
if (NOT TARGET stb)
|
|
||||||
add_library(stb INTERFACE)
|
add_library(stb INTERFACE)
|
||||||
target_include_directories(stb SYSTEM INTERFACE "${stb_SOURCE_DIR}")
|
target_include_directories(stb INTERFACE "${stb_SOURCE_DIR}")
|
||||||
endif()
|
|
||||||
|
|
||||||
if (NOT TARGET stb::stb)
|
|
||||||
add_library(stb::stb ALIAS stb)
|
add_library(stb::stb ALIAS stb)
|
||||||
endif()
|
|
||||||
|
|
||||||
set(stb_FOUND TRUE)
|
set(stb_FOUND TRUE)
|
||||||
set(stb_LIBRARIES stb::stb)
|
|
||||||
set(stb_VERSION "${STB_VERSION}")
|
|
||||||
set(stb_INCLUDE_DIR "${stb_SOURCE_DIR}")
|
set(stb_INCLUDE_DIR "${stb_SOURCE_DIR}")
|
||||||
|
set(stb_VERSION "${STB_VERSION}")
|
||||||
|
|
||||||
set(STB_LICENSE_FILE "${stb_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to stb license file")
|
# Mark as SYSTEM includes
|
||||||
|
set_target_properties(stb PROPERTIES
|
||||||
|
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${stb_SOURCE_DIR}"
|
||||||
|
)
|
||||||
|
|||||||
+6
-7
@@ -1,11 +1,10 @@
|
|||||||
#include "scenes/cube.hpp"
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
#include <glm/gtc/type_ptr.hpp>
|
|
||||||
|
|
||||||
#include "glad/glad.h"
|
#include "glad/glad.h"
|
||||||
|
#include "glm/gtc/matrix_transform.hpp"
|
||||||
|
#include "glm/gtc/type_ptr.hpp"
|
||||||
|
|
||||||
|
#include "scenes/cube.hpp"
|
||||||
|
|
||||||
namespace cbt::scenes {
|
namespace cbt::scenes {
|
||||||
|
|
||||||
@@ -24,14 +23,14 @@ auto cube::init() -> bool {
|
|||||||
|
|
||||||
auto cube::update(float) -> void {}
|
auto cube::update(float) -> void {}
|
||||||
|
|
||||||
auto cube::render() -> void {
|
auto cube::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();
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
auto aspect = 1280.0f / 720.0f;
|
auto aspect = float(width) / float(height);
|
||||||
auto proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
|
auto proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
|
||||||
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});
|
||||||
|
|||||||
+6
-5
@@ -1,13 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "glm/glm.hpp"
|
||||||
|
|
||||||
#include "cbt/scene.hpp"
|
#include "cbt/scene.hpp"
|
||||||
|
|
||||||
#include "cbt/opengl/shader.hpp"
|
|
||||||
#include "cbt/opengl/buffer.hpp"
|
#include "cbt/opengl/buffer.hpp"
|
||||||
|
#include "cbt/opengl/shader.hpp"
|
||||||
#include "cbt/opengl/vao.hpp"
|
#include "cbt/opengl/vao.hpp"
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
namespace cbt::scenes {
|
namespace cbt::scenes {
|
||||||
|
|
||||||
class cube final : public scene {
|
class cube final : public scene {
|
||||||
@@ -15,7 +16,7 @@ public:
|
|||||||
cube();
|
cube();
|
||||||
auto init() -> bool override;
|
auto init() -> bool override;
|
||||||
auto update(float delta_time) -> void override;
|
auto update(float delta_time) -> void override;
|
||||||
auto render() -> void override;
|
auto render(int width, int height) -> void override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
opengl::shader m_prog;
|
opengl::shader m_prog;
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
#include <cstddef>
|
||||||
|
#include <cmath>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "glad/glad.h"
|
||||||
|
#include "glm/gtc/matrix_transform.hpp"
|
||||||
|
#include "glm/gtc/type_ptr.hpp"
|
||||||
|
|
||||||
|
#include "scenes/sphere.hpp"
|
||||||
|
|
||||||
|
namespace cbt::scenes {
|
||||||
|
|
||||||
|
sphere::sphere() {
|
||||||
|
m_start = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sphere::init() -> bool {
|
||||||
|
if (!build_shader()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
build_mesh();
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sphere::update(float) -> void {}
|
||||||
|
|
||||||
|
auto sphere::render(int width, int height) -> void {
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
auto elapsed = std::chrono::duration<float>(now - m_start).count();
|
||||||
|
|
||||||
|
glClearColor(0.15f, 0.15f, 0.2f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
auto aspect = float(width) / float(height);
|
||||||
|
auto proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.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});
|
||||||
|
|
||||||
|
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();
|
||||||
|
glDrawElements(GL_TRIANGLES, m_index_count, GL_UNSIGNED_INT, nullptr);
|
||||||
|
m_vao.unbind();
|
||||||
|
m_prog.unuse();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sphere::build_mesh() -> void {
|
||||||
|
struct vertex {
|
||||||
|
glm::vec3 position;
|
||||||
|
glm::vec3 normal;
|
||||||
|
glm::vec2 uv;
|
||||||
|
glm::vec3 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::uint32_t const div = 32;
|
||||||
|
std::vector<vertex> vertices;
|
||||||
|
std::vector<std::uint32_t> indices;
|
||||||
|
|
||||||
|
// Distinct colors for each cube face so seams are visible
|
||||||
|
glm::vec3 const face_colors[6] = {
|
||||||
|
{1.0f, 0.2f, 0.2f}, // +X red
|
||||||
|
{0.2f, 1.0f, 0.2f}, // -X green
|
||||||
|
{0.2f, 0.2f, 1.0f}, // +Y blue
|
||||||
|
{1.0f, 1.0f, 0.2f}, // -Y yellow
|
||||||
|
{1.0f, 0.2f, 1.0f}, // +Z magenta
|
||||||
|
{0.2f, 1.0f, 1.0f}, // -Z cyan
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate 6 cube faces, each with div x div vertices
|
||||||
|
auto add_face = [&](glm::vec3 const& center, glm::vec3 const& u_axis,
|
||||||
|
glm::vec3 const& v_axis, std::uint32_t face_idx) -> void {
|
||||||
|
for (std::uint32_t i = 0; i < div; ++i) {
|
||||||
|
for (std::uint32_t j = 0; j < div; ++j) {
|
||||||
|
float const s = float(i) / float(div - 1) * 2.0f - 1.0f;
|
||||||
|
float const t = float(j) / float(div - 1) * 2.0f - 1.0f;
|
||||||
|
|
||||||
|
// Position on cube face
|
||||||
|
glm::vec3 pos = center + u_axis * s + v_axis * t;
|
||||||
|
|
||||||
|
// FIX: normalize to project onto unit sphere
|
||||||
|
// (the original nrz.cpp used a broken formula with p=50.0)
|
||||||
|
float const len = glm::length(pos);
|
||||||
|
glm::vec3 normal = pos / len;
|
||||||
|
|
||||||
|
vertices.push_back({normal, normal, {float(i) / float(div - 1), float(j) / float(div - 1)}, face_colors[face_idx]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// +X face (right)
|
||||||
|
add_face(glm::vec3{1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f}, glm::vec3{0.0f, 0.0f, 1.0f}, 0);
|
||||||
|
// -X face (left)
|
||||||
|
add_face(glm::vec3{-1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f}, glm::vec3{0.0f, 0.0f, -1.0f}, 1);
|
||||||
|
// +Y face (top)
|
||||||
|
add_face(glm::vec3{0.0f, 1.0f, 0.0f}, glm::vec3{1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 0.0f, -1.0f}, 2);
|
||||||
|
// -Y face (bottom)
|
||||||
|
add_face(glm::vec3{0.0f, -1.0f, 0.0f}, glm::vec3{1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 0.0f, 1.0f}, 3);
|
||||||
|
// +Z face (front)
|
||||||
|
add_face(glm::vec3{0.0f, 0.0f, 1.0f}, glm::vec3{1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f}, 4);
|
||||||
|
// -Z face (back)
|
||||||
|
add_face(glm::vec3{0.0f, 0.0f, -1.0f}, glm::vec3{-1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f}, 5);
|
||||||
|
|
||||||
|
// Generate indices for each face
|
||||||
|
std::uint32_t offset = 0;
|
||||||
|
for (std::uint32_t face = 0; face < 6; ++face) {
|
||||||
|
for (std::uint32_t i = 0; i < div - 1; ++i) {
|
||||||
|
for (std::uint32_t j = 0; j < div - 1; ++j) {
|
||||||
|
std::uint32_t const a = offset + i * div + j;
|
||||||
|
std::uint32_t const b = offset + (i + 1) * div + j;
|
||||||
|
std::uint32_t const c = offset + (i + 1) * div + j + 1;
|
||||||
|
std::uint32_t const d = offset + i * div + j + 1;
|
||||||
|
|
||||||
|
// Two triangles per quad (consistent winding)
|
||||||
|
indices.push_back(a);
|
||||||
|
indices.push_back(b);
|
||||||
|
indices.push_back(d);
|
||||||
|
|
||||||
|
indices.push_back(b);
|
||||||
|
indices.push_back(c);
|
||||||
|
indices.push_back(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += div * div;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_index_count = static_cast<GLsizei>(indices.size());
|
||||||
|
|
||||||
|
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<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 {
|
||||||
|
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";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "glm/glm.hpp"
|
||||||
|
|
||||||
|
#include "cbt/scene.hpp"
|
||||||
|
#include "cbt/opengl/buffer.hpp"
|
||||||
|
#include "cbt/opengl/shader.hpp"
|
||||||
|
#include "cbt/opengl/vao.hpp"
|
||||||
|
|
||||||
|
namespace cbt::scenes {
|
||||||
|
|
||||||
|
class sphere final : public scene {
|
||||||
|
public:
|
||||||
|
sphere();
|
||||||
|
auto init() -> bool override;
|
||||||
|
auto update(float delta_time) -> void override;
|
||||||
|
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;
|
||||||
|
|
||||||
|
std::chrono::steady_clock::time_point m_start;
|
||||||
|
|
||||||
|
auto build_mesh() -> void;
|
||||||
|
auto build_shader() -> bool;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user