C# 在OpenGL中绘制裁剪UI元素的最佳方法

C# 在OpenGL中绘制裁剪UI元素的最佳方法,c#,opengl,C#,Opengl,我有一个复杂的UI系统,它允许使用WPF完成许多工作,但支持多种平台(iOS、Android、Windows等等)。它尚未完成,现在我面临以下问题: 我的设计师想要旋转的物体!旋转对象比简单的轴对齐对象复杂得多,这就是我不能使用的原因。一个可能有助于理解问题的小图形: 您可以看到,我需要按照“父容器”的边界剪裁对象“子容器”。据我所知,有几个选择: 使用模具缓冲区,在这种情况下,我遇到了一个问题,因为我有不可见的对象,并且还必须影响模具缓冲区,因为它们可能会屏蔽子对象。此外,我必须绘制每个对

我有一个复杂的UI系统,它允许使用WPF完成许多工作,但支持多种平台(iOS、Android、Windows等等)。它尚未完成,现在我面临以下问题:

我的设计师想要旋转的物体!旋转对象比简单的轴对齐对象复杂得多,这就是我不能使用的原因。一个可能有助于理解问题的小图形:

您可以看到,我需要按照“父容器”的边界剪裁对象“子容器”。据我所知,有几个选择:

  • 使用模具缓冲区,在这种情况下,我遇到了一个问题,因为我有不可见的对象,并且还必须影响模具缓冲区,因为它们可能会屏蔽子对象。此外,我必须绘制每个对象两次,因为我需要减少模具缓冲区时,回到层次
  • 切割用于绘制ui对象的平面(三角剖分;或任何其他ui模型),这似乎非常困难,因为它们可能会在不同的点进行剪裁(想象旋转容器中的旋转容器中的容器…),而且很难正确剪裁它们,这可能是性能问题的根源

然而,两者似乎都会导致许多不同的问题,并且可能是性能泄漏的来源。有没有其他方法来归档我想要的内容,或者有没有任何方法来改进上述两种方法?

这只是一个想法,但是使用深度缓冲区来进行掩蔽呢

  • 启用深度缓冲并设置glDepthFunc(GL_LEQUAL)
  • 在Z=0时渲染容器A及其帧
  • 在Z=1时渲染容器内部区域/背景(其他嵌套容器将位于其中)
  • 现在您有了一个“深度模具”,容器框架位于深度0,容器内部位于深度1。这意味着您在中间渲染的任何内容都将位于内部之上,但位于框架之下(并被其剪裁)
  • 现在使用下一个容器B,将其帧渲染为Z=0.5(它将被GPU上的父容器A剪裁)
  • 在Z=0.75时渲染容器B内部区域
  • 现在,要在容器B中渲染的任何内容都必须在Z=0.75时进行渲染。它将覆盖容器内部区域,但将被容器A和B框架夹持

  • 也许您可以尝试渲染纹理。创建父纹理。然后将所有子对象渲染到该纹理。然后将父纹理渲染到屏幕,并根据需要进行变形和置换。此解决方案可能有问题,也可能没有问题,这取决于您希望实现的目标。特别是如果您对容器缩放设置动画,或者有一个包含许多嵌套容器的非常复杂的树,则可能会出现性能问题。

    我最终使用了模具缓冲区,这会生成比深度方法更多的绘制调用,但更容易实现

    在我画画之前,我写了以下代码:

    if (_Mask)
    {
        if (Stage.StencilMaskDepth++ == 0)
            GL.Enable(EnableFlags.STENCIL_TEST);
    
        GL.ColorMask(false, false, false, false);
        GL.DepthMask(false);
        GL.StencilFunc(StencilFunction.ALWAYS, Stage.StencilMaskDepth, Stage.StencilMaskDepth);
        GL.StencilOp(StencilOp.INCR, StencilOp.INCR, StencilOp.INCR);
    
        // Draw rectangle
        DrawColor(Colors.Black);
    
        GL.ColorMask(true, true, true, true);
        GL.DepthMask(true);
        GL.StencilFunc(StencilFunction.EQUAL, Stage.StencilMaskDepth, Stage.StencilMaskDepth);
        GL.StencilOp(StencilOp.KEEP, StencilOp.KEEP, StencilOp.KEEP);
    }
    
    绘制完所有儿童后,此代码称为:

    if (_Mask)
    {
        GL.ColorMask(false, false, false, false);
        GL.DepthMask(false);
        GL.StencilFunc(StencilFunction.ALWAYS, Stage.StencilMaskDepth, Stage.StencilMaskDepth);
        GL.StencilOp(StencilOp.DECR, StencilOp.DECR, StencilOp.DECR);
        // Draw rectangle
        DrawColor(Colors.Black);
    
        GL.ColorMask(true, true, true, true);
        GL.DepthMask(true);
    
        if (--Stage.StencilMaskDepth == 0)
            GL.Disable(EnableFlags.STENCIL_TEST);
    }
    

    也许我会在几个月后测试一些其他方法,但目前这是最容易实现的。

    也许一个所需效果的屏幕截图(分层)将有助于更好地呈现问题。@KromStern添加了一个显示效果的简单图像。顺便说一句,您可以使用类似Clipper的多边形剪裁库。或者opengl tesslib2。我个人会通过模板测试来实现。我使用了来自的解决方案。我不认为这是可行的?!但也许我不理解你的方法,你能再解释一点吗?@Krom Stern,如果能看到对你的方法的更深入的解释会很高兴。现在,在你再解释一点你的方法之后,这似乎是个好主意。需要测试,但当它工作时,它是一种比三角测量或使用模具缓冲区更好的方法。但需要一些工作来计算正确的z值。我不完全确定最佳z值,但可能可以应用类似“每个下一个z都是z/2”的东西。让我知道结果如何!请描述您打算如何渲染到纹理?每个容器的内容物分为单独的一个?什么时候轮换?(例如,旋转纹理会使其中的图片模糊)我也考虑过这种方法,但这似乎会导致大量内存消耗,我认为它不能用于移动设备。任何旋转都会使某些东西“模糊”。每个容器都必须有自己的纹理。我的建议并不完美。大量容器意味着大量渲染调用。缩放比旋转更有问题。如果你选择你的纹理是小的,然后你渲染它放大,然后你会得到那些讨厌的像素(类似于在大多数游戏中使用纹理和模具进行阴影贴图时的像素)。要克服这个问题,你必须使用足够大的纹理。但如果你有很多容器,这意味着你的应用程序将内存不足。。。。抱歉,我的解决方案确实存在问题。但它也有好处。就像在容器上做一些后处理是非常容易的。不仅是旋转和位移。您甚至可以为该纹理指定一个自定义片段着色器以获得其他效果。@e不管这是否正确,这非常简单,当然这不是一个坏主意。但目前它不适用于移动设备(可能在几年后),因为图形内存太低。我考虑过将其渲染成3个纹理的可能性,这些纹理的大小总是相同的,但这可能会对性能产生不良影响(需要填充更多的纹理)。