首页 > 其他分享 >GUIClip在IMGUI中的作用

GUIClip在IMGUI中的作用

时间:2025-01-22 22:10:12浏览次数:1  
标签:IMGUI localRect GUI current 坐标 GUIClip Event 作用

目录

简介

Unity中的IMGUI是一个独立于ugui的UI系统。IMGUI事件(消息)驱动的UI系统,主要用于编写开发工具。
Unity官方目前并无GUIClip的相关文档,本篇文章的主要目的是描述GUIClip类在 IMGUI中的作用,给有需要的同学提供一些学习资料。

IMGUI

IMGUI是WinAPI和图形API(DirectX, OpenGL等)的简单拼装。

  • OnGUI函数回调中,我们需要根据不同的事件类型,做出不同的处理。由于需要围绕事件写代码,所以IMGUI事件驱动的。
  • 我们可以直接使用GUI,GUILayout类中写好的控件。GUIUtilityEditorGUIUtility提供了一些功能函数。

IMGUI控件的渲染其实存在两个阶段,一个是Setup,另一个是Display

  • Setup阶段,我们需要设置一些参数用于坐标系变换。GUIClipGUI.matrix涉及坐标系的变化,对应到DirectX就是IDirect3DDevice9::SetTransform
  • Display阶段,我们需要调用Draw函数来渲染图形。GUIStyle负责图像的渲染,对应到DirectX就是IDirect3DDevice9::DrawIndexedPrimitive

GUIClip

简单介绍了IMGUI,这里我们进入正题。就像上面说的,GUIClipIMGUI系统中的作用涉及坐标系的变化。GUIClip在底层以栈Stack的形式保存。每次推入一个新的GUIClip,我们就进入了一个新的坐标系。

Push Pop Count

我们可以使用GUIClip.Internal_GetCount()获得push进去的GUIClip的个数,以及使用GUIClip.Push推入一个新的GUIClip

如果遇到"internal函数无法调用"的错误提示,可以参考《Cecil修改UnityDll,不使用反射就能调用internal的函数》

private void OnGUI()
{
    Debug.Log($"OnGUIStart: GUIClip.Count={GUIClip.Internal_GetCount()}");

    var rect = new Rect(10,10,100,100);
    GUIClip.Push(rect, default, default, false);
    Debug.Log($"AfterPush: GUIClip.Count={GUIClip.Internal_GetCount()}");
    GUIClip.Pop();
    Debug.Log($"AfterPop: GUIClip.Count={GUIClip.Internal_GetCount()}");
}

//OnGUIStart: GUIClip.Count=1
//AfterPush: GUIClip.Count=2
//AfterPop: GUIClip.Count=1

运行代码,我们可以看到在OnGUI函数开始的时候,就已经有一个GUIClip被push进去。接着我们调用GUIClip.Push,日志打印的GUICLip个数会加1变成2。对应的,如果我们pop出最近的GUIClip,打印的个数会变为1。

局部坐标

上文提到了,每次推入一个新的GUIClip,我们就进入了一个新的坐标系。IMGUI中的坐标基本上都是相对当前坐标系的局部坐标
比如,我们在当前坐标系中调用GUIStyle.Draw和在push一个新的GUIClip后调用GUIStyle.Draw,它们的位置是不同的。

StyleDraw中Rect点的位置

private void OnGUI()
{
    var currentEvent = Event.current.mousePosition;
    var localRect = new Rect(50,50,200,200);

    //在原坐标系中  渲染 新的GUIClip的Rect
    if(Event.current.type == EventType.Repaint) 
        GUI.skin.window.Draw(localRect, GUIContent.none, 0);

    GUIClip.Push(localRect, default, default, false);

    //在新坐标系中  渲染 相同的Rect
    if(Event.current.type == EventType.Repaint) 
        GUI.skin.window.Draw(localRect, GUIContent.none, 0);

    GUIClip.Pop();
}

虽然代码中都是调用GUIStyle.Draw渲染Rect(50,50,200,200) 这个长方形位置的图片,但由于Rect是局部坐标,并且GUIClip的绝对位置不同,因此它们渲染的位置不同。

