版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq992817263/article/details/88071030
前言
- 有没有人跟我有过同样的想法,觉得
Unity
的imgui
相当繁琐,每次想写点编辑器扩展都得自己写一大堆的东西,本来就一直在网上找找有没有一些造好的轮子可以使用,可惜没有找到。后面在找到dear imgui
的时候,就在想这么牛逼的东西,能集成到unity里面就好了啊,然后还真找到了解决方案,使用unity的原生渲染接口渲染dear imgui
就可以了。 - 有趣的是,因为工作上杂事比较多,这个集成技术测试,却不得不先放一放,这里只是临时作个记录,以方便后续还可以接着研究。所以这里其实是一个未完成的解决方案。
- 我目前只以我现有的环境做记录,暂不考虑兼容任何其他的平台或者抽象API,我的环境是
win10 64位系统
directx11
visual studio 2017
unity 2018.3.f2
参考链接
- dear imgui:https://github.com/ocornut/imgui
- unity 原生渲染接口:Unity Low-level native plug-in interface
功能实现
- 新建c++动态库工程命名
RenderingPlugin
,在unity安装目录下Unity2018.3.0f2\Editor\Data\PluginAPI
找到接口文件,添加到工程中 - 在
RenderingPlugin
按着Unity Low-level native plug-in interface教程添加代码 - 再将
dear imgui
的工程文件添加到RenderingPlugin
工程文件中,同时添加dear imgui
的imgui_impl_win32
&imgui_impl_dx11
,这是我们目前所需要的平台代码 - 然后再添加如下代码(windows编程真心不熟,全靠乱蒙),
//接收windwos的事件 传给imgui
extern LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (ImGui_ImplWin32_WndProcHandler(hwnd, msg, wParam, lParam))
{
return true;
}
//调用原生的事件
return CallWindowProcW(_oldWndProc,hWnd,msg, wParam,lParam);
}
//为c#添加设置窗口句柄的接口函数,同时设置imgui的初始化
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
SetHwnd(HWND hwnd)
{
_hwnd = hwnd;
_oldWndProc=(WNDPROC)SetWindowLongPtr(_hwnd, GWLP_WNDPROC,(LONG_PTR)WndProc);
// TODO: Initialization code.
auto* D3D = s_UnityInterfaces->Get<IUnityGraphicsD3D11>();
D3D11Device = D3D->GetDevice();
D3D11Device->GetImmediateContext(&D3D11DeviceContext);
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
ImGui_ImplWin32_Init(_hwnd);
ImGui_ImplDX11_Init(D3D11Device, D3D11DeviceContext);
}
//...
//在结束的时候,清理imgui
static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType)
{
// Create graphics API implementation upon initialization
if (eventType == kUnityGfxDeviceEventInitialize)
{
assert(s_CurrentAPI == NULL);
s_DeviceType = s_Graphics->GetRenderer();
s_CurrentAPI = CreateRenderAPI(s_DeviceType);
}
// Let the implementation process the device related events
if (s_CurrentAPI)
{
s_CurrentAPI->ProcessDeviceEvent(eventType, s_UnityInterfaces);
}
// Cleanup graphics API implementation upon shutdown
if (eventType == kUnityGfxDeviceEventShutdown)
{
delete s_CurrentAPI;
s_CurrentAPI = NULL;
s_DeviceType = kUnityGfxRendererNull;
//TODO: user shutdown code
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
D3D11DeviceContext->Release();
ImGui::DestroyContext();
}
}
//添加imgui 的demo作为测试
static void UNITY_INTERFACE_API OnRenderEvent(int eventID)
{
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
ImGui::ShowDemoWindow();
ImGui::Begin("Hello, World!");
ImGui::End();
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.
{
static float f = 0.0f;
static int counter = 0;
ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state
ImGui::Checkbox("Another Window", &show_another_window);
ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f
ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color
if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
counter++;
ImGui::SameLine();
ImGui::Text("counter = %d", counter);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End();
}
// 3. Show another simple window.
if (show_another_window)
{
ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
ImGui::Text("Hello from another window!");
if (ImGui::Button("Close Me"))
show_another_window = false;
ImGui::End();
}
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
- 将
RenderingPlugin
生成的dll放在unity
的Plugin
文件夹下,然后在脚本中添加unity的接口
[DllImport("RenderingPlugin")]
private static extern void SetHwnd(IntPtr hwnd);
IEnumerator Start()
{
IntPtr hwnd = HWND.GetHwnd();
SetHwnd(hwnd);
yield return StartCoroutine("CallPluginAtEndOfFrames");
}
//在主线程中调用渲染接口
private IEnumerator CallPluginAtEndOfFrames()
{
while (true) {
// Wait until all frame rendering is done
yield return new WaitForEndOfFrame();
// Issue a plugin event with arbitrary integer identifier.
// The plugin can distinguish between different
// things it needs to do based on this ID.
// For our simple plugin, it does not matter which ID we pass here.
GL.IssuePluginEvent(GetRenderEventFunc(), 1);
}
}
- 运行截图
待扩展
imgui
的脚本扩展,可以支持c#
、python
、lua
等多种选择imgui
有一个docking
分支,用来写编辑器布局,是非常方便又灵活的- 最开始就是考虑为扩展unity编辑器而集成
imgui
的,但是因为unity
的EditorWindow
的句柄太不可控了,问题太多,所以先测试了集成在UnityEditor.GameView
,和发布exe
测试,虽然上面有集成在EditorWindow
里的截图,但这里只是一个初步的测试 - 然而就算只为在
UnityEditor.GameView
与发布的exe
集成了imgui
也是有一定问题的,后续改进吧 - 至于源码,由于是测试代码,凌乱不堪,暂时不考虑放出
- 未完待续。。。