SpaiR / imgui-java

JNI based binding for Dear ImGui
MIT License
547 stars 90 forks source link

Bug: OpenGL Rendering in Imgui #200

Open Brohammer5 opened 9 months ago

Brohammer5 commented 9 months ago

Version

1.86.4

What happened?

I am trying to render an OpenGL scene into an ImGui Window. How would I achieve this as there seems to be no documentation on this repository? I have only seen C++ examples to implement this.

Reproduction

No Documentation

Relevant log output

No response

blockout22 commented 8 months ago

You can render your scene onto ImGui using ImGui.Image(); this requires a Frame Buffer which takes in the texture ID of your Frame Buffer as the first argument

ImGui.image(framebuffer.getTextureId(), regionAvail.x, regionAvail.y, 0, 1, 1, 0);

you can dig around my code below for a better example

https://github.com/blockout22/FusionCoreEditor/blob/686e59f7576694e8c5be2737892bd5d99736c942/src/main/java/fusion/core/editor/Viewport.java#L340

Displee commented 5 months ago

Here's how I did it for threekt:

package com.displee.imgui.threekt.texture

import org.lwjgl.opengl.GL30.*
import org.lwjgl.opengl.GL32.GL_PROGRAM_POINT_SIZE
import java.nio.ByteBuffer

class ThreeKtTexture {

    var texture_id = -1
    var FBO = -1
    var RBO = -1

    var width = 0
    var height = 0

    fun create_framebuffer(width: Int, height: Int)
    {
        this.width = width
        this.height = height
        // threekt defaults
        glEnable(GL_PROGRAM_POINT_SIZE)
        glEnable(GL_POINT_SPRITE)

        FBO = glGenFramebuffers()
        glBindFramebuffer(GL_FRAMEBUFFER, FBO)

        texture_id = glGenTextures()
        glBindTexture(GL_TEXTURE_2D, texture_id)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null as ByteBuffer?)
        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, texture_id, 0)

        RBO = glGenRenderbuffers()
        glBindRenderbuffer(GL_RENDERBUFFER, RBO)
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height)
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, RBO)

//        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
//            std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!\n";

        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glBindTexture(GL_TEXTURE_2D, 0)
        glBindRenderbuffer(GL_RENDERBUFFER, 0)
    }

    // here we bind our framebuffer
    fun bind_framebuffer()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, FBO)
    }

    // here we unbind our framebuffer
    fun unbind_framebuffer()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
    }

    // and we rescale the buffer, so we're able to resize the window
    fun rescale_framebuffer(width: Int, height: Int)
    {
        this.width = width
        this.height = height
        glBindTexture(GL_TEXTURE_2D, texture_id)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null as ByteBuffer?)
        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, texture_id, 0)

        glBindRenderbuffer(GL_RENDERBUFFER, RBO)
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height)
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, RBO)
    }

}

Then in your ImGui window:

            ImGui.text("This a demo for ImGui Canvas editor")
            ImGui.sameLine()
            ImGui.text("Mouse Left: drag to add lines,\nMouse Right: drag to scroll, click for context menu.")

            if (firstFrame) {
                initThreeKt()
                firstFrame = false
            }

            ImGui.image(threektTexture.texture_id, threektTexture.width.toFloat(), threektTexture.height.toFloat(), 0F, 1F, 1F, 0F)

            controlsEventSource.handleMouseEvents()

            val windowSize = ImGui.getWindowSize()

            ImGui.end()

            threektTexture.bind_framebuffer()
            // threekt clear is called once and while imgui calls it every frame, so we have to call it ourselves
            val bg = Color(Color.skyblue)
            GL32.glClearColor(bg.r, bg.g, bg.b, 1F)
            GL32.glClear(GL32.GL_COLOR_BUFFER_BIT or GL32.GL_DEPTH_BUFFER_BIT)

            onResizeThreeKt(windowSize)

            val dt = clock.getDelta()
            box.rotation.x += 0.5f * dt
            box.rotation.y += 0.5f * dt
            renderer.render(scene, camera)

            threektTexture.unbind_framebuffer()

Init OpenGL/threekt and only resize if needed

    private fun initThreeKt() {
        val imguiWindowSize = ImGui.getWindowSize()
        val windowSize = WindowSize(imguiWindowSize.x.toInt(), imguiWindowSize.y.toInt())
        camera = PerspectiveCamera(75, windowSize.aspect, 0.1, 1000).also {
            it.translateZ(10f)
        }
        controlsEventSource = ImGuiEventSource(windowSize)
        controls = OrbitControls(camera, controlsEventSource)
        renderer = GLRenderer(windowSize)

        threektTexture.create_framebuffer(imguiWindowSize.x.toInt(), imguiWindowSize.y.toInt())
    }

    private fun onResizeThreeKt(windowSize: ImVec2) {
        val width = windowSize.x.toInt()
        val height = windowSize.y.toInt()
        if (width == lastWidth && height == lastHeight) {
            return
        }
        camera.aspect = width.toFloat() / height.toFloat()
        camera.updateProjectionMatrix()
        camera.updateWorldMatrix()

        renderer.setSize(width, height)
        threektTexture.rescale_framebuffer(width, height)

        lastWidth = width
        lastHeight = height
    }