鼠标位置

除了GUIStyle.Draw传入的Rect是局部坐标。Event.mousePositin返回的也是局部坐标。

如果我们修改上面的代码,让其在当前坐标系的(0,0)打印鼠标的位置(红点标记),我们可以发现鼠标位置在不同GUIClip中的局部坐标也是不同的。

private void OnGUI()
{
    var currentEvent = Event.current.mousePosition;
    var localRect = new Rect(50,50,200,200);

    if(Event.current.type == EventType.Repaint) 
        GUI.skin.window.Draw(localRect, GUIContent.none, 0);
    LabelMousePos();

    GUIClip.Push(localRect, default, default, false);

    if(Event.current.type == EventType.Repaint) 
        GUI.skin.window.Draw(localRect, GUIContent.none, 0);
    LabelMousePos();

    GUIClip.Pop();

    
}

private void LabelMousePos()
{
    string content = $"{Event.current.mousePosition}";
    var size = GUI.skin.label.CalcSize(GUIContent.Temp(content));
    GUI.Label(new Rect(0, 0, size.x, size.y), $"{Event.current.mousePosition}");
}

绝对坐标

既然有局部坐标,那么就有绝对坐标。绝对坐标通俗点说就是相对窗口左上角的坐标
对于当前GUIClipRect,我们可以使用GUIClip.topmostRect获取的绝对坐标。

注意 GUIClip.GetTopRect()返回的是当前GUIClip在上一个GUIClip中的相对位置。

我们可以使用下面的代码,在当前GUIClip原点的位置显示当前GUICLip的绝对坐标。

private void OnGUI()
{
    var currentEvent = Event.current.mousePosition;
    var localRect = new Rect(50,50,200,200);

    if(Event.current.type == EventType.Repaint) 
        GUI.skin.window.Draw(localRect, GUIContent.none, 0);
    LabelAtOrigin(GUIClip.topmostRect.ToString());

    GUIClip.Push(localRect, default, default, false);

    if(Event.current.type == EventType.Repaint) 
        GUI.skin.window.Draw(localRect, GUIContent.none, 0);
    LabelAtOrigin(GUIClip.topmostRect.ToString());
    Debug.Log(GUIClip.GetTopRect());

    GUIClip.Pop();
    
}

private void LabelAtOrigin(string labelContent)
{
    var size = GUI.skin.label.CalcSize(GUIContent.Temp(labelContent));
    GUI.Label(new Rect(0, 0, size.x, size.y), labelContent);
}


可以发现刚进入OnGUI函数时就存在的GUIClip的绝对坐标的位置是(0,21),这是因为EditorWindow实际上只是DockArea(GUIView)的一个pane。在WinAPI中注册的窗口属于GUIView这个类,DockArea派生自GUIView,可以有多个EditorWindowDockArea调用OldOnGUI函数时,会调用GUIView.BeginOffsetArea推入一个GUIClip,之后才会接着调用当前EditorWindowOnGUI函数。

ScrollOffset对局部坐标的影响

我们修改代码,对push的GUIClip添加ScrollOffset参数。
我们在第一个GUIClip的局部坐标原点显示ScrollOffset参数的值,在第二个的GUIClip的局部坐标原点显示第二个GUIClip的绝对坐标。
以及在当前GUIClipmousePosition位置打印mousePosition

private void OnGUI()
{
    var localRect = new Rect(50,50,200,200);

    if(Event.current.type == EventType.Repaint) 
        GUI.skin.window.Draw(localRect, GUIContent.none, 0);
    LabelAtOrigin("scrollOffset="+scrollOffset.ToString());

    GUIClip.Push(localRect, scrollOffset, default, false);

    if(Event.current.type == EventType.Repaint) 
        GUI.skin.window.Draw(localRect, GUIContent.none, 0);
    LabelAtOrigin(GUIClip.topmostRect.ToString());
    LabelAt(Event.current.mousePosition, $"{Event.current.mousePosition}");

    GUIClip.Pop();

    if (Event.current.isScrollWheel)
    {
        var delta = Event.current.delta; //向下滚动滚轮, delay.y>0
        if (Event.current.alt)
            scrollOffset.x += delta.y; //scrollOffset.y<0时ui往上移动
        else
            scrollOffset.y -= delta.y; //scrollOffset.y<0时ui往上移动

        Event.current.Use();
    }

}

