ImGUI手动修改DrawList时的异常渲染问题排查
节点画布系统实现问题与需求
背景
基于ImGUI.NET参考imgui-node-editor的imgui_canvas.cpp实现简易节点画布系统,核心逻辑是捕获Canvas.Begin()与Canvas.End()之间的ImGUI命令和顶点,将其从画布局部空间转换为屏幕空间,使该区间内的UI元素坐标基于画布局部空间。
异常渲染问题
- 当设置元素渲染在(0,0)位置时,元素无法显示
- 仅当父窗口Y坐标为负时,元素才会出现
- 同时存在异常遮罩行为
已尝试的排查操作
- 排除裁剪矩形边界问题
- 尝试移除裁剪代码、设置全屏裁剪范围
- 将裁剪范围设为(-9999,-9999)到(9999,9999)
- 始终使用屏幕空间裁剪
- 若将按钮设置为默认位置(
ImGui.SetCursorScreenPos(_widgetPos))可显示,但画布变换会导致定位错误
替代方案需求
寻求支持以下特性的ImGUI画布替代实现方案:
- 无限画布的缩放、平移
- 可在画布上绘制常规ImGUI元素并响应鼠标事件
相关代码
Canvas类实现
private Vector2 _widgetPos; private Vector2 _widgetSize; private ImDrawListPtr _drawList; private Vector2 _offset; private float _scale = 1f; private int _vtxBufferStart; private int _cmdBufferStart; private const float GridStep = 64f; public void Begin() { _widgetPos = ImGui.GetCursorScreenPos(); _widgetSize = ImGui.GetContentRegionAvail(); _drawList = ImGui.GetWindowDrawList(); // Draw Grid (Grid jumps a bit when scaling - TODO: fix this) var localMin = WidgetToLocal(Vector2.Zero); var localMax = WidgetToLocal(_widgetSize); var gridColour = ImGui.ColorConvertFloat4ToU32(new Vector4(220, 220, 220, 50) / 255); for (float x = localMin.X % GridStep; x < localMax.X - localMin.X; x += GridStep) _drawList.AddLine(LocalToScreen(new Vector2(localMax.X - x, localMin.Y)), LocalToScreen(new Vector2(localMax.X - x, localMax.Y)), gridColour); for (float y = localMin.Y % GridStep; y < localMax.Y - localMin.Y; y += GridStep) _drawList.AddLine(LocalToScreen(new Vector2(localMin.X, localMax.Y - y)), LocalToScreen(new Vector2(localMax.X, localMax.Y - y)), gridColour); // Clip to control _drawList.PushClipRect(ScreenToLocal(_widgetPos), ScreenToLocal(_widgetPos + _widgetSize), false); // Any UI drawn past this point will be in canvas space _vtxBufferStart = _drawList.VtxBuffer.Size; _cmdBufferStart = _drawList.CmdBuffer.Size; // Start Drawing from (0, 0) in the canvas ImGui.SetCursorScreenPos(Vector2.Zero); } public Vector2 ScreenToLocal(Vector2 screen) => WidgetToLocal(screen - _widgetPos); public Vector2 LocalToScreen(Vector2 local) => LocalToWidget(local) + _widgetPos; public Vector2 LocalToWidget(Vector2 local) => (local + _offset) * _scale; public Vector2 WidgetToLocal(Vector2 widget) => widget / _scale - _offset; public void End() { // Any UI drawn past this point is in screen space var vtxBufferEnd = _drawList.VtxBuffer.Size; var cmdBufferEnd = _drawList.CmdBuffer.Size; for (int idx = _vtxBufferStart; idx < vtxBufferEnd; idx++) // Update vertices { var vtx = _drawList.VtxBuffer[idx]; vtx.pos = LocalToScreen(vtx.pos); } for (int idx = _cmdBufferStart; idx < cmdBufferEnd; idx++) // Update clipping { var cmd = _drawList.CmdBuffer[idx]; var (min, max) = Util.SplitVector4(cmd.ClipRect); cmd.ClipRect = Util.MergeVector2s(LocalToScreen(min), LocalToScreen(max)); } _drawList.PopClipRect(); // We are done with clipping now // Zooming var io = ImGui.GetIO(); _scale += io.MouseWheel * _scale * 0.1f; // Draw Invisible Button to capture click and focus events ImGui.SetCursorScreenPos(_widgetPos); ImGui.InvisibleButton("Canvas", _widgetSize); bool isHovered = ImGui.IsItemHovered(); bool isClicked = ImGui.IsItemActive(); if (isClicked) { _offset += WidgetToLocal(io.MousePos) - WidgetToLocal(io.MousePosPrev); } }
画布使用方式
ImGui.Begin("StoryGraph", ref _open); _canvas.Begin(); ImGui.Button("Example Button!"); _canvas.End(); ImGui.End();
内容的提问来源于stack exchange,提问作者Alex Davies
相关产品推荐
相关产品推荐

