#define STAPLEGL_DEBUG
#include "glad.h"
#include <GLFW/glfw3.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <iostream>
#include <span>
#include <utility>
#include "box.h"
#include "screen_quad.h"
#include "teapot_data.h"
#include "glm.hpp"
#include "gtc/matrix_transform.hpp"
#include "gtc/type_ptr.hpp"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STAPLEGL_TEAPOT_LOW_MSAA
#ifdef STAPLEGL_TEAPOT_LOW_MSAA
#else
#endif
void GLAPIENTRY
GLenum type,
GLuint id [[maybe_unused]],
GLenum severity,
GLsizei length [[maybe_unused]],
const GLchar* message,
const void* userParam [[maybe_unused]])
{
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION || type == GL_DEBUG_TYPE_PERFORMANCE || type == GL_DEBUG_TYPE_OTHER) {
return;
}
fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x,\nmessage = %s\n",
(type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
type, severity, message);
fprintf(stderr, "source = 0x%x, id = %d\n", source, id);
}
{
uint32_t levels = 1;
while (width > 2 && height > 2) {
width /= 2;
height /= 2;
levels++;
}
return levels;
}
{
std::string_view const hello_message {
"Hello! This is a more complex example of staplegl usage, featuring the Utah Teapot model.\n"
"Press the U and D keys to increase (U) and decrease (D) the luminosity of the light source.\n"
"Play around with them to observe how the bloom effect changes."
};
std::cout << hello_message << std::endl;
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
if (window == nullptr) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
if (gladLoadGLLoader(reinterpret_cast<GLADloadproc>(glfwGetProcAddress)) == 0) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
glEnable(GL_DEPTH_TEST);
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_CULL_FACE);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
#ifdef STAPLEGL_DEBUG
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
std::clog << "Printing OpenGL version info:" << std::endl;
std::clog << "OpenGL version: " << glGetString(GL_VERSION) << std::endl;
std::clog << "OpenGL vendor: " << glGetString(GL_VENDOR) << std::endl;
std::clog << "OpenGL renderer: " << glGetString(GL_RENDERER) << std::endl;
#endif
skybox_shader.upload_uniform1i("skybox", 0);
teapot_shader.bind();
teapot_shader.upload_uniform1i("environment", 0);
passthrough_shader.bind();
passthrough_shader.upload_uniform1i("scene", 1);
tonemap_shader.bind();
tonemap_shader.upload_uniform1i("scene", 1);
tonemap_shader.upload_uniform1i("bloom", 2);
downsample_shader.bind();
downsample_shader.upload_uniform1i("scene", 1);
upsample_shader.bind();
upsample_shader.upload_uniform1i("scene", 1);
std::span<const float> {},
.
min_filter = GL_LINEAR, .mag_filter = GL_LINEAR, .clamping = GL_CLAMP_TO_EDGE },
};
std::span<const float> {},
.
min_filter = GL_LINEAR, .mag_filter = GL_LINEAR, .clamping = GL_CLAMP_TO_EDGE }
};
for (int i = 0; auto& tex : pyramid_textures) {
std::span<const float> {},
.
min_filter = GL_LINEAR, .mag_filter = GL_LINEAR, .clamping = GL_CLAMP_TO_EDGE }
};
i++;
}
{ teapot_indices }
};
std::vector<unsigned int> skybox_indices(SKYBOX_VERTS / 3);
std::generate(skybox_indices.begin(), skybox_indices.end(),
[n = 0]() mutable { return n++; });
glm::mat4 const model = glm::mat4(1.0F);
{ u_type::mat4, "projection" },
{ u_type::mat4, "view" },
{ u_type::mat4, "model" },
{ u_type::vec4, "camera_pos" }
};
{ u_type::vec4, "light_pos" },
{ u_type::vec4, "light_color" },
{ u_type::vec4, "light_attenuation" },
{ u_type::vec2, "light_intensities" }
};
const glm::vec4 light_pos { 1.0F, 1.0F, 10.0F, 1.0F };
const glm::vec4 light_color { 0.9333F, 0.5098, 0.9333F, 1.0F };
light_block.set_attribute_data(std::span { glm::value_ptr(light_pos), 4 }, "light_pos");
light_block.set_attribute_data(std::span { glm::value_ptr(light_color), 4 }, "light_color");
light_block.set_attribute_data(std::span { glm::value_ptr(glm::vec4(1.0F, 0.22F, 0.20F, 0.0F)), 4 }, "light_attenuation");
light_block.set_attribute_data(std::span { glm::value_ptr(glm::vec2(
luminosity, 1.2F)), 2 },
"light_intensities");
light_block.unbind();
{ u_type::vec4, "material_color" },
{ u_type::float32, "material_shininess" },
{ u_type::float32, "material_roughness" }
};
const glm::vec4 teapot_color { 0.51F, 0.55F, 0.66F, 1.0F };
const float teapot_shininess = 32.0F;
const float teapot_roughness = 0.80F;
material_block.set_attribute_data(std::span { glm::value_ptr(teapot_color), 4 }, "material_color");
material_block.set_attribute_data(std::span { &teapot_shininess, 1 }, "material_shininess");
material_block.set_attribute_data(std::span { &teapot_roughness, 1 }, "material_roughness");
material_block.unbind();
std::array<std::string, 6> const faces {
"./assets/skybox/right.jpg", "./assets/skybox/left.jpg",
"./assets/skybox/top.jpg", "./assets/skybox/bottom.jpg",
"./assets/skybox/front.jpg", "./assets/skybox/back.jpg"
};
std::array<std::span<std::byte>, 6> cube_data;
std::int32_t width = 0, height = 0, nrChannels = 0;
int i = 0;
for (auto& face : faces) {
auto* data = reinterpret_cast<std::byte*>(
stbi_load(face.c_str(), &width, &height, &nrChannels, 0));
if (data == nullptr) {
std::cout << "Failed to load texture" << std::endl;
return -1;
}
cube_data[i++] = std::span<std::byte> { data, static_cast<size_t>(width * height * nrChannels) };
}
cube_data, { width, height },
{ .internal_format = GL_SRGB8, .format = GL_RGB, .datatype = GL_UNSIGNED_BYTE },
{ .min_filter = GL_LINEAR, .mag_filter = GL_LINEAR, .clamping = GL_CLAMP_TO_EDGE },
true
};
for (auto& face : cube_data) {
stbi_image_free(face.data());
}
skybox.set_unit(0);
glClearColor(0.F, 0.F, 0.F, 1.0F);
while (glfwWindowShouldClose(window) == 0) {
light_block.bind();
light_block.set_attribute_data(std::span { glm::value_ptr(glm::vec2(
luminosity, 1.2F)), 2 },
"light_intensities");
const auto time = static_cast<float>(glfwGetTime());
const float radius = 4.0F;
const float slow_factor = 0.25F;
const float camX = sin(time * slow_factor) * radius;
const float camZ = cos(time * slow_factor) * radius;
const float camY = sin(time * slow_factor) + 0.5F;
glm::vec4 camera_pos { camX, camY, camZ, 1.0F };
glm::mat4 view = glm::lookAt(glm::vec3(camera_pos), glm::vec3(0.0F, 0.0F, 0.0F),
glm::vec3(0.0F, 1.0F, 0.0F));
glm::mat4 projection = glm::perspective(glm::radians(90.0F),
aspect_ratio, 0.01F, 100.0F);
camera_block.bind();
camera_block.set_attribute_data(
std::span { glm::value_ptr(camera_pos), 4 }, "camera_pos");
camera_block.set_attribute_data(std::span { glm::value_ptr(view), 16 }, "view");
camera_block.set_attribute_data(std::span { glm::value_ptr(projection), 16 }, "projection");
msaa_fbo.bind();
msaa_fbo.set_texture(msaa_color);
msaa_fbo.set_renderbuffer(
if (!msaa_fbo.assert_completeness()) [[unlikely]] {
std::cerr << "Framebuffer not complete, line: " << __LINE__ << std::endl;
return EXIT_FAILURE;
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
skybox_shader.bind();
glm::mat4 skybox_mat = glm::scale(glm::mat4(1.0F), glm::vec3(50.0F, 50.0F, 50.0F));
camera_block.set_attribute_data(std::span { glm::value_ptr(skybox_mat), 16 }, "model");
glDepthMask(GL_FALSE);
glDrawElements(GL_TRIANGLES, skybox_VAO.
index_data().count(), GL_UNSIGNED_INT,
nullptr);
glDepthMask(GL_TRUE);
glm::mat4 light_mat = glm::translate(glm::mat4(1.0F), glm::vec3(light_pos));
camera_block.set_attribute_data(std::span { glm::value_ptr(light_mat), 16 }, "model");
light_shader.bind();
glFrontFace(GL_CW);
glDrawElements(GL_TRIANGLES, skybox_VAO.
index_data().count(), GL_UNSIGNED_INT,
nullptr);
glFrontFace(GL_CCW);
glm::mat4 model_mat = glm::scale(model, glm::vec3(1.0F, -1.0F, 1.0F));
camera_block.set_attribute_data(std::span { glm::value_ptr(model_mat), 16 }, "model");
teapot_shader.bind();
glDrawElements(GL_TRIANGLES, VAO.
index_data().count(), GL_UNSIGNED_INT,
nullptr);
post_fbo.bind();
post_fbo.set_texture(hdr_color);
post_fbo.bind();
passthrough_shader.bind();
hdr_color.set_unit(1);
post_fbo.set_texture(pyramid_textures[0], 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
downsample_shader.bind();
for (size_t i = 0; i < pyramid_textures.size() - 1; i++) {
auto& draw_source = pyramid_textures[i];
auto& draw_target = pyramid_textures[i + 1];
auto const& t_res = draw_target.get_resolution();
draw_source.set_unit(1);
post_fbo.set_texture(draw_target, 0);
post_fbo.set_viewport(
{ t_res.width,
t_res.height });
downsample_shader.upload_uniform2f(
"uResolution", static_cast<float>(t_res.width),
static_cast<float>(t_res.height));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
glBlendEquation(GL_FUNC_ADD);
upsample_shader.bind();
for (int i = pyramid_textures.size() - 1; i > 0; i--) {
auto& draw_source = pyramid_textures[i];
auto& draw_target = pyramid_textures[i - 1];
draw_source.set_unit(1);
post_fbo.set_texture(draw_target, 0);
post_fbo.set_viewport(
{ draw_target.get_resolution().width,
draw_target.get_resolution().height });
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
glDisable(GL_BLEND);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
tonemap_shader.bind();
hdr_color.set_unit(1);
pyramid_textures[0].set_unit(2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, 1);
}
if (glfwGetKey(window, GLFW_KEY_U) == GLFW_PRESS) {
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
}
if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) {
static bool wireframe = false;
wireframe = !wireframe;
glPolygonMode(GL_FRONT_AND_BACK, wireframe ? GL_LINE : GL_FILL);
}
}
{
glViewport(0, 0, width, height);
aspect_ratio =
static_cast<float>(width) /
static_cast<float>(height);
}
void processInput(GLFWwindow *window)
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
void GLAPIENTRY MessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam)
Cube map texture wrapper.
Framebuffer Object (FBO) wrapper.
static void bind_default()
Bind the default framebuffer.
static void transfer_data(framebuffer const &src, framebuffer const &dst, resolution res)
Transfer the contents of a framebuffer to another.
Element Buffer Object (EBO) wrapper.
void bind() const
Bind the shader program.
Vertex Array Object (VAO) wrapper.
void bind() const
Bind the vertex array object.
void set_index_buffer(index_buffer &&ibo)
Set the index buffer object.
static void unbind()
Unbind the vertex array object.
constexpr auto index_data() -> index_buffer &
Get the index buffer object.
auto add_vertex_buffer(vertex_buffer &&vbo) -> vertex_array::iterator_t
Add a vertex buffer to the vertex array object.
Vertex Buffer Object (VBO) wrapper.
void set_layout(const vertex_buffer_layout &layout)
Set the layout object.
@ ATTACH_DEPTH_STENCIL_BUFFER
StapleGL, a C++20 wrapper for OpenGL.
A struct that represents an image's dimensions.
OpenGL texture details relating to the color format and data type.
std::int32_t internal_format
OpenGL texture details relating to filtering and clamping of the image.
constexpr auto MSAA_SAMPLES
constexpr auto calc_pyramid_levels(uint32_t width, uint32_t height) -> uint32_t