After creating the backbone of our gui last time we will be adding a viewport that shows our OpenGL renderd scene. Using this viewport the user is able to position 3d models and rotate and scale them as wished. Check out the previous posts here:

Rendering using GLFW

This will not be a tutorial about how to use GLFW to render scenes. You can learn that here. I will tell you how to put your rendered image on a imgui window, like this:

We will be creating the middle window with the dragon

The renderer takes in a scene and creates an image every time the render function is called. The image itself is rendered to a framebuffer instead of a GLFW window. This way we can use it in our imgui window.

Render class definition

class RenderManager { public: RenderManager(Window* basewindow); ~RenderManager(); void render(); unsigned int getFrame() { return textureColorbuffer; } void setNewSize(int width, int height); private: int shaderProgram; Window* basewindow; unsigned int framebuffer, textureColorbuffer; int width, height; Shader* shader; };

In the constructor, GLFW and glad are initialized. This is also the place where we call setNewSize for the first time. In this function the framebuffer is set the same way as discribed here. This might seem daunting, but it’s not that difficult if you read carefully. The function setNewSize is also called if our imgui window changes size. Please note that I make use of the camera and shader class discribed in the learnOpenGL tutorials. I acces my camera via a singleton, but you can just add it to the RenderManager class for ease of use.

Render class implementation

RenderManager::RenderManager(Window* basewindow) { this->basewindow = basewindow; ViewportCamera::createInstance(glm::vec3(0.0f, 0.0f, 8.0f), glm::vec3(0.0f, 1.0f, 0.0f)); glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); GLFWmonitor* monitor = glfwGetPrimaryMonitor(); const GLFWvidmode* mode = glfwGetVideoMode(monitor); basewindow->setSize(mode->width, mode->height); basewindow->setWindow(glfwCreateWindow(basewindow->getWidth(), basewindow->getHeight(), "Trixs animator", NULL, NULL)); if (basewindow->getWindow() == NULL) { glfwTerminate(); return; } glfwMakeContextCurrent(basewindow->getWindow()); auto Resizefunc = [](GLFWwindow* w, int width, int height) { static_cast<RenderManager*>(glfwGetWindowUserPointer(w))->framebufferSizeCallback(width, height); }; glfwSetFramebufferSizeCallback(basewindow->getWindow(), Resizefunc); auto func = [](GLFWwindow* w, double x, double y) { static_cast<RenderManager*>(glfwGetWindowUserPointer(w))->cursor_position_callback(w, x, y); }; glfwSetCursorPosCallback(basewindow->getWindow(), func); // glad: load all OpenGL function pointers // --------------------------------------- if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { return; } shader = new Shader("Shader.vs", "Shader.fs"); glEnable(GL_DEPTH_TEST); setNewSize(basewindow->getWidth(), basewindow->getHeight()); } RenderManager::~RenderManager() { glfwTerminate(); } void RenderManager::render() { processInput(basewindow->getWindow()); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glClearColor(0.2f, 0.2f, 0.4f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shader->use(); glm::mat4 view = glm::mat4(1.0f); glm::mat4 projection = glm::mat4(1.0f); projection = glm::perspective(glm::radians(90.0f), (float)width / (float)height, 0.1f, 100.0f); view = glm::lookAt(ViewportCamera::getInstance().Position, ViewportCamera::getInstance().Position + ViewportCamera::getInstance().Front, ViewportCamera::getInstance().Up); glUniformMatrix4fv(glGetUniformLocation(shader->ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); glUniformMatrix4fv(glGetUniformLocation(shader->ID, "view"), 1, GL_FALSE, glm::value_ptr(view)); shader->setVec3("lightPos", 1.2f, 1.0f, 2.0f); shader->setVec3("viewPos", 0.0f, 0.5f, -3.0f); shader->setVec3("lightColor", 1.0f, 1.0f, 1.0f); unsigned int transformLoc = glGetUniformLocation(shader->ID, "model"); //model transform glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(glm::mat4(1.0f))); glBindVertexArray(VAO); glDrawElements(GL_LINES, 22, GL_UNSIGNED_INT, 0); glBindVertexArray(0); std::vector<Hittable*>* world = MainManager::getInstance().getProject()->getCurrentScene()->getObjects(); for (auto i = 0; i < world->size(); i++) { glm::mat4 transform = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first Transform* t = world->at(i)->getTransform(); glm::vec3 pos(t->getPos().x(), t->getPos().y() * -1, t->getPos().z()); glm::vec3 rot(t->getRot().x(), t->getRot().y(), t->getRot().z()); glm::vec3 scale(t->getScale().x(), t->getScale().y(), t->getScale().z()); transform = glm::translate(transform, pos); transform = glm::rotate(transform, glm::radians(rot.x), glm::vec3(1.0f, 0.0f, 0.0f)); transform = glm::rotate(transform, glm::radians(rot.y), glm::vec3(0.0f, 1.0f, 0.0f)); transform = glm::rotate(transform, glm::radians(rot.z), glm::vec3(0.0f, 0.0f, 1.0f)); transform = glm::scale(transform, scale); glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform)); shader->setVec3("objectColor", world->at(i)->getMaterial()->getColor().r(), world->at(i)->getMaterial()->getColor().g(), world->at(i)->getMaterial()->getColor().b() ); world->at(i)->draw(); } glBindFramebuffer(GL_FRAMEBUFFER, 0); } bool RenderManager::WindowShouldClose() { return glfwWindowShouldClose(basewindow->getWindow()); } GLFWwindow * RenderManager::getWindow() { return basewindow->getWindow(); } void RenderManager::setNewSize(int width, int height) { glViewport(0, 0, width, height); //aspect ratio this->width = width; this->height = height; //glDeleteFramebuffers(1, &framebuffer); // framebuffer configuration // ------------------------- framebuffer = 0; glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glGenTextures(1, &textureColorbuffer); glBindTexture(GL_TEXTURE_2D, textureColorbuffer); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0); // create a renderbuffer object for depth and stencil attachment (we won't be sampling these) unsigned int rbo; glGenRenderbuffers(1, &rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); // use a single renderbuffer object for both a depth AND stencil buffer. glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // now actually attach it // now that we actually created the framebuffer and added all attachments we want to check if it is actually complete now if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl; glBindFramebuffer(GL_FRAMEBUFFER, 0); }

