Win32 C++程序中Unity与ImGui基于DX11同窗口分层渲染问题
解决方案:Win32 DX11程序中Unity场景与ImGui层级控制
核心问题分析
你遇到的本质问题是:Win32子窗口会默认绘制在父窗口之上,而ImGui是渲染在主窗口(父窗口)的DX目标上,因此Unity子窗口会覆盖ImGui内容;同时Unity内部可能存在强制窗口置顶的逻辑,导致BringWindowToTop无法生效。
下面提供两种可行方案,其中纹理共享方案为最优解。
方案一:调整窗口层级与透明子窗口(无需修改Unity核心逻辑)
通过创建双层子窗口分离Unity渲染与ImGui绘制,确保ImGui窗口层级更高:
创建Unity子窗口:
使用以下样式创建Unity专属子窗口,绑定为主窗口的子控件:HWND unityHwnd = CreateWindowEx( 0, L"UnityWindowClass", // Unity注册的窗口类名 L"UnityRender", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, x, y, width, height, // 指定Unity场景的显示区域 mainHwnd, // 主窗口句柄 NULL, GetModuleHandle(NULL), NULL );注意:禁止添加
WS_EX_TOPMOST扩展样式,避免Unity窗口强制置顶。创建ImGui透明子窗口:
创建一个覆盖整个主窗口的透明子窗口,专门用于渲染ImGui,确保其Z序高于Unity子窗口:HWND imguiHwnd = CreateWindowEx( WS_EX_TRANSPARENT | WS_EX_LAYERED, // 透明+分层属性,允许鼠标穿透 L"ImGuiWindowClass", L"ImGuiRender", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, 0, 0, mainWidth, mainHeight, mainHwnd, NULL, GetModuleHandle(NULL), NULL ); // 将ImGui窗口置于Unity窗口之上 SetWindowPos(imguiHwnd, unityHwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);ImGui初始化绑定:
将ImGui的渲染目标绑定到imguiHwnd的DX SwapChain,确保UI绘制在透明子窗口上,不会被Unity遮挡。鼠标事件处理:
由于ImGui窗口设置了WS_EX_TRANSPARENT,需要在主窗口的消息循环中,将非ImGui区域的鼠标事件转发给Unity子窗口,保证Unity能正常响应交互。
方案二:DX11纹理共享(最优解,彻底规避窗口层级问题)
通过DX11的纹理共享机制,将Unity的渲染结果作为纹理传入主程序的DX上下文,直接在主窗口绘制Unity纹理后叠加ImGui,完全消除窗口Z序冲突:
1. Unity端配置(C#代码)
- 创建兼容DX11的RenderTexture,并设置为相机渲染目标:
RenderTexture unityRenderTex = new RenderTexture(1920, 1080, 24, RenderTextureFormat.ARGB32); unityRenderTex.enableRandomWrite = true; unityRenderTex.Create(); Camera.main.targetTexture = unityRenderTex; - 获取共享纹理句柄:
通过Unity的原生接口获取RenderTexture对应的DX11纹理,并生成共享句柄:
注:需要引用IntPtr nativeTexPtr = unityRenderTex.GetNativeTexturePtr(); ID3D11Texture2D dxTex = (ID3D11Texture2D)Marshal.GetObjectForIUnknown(nativeTexPtr); IDXGIResource dxgiRes = dxTex as IDXGIResource; IntPtr sharedHandle; dxgiRes.GetSharedHandle(out sharedHandle); // 将sharedHandle传递给Win32主程序(可通过命名管道、共享内存等IPC方式)System.Runtime.InteropServices,并定义DX11接口的COM封装(可通过NuGet或手动定义)。
2. Win32主程序端实现(C++代码)
- 打开共享纹理:
在主程序的DX11设备中,通过Unity传递的共享句柄获取纹理:ID3D11Texture2D* pSharedTex = nullptr; HRESULT hr = pD3DDevice->OpenSharedResource( sharedHandle, __uuidof(ID3D11Texture2D), (void**)&pSharedTex ); - 创建Shader资源视图:
ID3D11ShaderResourceView* pSRV = nullptr; D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = 1; pD3DDevice->CreateShaderResourceView(pSharedTex, &srvDesc, &pSRV); - 主渲染循环流程:
// 1. 清空主窗口渲染目标 pImmediateContext->ClearRenderTargetView(pRenderTargetView, clearColor); // 2. 绘制Unity共享纹理到指定区域 SetShaderAndSRV(pSRV); // 绑定自定义Shader和纹理 DrawQuad(x, y, width, height); // 绘制Unity场景区域 // 3. 渲染ImGui(此时ImGui直接绘制在主渲染目标上,自然在Unity纹理之上) ImGui::Render(); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); // 4. 呈现画面 pSwapChain->Present(1, 0);
关键注意事项
- 若使用子窗口方案,需检查Unity是否在内部调用了
SetWindowPos强制置顶,如有则需修改Unity的启动参数或插件代码禁用该逻辑。 - 纹理共享方案需确保Unity与主程序的DX11设备使用相同的功能级别,避免兼容性问题。
- 纹理共享的IPC通信需保证线程安全,可使用命名管道或共享内存实现。
内容的提问来源于stack exchange,提问作者Harald Hansen
相关产品推荐
相关产品推荐

