Hello folks,
I’m currently working on OpenGL application with GLFW, and having difficulties to make multiple viewports work.
Below is my code, I’m getting first window work only although I’m trying to draw same scene across all viewports.
Eventually I will separate work for each views (Top/Bottom, Perspective, Front/Back, Left/Right), but first thing is to make the all viewports works.
Can anyone give me some advice how to fix my problem?
Thank you so much!
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>
#include "GLFW/glfw3native.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <filesystem.h>
#include <shader.h>
#include <camera.h>
#include <model.h>
#include "windows.h"
#include <iostream>
using namespace std;
using namespace glm;
#define MAX_LOADSTRING 100
#define GLFW_EXPOSE_NATIVE_WGL
#define GLFW_EXPOSE_NATIVE_WIN32
#define VIEWPORT_START -1
#define VIEWPORT_TOPLEFT 0
#define VIEWPORT_TOPRIGHT 1
#define VIEWPORT_BOTTOMLEFT 2
#define VIEWPORT_BOTTOMRIGHT 3
#define VIEWPORT_END 4
#define SYSTEM_EXIT_NORMAL 0
#define SYSTEM_EXIT_MAIN_WINDOW_CREATION_FAIL -1
#define SYSTEM_EXIT_GLVIEW_CREATION_FAIL -2
// RGBA
struct RGBA {
GLdouble r, g, b, a;
RGBA(GLdouble _r = 0.0, GLdouble _g = 0.0, GLdouble _b = 0.0, GLdouble _a = 1.0) : r(_r), g(_g), b(_b), a(_a) {}
};
// Application
typedef struct { // Contains information vital to a application
char className[1024]; // Window class name
HINSTANCE hInstance; // Application instance
} Application;
// Window information
struct GL_Window { // Contains information vital to a OpenGL Window
HWND hWnd; // Window handle
GLFWwindow* glfwHandle; // GLFW window handle
char className[256]; // Window class name
int x, y; // x, y Position
int Width, Height; // Width, Height
int viewport; // Viewport
RGBA bgColor; // Background color
mat4 view; // Viewport size information
mat4 mvMatrix; // Model view matrix
mat4 prjMatrix; // Projection view matrix
Shader shader, shaderLight, shaderBlur, shaderBloomFinal; // Shaders
};
// Settings
int DisplayWidth, DisplayHeight;
int WindowWidth, WindowHeight;
int ViewWidth, ViewHeight;
bool bloom = true;
bool bloomKeyPressed = false;
float exposure = 1.0f;
// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 5.0f));
float lastX = (float)WindowWidth / 2.0;
float lastY = (float)WindowHeight / 2.0;
bool firstMouse = true;
// Timing
float deltaTime = 0.0f;
float lastFrame = 0.0f;
HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];
unsigned int woodTexture;
unsigned int containerTexture;
unsigned int hdrFBO;
vector<glm::vec3> lightPositions;
vector<glm::vec3> lightColors;
unsigned int pingpongFBO[2];
unsigned int pingpongColorbuffers[2];
unsigned int colorBuffers[2];
int CreateGLWindows(GL_Window& glWindow);
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow* window);
unsigned int loadTexture(const char* path, bool gammaCorrection);
void renderQuad();
void renderCube();
void DrawScene(GL_Window glWindow);
bool RegisterWindowClass(Application* application);
bool InitInstance(HINSTANCE hInstance, int nWidth, int nHeight);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void InitGLWindows();
HWND mainHwnd;
HMENU mainMenu;
Application application;
MSG msg; // Window message
GL_Window glWindows[4];
// Main window procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
// Main window callback...
return 0;
}
// Register a window
bool RegisterWindowClass(Application* application) {
// Register main window...
return true;
}
// Initialize window
bool InitInstance(HINSTANCE hInstance, int nWidth, int nHeight) {
// Initialize main window...
return true;
}
// Create GLFW windows
int CreateGLWindows(GL_Window& glWindow) {
// GLFW: Initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
glfwWindowHint(GLFW_DECORATED, GL_FALSE);
// GLFW window creation
glWindow.glfwHandle = glfwCreateWindow(glWindow.Width, glWindow.Height, glWindow.className, NULL, NULL);
// Get window handle
glWindow.hWnd = glfwGetWin32Window(glWindow.glfwHandle);
// Failed to create window?
if (glWindow.glfwHandle == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(glWindow.glfwHandle);
// Register callbacks
glfwSetFramebufferSizeCallback(glWindow.glfwHandle, framebuffer_size_callback);
glfwSetCursorPosCallback(glWindow.glfwHandle, mouse_callback);
glfwSetScrollCallback(glWindow.glfwHandle, scroll_callback);
// Refine window style
long style = GetWindowLong(glWindow.hWnd, GWL_STYLE);
style &= ~(WS_POPUP | WS_CAPTION); // remove popup style
style |= WS_CHILD | CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
SetWindowLong(glWindow.hWnd, GWL_STYLE, style);
// Tell GLFW to capture our mouse
glfwSetInputMode(glWindow.glfwHandle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
// glfwSetInputMode(glWindow.glfwHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// GLAD: Load all OpenGL function pointers
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// Configure global opengl state
glEnable(GL_DEPTH_TEST);
// Build and compile shaders
glWindow.shader = Shader("bloom.vs", "bloom.fs");
glWindow.shaderLight = Shader("bloom.vs", "light_box.fs");
glWindow.shaderBlur = Shader("blur.vs", "blur.fs");
glWindow.shaderBloomFinal = Shader("bloom_final.vs", "bloom_final.fs");
// Load textures
woodTexture = loadTexture(FileSystem::getPath("resources/textures/wood.png").c_str(), true); // Loading the texture as an SRGB texture
containerTexture = loadTexture(FileSystem::getPath("resources/textures/container2.png").c_str(), true); // Loading the texture as an SRGB texture
// Configure (floating point) framebuffers
glGenFramebuffers(1, &hdrFBO);
glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
// Create 2 floating point color buffers (1 for normal rendering, other for brightness threshold values)
glGenTextures(2, colorBuffers);
for (unsigned int i = 0; i < 2; i++) {
glBindTexture(GL_TEXTURE_2D, colorBuffers[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, glWindow.Width, glWindow.Height, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// We clamp to the edge as the blur filter would otherwise sample repeated texture values!
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Attach texture to framebuffer
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0);
}
// Create and attach depth buffer (renderbuffer)
unsigned int rboDepth;
glGenRenderbuffers(1, &rboDepth);
glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, glWindow.Width, glWindow.Height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);
// Tell OpenGL which color attachments we'll use (of this framebuffer) for rendering
unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(2, attachments);
// Finally check if framebuffer is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "Framebuffer not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Ping-pong-framebuffer for blurring
glGenFramebuffers(2, pingpongFBO);
glGenTextures(2, pingpongColorbuffers);
for (unsigned int i = 0; i < 2; i++) {
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);
glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, glWindow.Width, glWindow.Height, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// We clamp to the edge as the blur filter would otherwise sample repeated texture values!
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongColorbuffers[i], 0);
// Also check if framebuffers are complete (no need for depth buffer)
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "Framebuffer not complete!" << std::endl;
}
// Lighting info, positions
lightPositions.push_back(glm::vec3(0.0f, 0.5f, 1.5f));
// Colors
lightColors.push_back(glm::vec3(5.0f, 5.0f, 5.0f));
// Shader configuration
glWindow.shader.use();
glWindow.shader.setInt("diffuseTexture", 0.1);
glWindow.shaderBlur.use();
glWindow.shaderBlur.setInt("image", 0);
glWindow.shaderBloomFinal.use();
glWindow.shaderBloomFinal.setInt("scene", 0);
glWindow.shaderBloomFinal.setInt("bloomBlur", 1);
// Attach to parnet window
SetParent(glWindow.hWnd, mainHwnd);
// Change the window position
glfwSetWindowPos(glWindow.glfwHandle, glWindow.x, glWindow.y);
// Show window
glfwShowWindow(glWindow.glfwHandle);
// Set clear color
glClearColor(glWindow.bgColor.r, glWindow.bgColor.g, glWindow.bgColor.b, glWindow.bgColor.a);
return 0;
}
void DrawScene(GL_Window glWindow) {
if (!glfwWindowShouldClose(glWindow.glfwHandle)) {
// Change the calling thread's current rendering context
glfwMakeContextCurrent(glWindow.glfwHandle);
// Per-frame time logic
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// Input
processInput(glWindow.glfwHandle);
// Render
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 1. Render scene into floating point framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glWindow.prjMatrix = glm::perspective(glm::radians(camera.Zoom), (float)glWindow.Width / (float)glWindow.Height, 0.1f, 100.0f);
glWindow.view = camera.GetViewMatrix();
glWindow.mvMatrix = glm::mat4(1.0f);
glWindow.shader.use();
glWindow.shader.setMat4("projection", glWindow.prjMatrix);
glWindow.shader.setMat4("view", glWindow.view);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, woodTexture);
// Set lighting uniforms
glWindow.shader.setVec3("lights[" + std::to_string(0) + "].Position", lightPositions[0]);
glWindow.shader.setVec3("lights[" + std::to_string(0) + "].Color", lightColors[0]);
glWindow.shader.setVec3("viewPos", camera.Position);
// Create one large cube that acts as the floor
glWindow.mvMatrix = glm::mat4(1.0f);
glWindow.mvMatrix = glm::translate(glWindow.mvMatrix, glm::vec3(0.0f, -1.0f, 0.0));
glWindow.mvMatrix = glm::scale(glWindow.mvMatrix, glm::vec3(12.5f, 0.5f, 12.5f));
glWindow.shader.setMat4("model", glWindow.mvMatrix);
renderCube();
// Finally show all the light sources as bright cubes
glWindow.shaderLight.use();
glWindow.shaderLight.setMat4("projection", glWindow.prjMatrix);
glWindow.shaderLight.setMat4("view", glWindow.view);
glWindow.mvMatrix = glm::mat4(1.0f);
glWindow.mvMatrix = glm::translate(glWindow.mvMatrix, glm::vec3(lightPositions[0]));
glWindow.mvMatrix = glm::scale(glWindow.mvMatrix, glm::vec3(0.25f));
glWindow.shaderLight.setMat4("model", glWindow.mvMatrix);
glWindow.shaderLight.setVec3("lightColor", lightColors[0]);
renderCube();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 2. Blur bright fragments with two-pass Gaussian Blur
bool horizontal = true, first_iteration = true;
glWindow.shaderBlur.use();
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
glWindow.shaderBlur.setInt("horizontal", horizontal);
// Bind texture of other framebuffer (or scene if first iteration)
glBindTexture(GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongColorbuffers[!horizontal]);
renderQuad();
horizontal = !horizontal;
if (first_iteration)
first_iteration = false;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 3. Now render floating point color buffer to 2D quad and tonemap HDR colors to default framebuffer's (clamped) color range
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glWindow.shaderBloomFinal.use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, colorBuffers[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[!horizontal]);
glWindow.shaderBloomFinal.setInt("bloom", bloom);
glWindow.shaderBloomFinal.setFloat("exposure", exposure);
renderQuad();
// Swap buffers
glfwSwapBuffers(glWindow.glfwHandle);
}
}
// Renders a 1x1 3D cube in NDC.
unsigned int cubeVAO = 0;
unsigned int cubeVBO = 0;
void renderCube() {
// Render cube...
}
// Renders a 1x1 XY quad in NDC
unsigned int quadVAO = 0;
unsigned int quadVBO;
void renderQuad() {
// Render quad...
}
// Process all input: Query GLFW whether relevant keys are pressed/released this frame and react accordingly
void processInput(GLFWwindow* window) {
// Keyboard inputs handling...
}
// GLFW: Whenever the window size changed (by OS or user resize) this callback function executes
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
// GLFW: Whenever the mouse moves, this callback is called
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
// Mouse movement events handling...
}
// GLFW: Whenever the mouse scroll wheel scrolls, this callback is called
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
// Mouse scroll events handling...
}
// Utility function for loading a 2D texture from file
unsigned int loadTexture(char const* path, bool gammaCorrection) {
unsigned int textureID;
// Load textures...
return textureID;
}
// Initialize GL Windows
void InitGLWindows() {
// Viewport initialization...
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_POINT3D, szWindowClass, MAX_LOADSTRING);
// Load accelerators
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_POINT3D));
// Fill Out Application Data
sprintf(application.className, "%s", "Point3D");
application.hInstance = hInstance;
// Window width & height
WindowWidth = 1920;
WindowHeight = 1080;
// Get current display size
DisplayWidth = GetSystemMetrics(SM_CXFULLSCREEN);
DisplayHeight = GetSystemMetrics(SM_CYFULLSCREEN);
// Register class for main window
if (RegisterWindowClass(&application) == false)
return SYSTEM_EXIT_MAIN_WINDOW_CREATION_FAIL;
// Initialize window
if (InitInstance(hInstance, WindowWidth, WindowHeight) == true) {
// Initialize GL Windows
InitGLWindows();
// Create GL Windows
for (int i = VIEWPORT_TOPLEFT; i < VIEWPORT_END; i++) {
if (CreateGLWindows(glWindows[i]) != 0)
return SYSTEM_EXIT_GLVIEW_CREATION_FAIL;
}
}
// Message pump
while (GetMessage(&msg, nullptr, 0, 0)) {
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
// Main loop
for (;;) {
for (int x = VIEWPORT_TOPLEFT; x < VIEWPORT_END; x++) {
DrawScene(glWindows[x]);
}
glfwWaitEvents();
}
}
}
return (int)msg.wParam;
}