14
\$\begingroup\$

How do i render my game scene into an imgui window?

I want to get from this:

enter image description here

to this :

enter image description here

\$\endgroup\$

2 Answers 2

18
\$\begingroup\$

First of all we need to create what is called Frame Buffer Object, or FBO for short. I'll leave you a great tutorial on (pretty much everything regarding OpenGL)FBOs right here: https://learnopengl.com/#!Advanced-OpenGL/Framebuffers

Now that we created an FBO, we should have a texture. This texture contains the image of what is rendered on the FBO. At this point all we need to do is to "put" this image inside of a dear ImGui Window. Let's see how to do this:

//this is our main game loop
while (!window.Closed())
{
    //we need to clear the window,
    //This is how the clear method is structured:
    //void Window::Clear(float x, float y, float z, bool enableGLDepth,bool clearImGUIframe)const
    //{
    //  //clear the color of the window with an alpha value of 1.0f
    //  glClearColor(x, y, z, 1.0f);    
    //  if (clearImGUIframe)
    //  {
    //      ImGui_ImplGlfwGL3_NewFrame();
    //  }
    //  if (enableGLDepth)
    //  {
    //      //enable the GL_DEPTH_TEST to be able to see 3D object correctly
    //      glEnable(GL_DEPTH_TEST);
    //      //clear both the color buffer bit and the depth buffer bit
    //      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //  }
    //  else
    //  {
    //      //clear just the color buffer bit
    //      glClear(GL_COLOR_BUFFER_BIT);
    //  }
    //}

    window.Clear(0.5f, 0.5f, 0.5f,true,true);

    //switch to our custom FBO
    //window.bindFBO is the same as writing glBindFramebuffer(GL_FRAMEBUFFER,FBO);
    window.bindFBO();
    
    //we need to call clear twice
    window.Clear(1.0f, 1.0f, 1.0f, true, false);
    //Draw our simple triangle
    shader.Use();
    glBindVertexArray(Tvao);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    //switch to the default FBO
    //again, unbindFBO() is the same as glBindFramebuffer(GL_FRAMEBUFFER,0)
    window.unbindFBO();

    //create our ImGui window
    ImGui::Begin("Scene Window");
    //get the mouse position
    ImVec2 pos = ImGui::GetCursorScreenPos();

    //pass the texture of the FBO
    //window.getRenderTexture() is the texture of the FBO
    //the next parameter is the upper left corner for the uvs to be applied at
    //the third parameter is the lower right corner
    //the last two parameters are the UVs
    //they have to be flipped (normally they would be (0,0);(1,1) 
    ImGui::GetWindowDrawList()->AddImage(
        (void *)window.getRenderTexture(), 
ImVec2(ImGui::GetCursorScreenPos()),
        ImVec2(ImGui::GetCursorScreenPos().x + window.getWidth()/2, 
ImGui::GetCursorScreenPos().y + window.getHeight()/2), ImVec2(0, 1), ImVec2(1, 0));

    //we are done working with this window
    ImGui::End();
    //swap the buffers and check for events
    window.Update();
}

after all this you should get this result:

enter image description here

Hope this helped someone! I'll also leave the entire code i am using down here:

Main.cpp

#include "Engine.h"

using namespace Engine;
using namespace graphics;

int main()
{
    Window window("Advent3D", 800,600, false);
    Shader shader("Res/Shaders/Unlit.shader");

    //TRIANGLE CREATION//
    float Tvertices[] = {-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f,  0.5f, 0.0f};
    
    unsigned int Tvbo, Tvao;
    glGenVertexArrays(1, &Tvao);
    glGenBuffers(1, &Tvbo);
    glBindVertexArray(Tvao);
    glBindBuffer(GL_ARRAY_BUFFER, Tvbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Tvertices), Tvertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glBindVertexArray(0);


    while (!window.Closed())
    {
        //we need to clear the window, 
        window.Clear(0.5f, 0.5f, 0.5f,true,true);

        //switch to the custom fbo
        window.bindFBO();
        
        window.Clear(1.0f, 1.0f, 1.0f, true, false);

        shader.Use();
        glBindVertexArray(Tvao);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        //switch to the default fbo
        window.unbindFBO();

        ImGui::Begin("Scene Window");

        ImVec2 pos = ImGui::GetCursorScreenPos();

        ImGui::GetWindowDrawList()->AddImage(
            (void *)window.getRenderTexture(), ImVec2(ImGui::GetCursorScreenPos()),
            ImVec2(ImGui::GetCursorScreenPos().x + window.getWidth()/2, ImGui::GetCursorScreenPos().y + window.getHeight()/2), ImVec2(0, 1), ImVec2(1, 0));

        ImGui::End();

        window.Update();
    }   
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ A gist would be greatly helpful for newcomers. A simple 3D cube in an imgui window. \$\endgroup\$
    – Dilawar
    Commented Nov 20, 2021 at 16:34
0
\$\begingroup\$

The best approach I found so far that integrates well with windows even if they hover above others (in contrast to the original answer) and is also performant when rendering multiple views (in contrast to the accepted answer of using images), is as follows:

int fbX, fbY;
glfwGetFramebufferSize(ImGui::GetCurrentWindowRead(), &fbX, &fbY);

auto viewWin = ImGui::GetCurrentWindowRead();
Eigen::Vector4f viewRect(viewWin->InnerRect.Min.x, viewWin->InnerRect.Min.y, viewWin->InnerRect.GetWidth(), viewWin->InnerRect.GetHeight());
ImDrawList* draw_list = ImGui::GetWindowDrawList();
draw_list->AddCallback([&](const ImDrawList* dl, const ImDrawCmd* dc)
{
    Eigen::Vector4f clip(
        dc->ClipRect.x, dc->ClipRect.y,
        (dc->ClipRect.z-dc->ClipRect.x), (dc->ClipRect.w-dc->ClipRect.y));
    viewRect *= pixelRatio;
    clip *= pixelRatio;
    glViewport(viewRect.x(), fbY-viewRect.y()-viewRect.w(), viewRect.z(), viewRect.w());
    glScissor(clip.x(), fbY-clip.y()-clip.w(), clip.z(), clip.w());
    glClearColor(0.2f, 0.0, 0.2f, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Enable Depth for 3D scene
    glDepthMask(GL_TRUE);
    glClearDepth(0);
    glDepthFunc(GL_GEQUAL);
    glEnable(GL_DEPTH_TEST);

    // TODO: Render 3D scene
}, nullptr);
draw_list->AddCallback(ImDrawCallback_ResetRenderState, nullptr);

The example uses glfw and some eigen but the idea should be clear. Use a callback to render a scene (3D or not) at the right time so the UI elements that should be behind are behind, and the ones ontop are properly ontop. UI is always rendered ontop, so no need to worry about depth buffer, you can use it for your scene or not. The advantage is also that it'll only render the part of the scene that is visible, and you can differentiate between your viewRect and your clipRect.

Original Answer:

A much simpler (and, if you have multiple windows with GL viewports, more performant) solution, is to keep all background of windows with GL ports transparent (and dockspace background, if you use it), and then manually clear the backgrounds (using glViewport and glScissor followed by glClearColor and glClear) using the rectangle of each window (GetWindowPos/GetWindowSize).

Then you can first render your GL part using the same method in the rectangles you want them to be in, and then the UI on top. Looks seamless, and should be much more performant than multiple render passes.

\$\endgroup\$

You must log in to answer this question.