You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 08:41:00