24.5使用ImGui实现引擎编辑器


文档摘要

24.5 使用ImGui实现引擎编辑器 引擎编辑器最基础的功能,就是要将游戏画面渲染到编辑器的窗口里,例如Unity就是渲染到Game和Scene窗口。 这个功能有多种方式可以选择。 技术选型 为了将游戏画面渲染到ImGui的窗口,我尝试过两种方式: 编辑器和游戏是独立的Context,在游戏里采集每一帧画面,存储到RingBuffer,然后在编辑器里读取出来显示。 编辑器共享游戏Context,这样在游戏里渲染到FBO后,编辑器里可以实时获取并显示。 方式1的优点是独立。隔离,逻辑清晰简单,缺点是需要从GPU保存画面到文件,然后又上传到GPU显示,开销极大。

24.5 使用ImGui实现引擎编辑器

CLion项目文件位于 samples\engine_editor\imgui_editor_share_context

引擎编辑器最基础的功能,就是要将游戏画面渲染到编辑器的窗口里,例如Unity就是渲染到Game和Scene窗口。

这个功能有多种方式可以选择。

1. 技术选型

为了将游戏画面渲染到ImGui的窗口,我尝试过两种方式:

  1. 编辑器和游戏是独立的Context,在游戏里采集每一帧画面,存储到RingBuffer,然后在编辑器里读取出来显示。
  2. 编辑器共享游戏Context,这样在游戏里渲染到FBO后,编辑器里可以实时获取并显示。

方式1的优点是独立。隔离,逻辑清晰简单,缺点是需要从GPU保存画面到文件,然后又上传到GPU显示,开销极大。
方式2的优点是Context共享,编辑器可以直接获取游戏渲染画面的FBO Attached Texture,无需下载再上传,没什么开销。

毫无疑问,方式2是游戏编辑器普遍采用的方式。

而方式1虽然在引擎编辑器被抛弃,但是在制作性能分析器时,可以作为实时录屏的技术选型,以后再介绍吧。

2. 共享Context

OpenGL的Context是可以共享的,就如同上面说的,游戏Context里的FBO Attached Texture,在Editor Context里是可以访问使用的。

那么首先就创建游戏Context,然后创建ImGui的Context,并将游戏Context共享。

//file:source/app/application_editor.cpp line:49 void ApplicationEditor::InitGraphicsLibraryFramework() { glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) { DEBUG_LOG_ERROR("glfw init failed!"); exit(EXIT_FAILURE); } 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 //创建游戏窗口(游戏Context) game_glfw_window_ = glfwCreateWindow(960, 640, title_.c_str(), NULL, NULL); if (!game_glfw_window_) { DEBUG_LOG_ERROR("glfwCreateWindow error!"); glfwTerminate(); exit(EXIT_FAILURE); } //创建编辑器窗口(Editor Context),并将游戏Context共享。 editor_glfw_window_ = glfwCreateWindow(1280, 720, "Dear ImGui GLFW+OpenGL3 example", NULL, game_glfw_window_); if (!editor_glfw_window_) { DEBUG_LOG_ERROR("glfwCreateWindow error!"); glfwTerminate(); exit(EXIT_FAILURE); } //设置编辑器主线程使用的是 Editor Context glfwMakeContextCurrent(editor_glfw_window_); //开启垂直同步 glfwSwapInterval(1); // Enable vsync //ImGui初始化 IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; //设置主题 ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); //配置后端 ImGui_ImplGlfw_InitForOpenGL(editor_glfw_window_, true); const char* glsl_version = "#version 330"; ImGui_ImplOpenGL3_Init(glsl_version); //游戏窗口 初始化渲染任务消费者(单独渲染线程) RenderTaskConsumer::Init(new RenderTaskConsumerEditor(game_glfw_window_)); }

3. 游戏画面渲染到FBO

OpenGL规定每个线程需要使用单独的Context,在渲染线程,使用的是游戏Context进行绘制。

上面已经将游戏Context共享给Editor Context,为了能在编辑器绘制出游戏画面,现在需要将游戏画面渲染到Texture,也就是渲染到FBO。