private void LabelAtOrigin(string labelContent)
{
    LabelAt(Vector2.zero, labelContent);
}

private void LabelAt(Vector2 pos, string labelContent)
{
    var size = GUI.skin.label.CalcSize(GUIContent.Temp(labelContent));
    GUI.Label(new Rect(pos.x, pos.y, size.x, size.y), labelContent);
}

可以看到随着scrollOffset的变化, 第二个GUIClip的局部坐标的位置都随之发生了变化。

但是GUIClip的绝对坐标并没有因为scrollOffset的变化而发生变化。

局部坐标和绝对坐标的相互转化

上一小节,我们可以观察到虽然鼠标没有挪动,但是随着scrollOffset的变化,鼠标局部坐标的数值也发生变化,从而得出scrollOffset可以影响到局部坐标的数值。
除此之外,GUI.matrix也可以影响到绝对坐标和局部坐标的相互转化。

这里给出示例代码以及对应的源码

private void OnGUI()
{
    var localRect = new Rect(50,50,200,200);

    if(Event.current.type == EventType.Repaint) 
        GUI.skin.window.Draw(localRect, GUIContent.none, 0);
    LabelAtOrigin("scale=" + scale.ToString());

    GUIClip.Push(localRect, scrollOffset, default, false);
    GUI.matrix = Matrix4x4.Scale(new Vector3(scale, scale, 1));

    if(Event.current.type == EventType.Repaint) 
        GUI.skin.window.Draw(localRect, GUIContent.none, 0);
    LabelAtOrigin(GUIClip.topmostRect.ToString());
    LabelAt(Event.current.mousePosition, $"{Event.current.mousePosition}");

    GUI.matrix = Matrix4x4.identity;
    GUIClip.Pop();

    if (Event.current.isScrollWheel)
    {
        var delta = Event.current.delta; //向下滚动滚轮, delay.y>0
        scale += delta.y *0.01f;

        Event.current.Use();
    }
}

/// Clips /absolutePos/ to drawing coordinates
Vector2 Clip(Vector2 absolutePos, Vector2 scrollOffset)  // m_AbsoluteMousePosition
{
    if (GUIClip.Internal_GetCount() == 0)
    {
        return default;
    }

    var inverseMatrix = Matrix4x4.Inverse(GUIClip.GetMatrix());
    Vector2 transformedPoint = inverseMatrix.MultiplyPoint(absolutePos);

    Vector2 result = transformedPoint - scrollOffset - GUIClip.topmostRect.position;
    return result;

}
//Unity的 UnClip函数的实现
Vector2 UnClip_F__M_(Vector2 pos, Vector2 scrollOffset)
{
    if (GUIClip.Internal_GetCount() == 0)
    {
        return default;
    }

    var matrix = GUIClip.GetMatrix();
    Vector2 transformedPoint = matrix.MultiplyPoint(new Vector3(pos.x, pos.y, 0.0F));
    return transformedPoint + scrollOffset + GUIClip.topmostRect.position;
}

裁剪

GUIClip的裁剪分成几部分,

  • 在push进一个新的GUIClip的时候,底层函数会保证新的GUIClip的4个点的绝对坐标不超过当前的GUIClip
  • GUIStyle调用Draw的时候,会将局部坐标在m_VisibleRect 之外的点都裁剪掉。
  • 渲染管道的Clipping裁剪阶段会将位于View Volume之外的点裁剪掉。

m_VisibleRect = Rectf (-topmost.scrollOffset.x, -topmost.scrollOffset.y, topmost.physicalRect.width, topmost.physicalRect.height);

参考链接

Immediate Mode GUI – Theory and Example

IMGUI crash course