I am using these shaders

Vertex shader

#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; out vec3 FragPos; out vec3 Normal; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { FragPos = vec3(model * vec4(aPos, 1.0)); Normal = mat3(transpose(inverse(model))) * aNormal; gl_Position = projection * view * vec4(FragPos, 1.0); }

Fragment shader

#version 330 core out vec4 FragColor; in vec3 Normal; in vec3 FragPos; uniform vec3 lightPos; uniform vec3 viewPos; uniform vec3 lightColor; uniform vec3 objectColor; void main() { // ambient float ambientStrength = 0.1; vec3 ambient = ambientStrength * lightColor; // diffuse vec3 norm = normalize(Normal); vec3 lightDir = normalize(lightPos - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = diff * lightColor; // specular float specularStrength = 0.5; vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); vec3 specular = specularStrength * spec * lightColor; vec3 result = (ambient + diffuse + specular) * objectColor; FragColor = vec4(result, 1.0); }

In the Mesh class we created in part 2 of this devlog we will add the following functions.:

bool Mesh::init() { //init opengl buffers etc. glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glGenBuffers(1, &EBO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW); // glBufferData(GL_ARRAY_BUFFER, vertexNormals.size() * sizeof(vec3), &vertexNormals[0], GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW); //vertex glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0); //normal glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Vertex::Normal)); // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind glBindBuffer(GL_ARRAY_BUFFER, 0); // remember: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound. //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. glBindVertexArray(0); return true; } void Mesh::draw() const { //opengl draw glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, indices.size()); //glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0); glBindVertexArray(0); }

The init function is called at the end of the mesh constructor. This initializes all the buffers needed to render the mesh. These buffers are added to the mesh class definition:

unsigned int VAO, VBO, EBO;

Scene management is just a list of all meshes in this case, so I leave that to you. In the render function loop over these meshes and call the draw function on them.

Imgui viewport window

The viewport window is actually very easy to create now. It’s just an imgui window that inherits from ImGuiWindow and overrides the begin and update functions:

class IGViewPortWindow : public ImGuiWindow { public: IGViewPortWindow(Window* window) { this->window = window; this->renderman = new RenderManager(window); size.x = -1; size.y = -1; } void update() override; bool begin(std::string name) override; void setRenderManager(RenderManager* renderman) { this->renderman = renderman; } private: Window* window; RenderManager* renderman; ImVec2 size; }; inline bool IGViewPortWindow::begin(std::string name) { return ImGui::Begin(name.c_str(), &show, ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse); } void IGViewPortWindow::update() { if (renderman->WindowShouldClose()) { MainManager::getInstance().stop(); return; //stop application } if (show) { if (!begin("Viewport")) { end(); } else { if (size.x != ImGui::GetWindowWidth() || size.y != ImGui::GetWindowHeight()) { size.x = ImGui::GetWindowWidth(); size.y = ImGui::GetWindowHeight(); renderman->setNewSize(size.x, size.y); } renderman->render(); auto tex = renderman->getFrame(); ImGui::Image((void *)tex, size); end(); } } }

I have overwritten the begin function to add some windowflags to my liking. I disabled scrolling because that can get very annoying. In the constructor a new rendermanager is created. By setting the size of the window to -1 it is certain that the viewbuffer will be updated to the right size from frame 1. The update function calls the render function, gets the new frame into a texture and this is passed to an Imgui image as a void pointer. Just before we render we check if the imgui window has been resized. If so we update the framebuffers size.

Add this window to the windowmanager and it’s done! I hope you learned something and if you have any questions please ask them in the comments below.

Ruurd

Want to learn more about openGL?

Please note this is an affiliate link, all earnings are used for hosting and improving this website

This book is all you need to start openGL development.I own this book and always use it when developing opengl applications. It contains lots of examples and use-cases! Not only that, it also explains basic rendering techniques and key topics like performance and cross platform development.