//file:source/render_device/render_task_consumer_editor.cpp line:34 void RenderTaskConsumerEditor::InitGraphicsLibraryFramework() { //渲染相关的API调用需要放到渲染线程中。 glfwMakeContextCurrent(window_); gladLoadGL(glfwGetProcAddress); glfwSwapInterval(1); //创建全局FBO,将整个游戏渲染到FBO,提供给编辑器,作为Game视图显示 GLuint frame_buffer_object_id=0; glGenFramebuffers(1, &frame_buffer_object_id);__CHECK_GL_ERROR__ if(frame_buffer_object_id==0){ DEBUG_LOG_ERROR("CreateFBO FBO Error!"); return; } glBindFramebuffer(GL_DRAW_FRAMEBUFFER,frame_buffer_object_id);__CHECK_GL_ERROR__ //创建颜色纹理 Attach到FBO颜色附着点上 glGenTextures(1, &color_texture_id_); glBindTexture(GL_TEXTURE_2D, color_texture_id_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 960, 640, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);__CHECK_GL_ERROR__ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_texture_id_, 0);__CHECK_GL_ERROR__ //创建深度纹理 Attach到FBO深度附着点上 glGenTextures(1, &depth_texture_id_); glBindTexture(GL_TEXTURE_2D, depth_texture_id_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 960, 640, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, nullptr);__CHECK_GL_ERROR__ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_texture_id_, 0);__CHECK_GL_ERROR__ //检测帧缓冲区完整性 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);__CHECK_GL_ERROR__ if (status != GL_FRAMEBUFFER_COMPLETE) { DEBUG_LOG_ERROR("BindFBO FBO Error,Status:{} !",status);//36055 = 0x8CD7 GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 附着点没有东西 return; } }

4. 绘制游戏画面到ImGui窗口

24.3 ImGui介绍与使用 介绍了 ImGui的example,了解了ImGui创建窗口、绘制文字的方式。

现在只要拿到渲染线程的FBO Attached Texture Id,然后使用ImGui::Image接口绘制出来即可。

//file:source/app/application_editor.cpp line:108 void ApplicationEditor::Run() { ApplicationBase::Run(); bool show_demo_window = true; bool show_another_window = false; ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); while (!glfwWindowShouldClose(editor_glfw_window_)) { glfwPollEvents(); //ImGui刷帧 ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); //1.游戏渲染画面 窗口 { ImGui::Begin("Game"); RenderTaskConsumerEditor* render_task_consumer_editor= dynamic_cast<RenderTaskConsumerEditor *>(RenderTaskConsumer::Instance()); //从游戏渲染线程拿到FBO Attach Texture id GLuint texture_id=render_task_consumer_editor->color_texture_id(); ImTextureID image_id = (void*)(intptr_t)texture_id; // ImGui绘制Image,使用FBO Attach Texture id // 第一个参数:生成的纹理的id // 第2个参数:Image的大小 // 第3,4个参数:UV的起点坐标和终点坐标,UV是被规范化到(0,1)之间的坐标 // 第5个参数:图片的色调 // 第6个参数:图片边框的颜色 ImGui::Image(image_id, ImVec2(480,320), ImVec2(0.0, 1.0), ImVec2(1.0, 0.0), ImVec4(1, 1, 1, 1), ImVec4(0, 1, 0, 1)); ImGui::End(); } //2.游戏渲染深度图 窗口 { ImGui::Begin("Depth"); RenderTaskConsumerEditor* render_task_consumer_editor= dynamic_cast<RenderTaskConsumerEditor *>(RenderTaskConsumer::Instance()); GLuint texture_id=render_task_consumer_editor->depth_texture_id(); ImTextureID image_id = (void*)(intptr_t)texture_id; ImGui::Image(image_id, ImVec2(480,320), ImVec2(0.0, 1.0), ImVec2(1.0, 0.0), ImVec4(1, 1, 1, 1), ImVec4(0, 1, 0, 1)); ImGui::End(); } //绘制 ImGui::Render(); int display_w, display_h; glfwGetFramebufferSize(editor_glfw_window_, &display_w, &display_h); glViewport(0, 0, display_w, display_h); glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(editor_glfw_window_); //渲染游戏 EASY_BLOCK("Frame"){ OneFrame(); }EASY_END_BLOCK; } Exit(); }

更多ImGui::Image介绍参考:https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples#Example-for-OpenGL-users

5. 测试


发布者: 作者: 转发
评论区 (0)
U