标签:IMGUI,localRect,GUI,current,坐标,GUIClip,Event,作用
From: https://www.cnblogs.com/dewxin/p/18685680

相关文章

  • 《String类的equals()的作用和源代码解读》
    一、equals()方法的由来equals()最开始是定义在Java.lang包下的Object中的一个经行比较的方法,根据Object类的核心代码可以看出来,在Object类中equals()方法比较时使用“==”运算符来比较两者地址,但实际应用情况下,人们往往想比较两者的值是否相同,当两个相同的值存进不同内存地址时......
  • String类的equals()的作用和源代码解读
    1. 了解equals()方法equals方法是用于比较两个对象是否相等的方法,定义在Object类中。其默认实现仅比较对象的引用地址,但可以通过重写方法实现对对象内容的比较。只有引用数据类型才可以使用equals方法,我们点进equals方法的源码:我们看代码前几行,观察到当传入进来的参数之间......
  • html的a标签属性rel="noopener"有什么作用?
    在HTML中,<a>标签的rel属性用于定义当前文档与被链接文档之间的关系。rel="noopener"是其中的一个值,它主要的作用是在打开新窗口或新标签页时,防止新页面通过window.opener属性访问到原页面的window对象。这可以提高网站的安全性,防止一些潜在的跨站脚本攻击(XSS)或反向Tabnabb......
  • 什么是IPMI及其在独立服务器中的作用?
    IPMI的优势功能描述提高效率减少了对现场技术人员的依赖,加快问题解决速度。增强安全性提供了额外的安全层,确保只有授权人员才能访问敏感信息。降低运营成本避免了频繁派遣技术人员到现场,节省了时间和费用。简化管理统一的管理界面使多台服务器的管理和维护......
  • Spring中Bean的作用域解析及使用场景
    目录一、Singleton(单例模式)二、Prototype(原型模式)三、Request(请求作用域)四、Session(会话作用域)五、GlobalSession(全局会话作用域)六、Application(应用作用域)七、WebSocket(WebSocket作用域)在Spring框架中,Bean的作用域决定了Bean的生命周期和可见范围。合理选择Bean的作......
  • 你了解什么是AOP吗?它的作用是什么?举个例子
    AOP(AspectOrientedProgramming,面向切面编程)是一种编程范式,它的主要作用是通过预编译方式和运行期间动态代理,实现程序功能的统一维护。AOP是OOP(面向对象编程)的延续,可以弥补OOP的不足,提高程序的可重用性和开发效率。AOP的核心作用包括:降低耦合度:AOP通过对业务逻辑的各个部分进......
  • css的linear-gradient有什么作用呢?
    linear-gradient()是CSS中的一个函数,用于创建一个线性渐变的背景图像。这个函数可以让你在两个或更多的颜色之间创建一个平滑的过渡效果。linear-gradient()函数的基本语法如下:linear-gradient(angleordirection,color-stop1,color-stop2,...);angle:定义渐变线的角......
  • cv(const-volatile)属性的作用
    在C++中,cv是const-volatile的缩写,表示类型的const和volatile修饰符。它们用来修饰变量或对象,影响变量的行为,主要和变量的可修改性和编译器优化相关。C++中cv属性的作用const修饰符表示变量是只读的,不能被修改。如果试图修改const修饰的变量,编译器会报错。例......
  • "moduleResolution": "node"的作用
    "moduleResolution":"node"是TypeScript编译选项之一,它指定了模块解析策略,具体来说是指定如何查找和解析模块。当你的项目中使用了import或require语句来导入其他模块时,TypeScript编译器需要知道去哪里寻找这些模块以及如何解析它们的路径。设置"moduleResolution":"no......
  • 请说明 Vue 3 中的 setup() 函数的作用及其用法
    深入理解Vue3中的setup()函数在Vue3中,性能和可维护性得到了显著提升,其中最引人注目的变化之一就是引入了CompositionAPI,而setup()函数则是这一API的核心部分。本文将深入探讨setup()函数的作用及其用法,帮助您理解如何在Vue3中更高效地组织和管理组件逻......