Direct2D深度缓冲区

11

我需要绘制一组形状,我正在使用Direct2D。我从文件中获取形状列表。该列表已排序,文件内元素的顺序表示将绘制这些形状的顺序。因此,例如,如果文件指定了两个位置和大小相同的矩形,则只有第二个矩形将可见(因为第一个将被覆盖)。

给定我的形状列表,我按以下方式进行绘制:

list<Shape> shapes;

for (const auto& shape : shapes)
   shape.draw();

很容易看出, 如果我有两个形状,我不能反转绘图操作的顺序,这意味着我必须确保 shape2 总是在 shape1 之后绘制,以此类推。由此可见,我无法使用多线程来绘制我的形状,这在性能方面是一个巨大的劣势。

我了解到 Direct3D 支持深度缓冲区(或 z 缓冲区),它指定每个像素的 z 坐标,因此只会绘制“可见”的像素(比观察者更近的像素),而不管绘制形状的顺序如何。当我读取文件时,我拥有每个形状的深度信息。

是否有一种方法可以在 Direct2D 中使用深度缓冲区,或类似的技术,以允许我使用多个线程来绘制我的形状?


我对 Direct2D 本身不是很确定 - 但通常你会给形状分配一个 z 坐标,按照该坐标进行排序,然后相应地进行渲染,从而实现简单的 Z 缓冲。 - rhughes
2
@rhughes 我不需要对它们进行排序,因为在读取文件时它们已经被排序。此外,使用这种方法可以避免使用多个线程。 - Nick
我的意思是按z坐标排序。您不需要在单独的线程上呈现它们。首先使用最低的z坐标渲染形状,然后逐步处理具有最高z坐标的形状。 - rhughes
我认为你确实应该重新排序颜色,因为那里没有深度。然后你的绘画是(或可能是)等待后续绘画的队列(特别是在 EndDraw 中),所以直接从线程中执行这些操作不会奏效,因为你应该序列化这些调用(或者让实现进行序列化,这几乎是一样的)。 - Roman R.
这些形状是什么类型的,它们是矩形还是更复杂的多边形? - Anton Angelov
@AntonAngelov 每种形状,从矩形到折线到文本等等。但对于每个图形元素,我都有它的边界(一个矩形)。 - Nick
2个回答

4

有没有一种使用Direct2D深度缓冲区或类似技术的方法,允许我使用多个线程绘制我的形状?

答案是没有。虽然Direct2D库是建立在Direct3D之上的,但它不通过API提供用户这样的功能,因为您可以绘制的基本图形仅由二维坐标描述。您绘制到渲染目标的最后一个基元保证可见,因此不进行深度测试。此外,Direct3D中的深度缓冲区与CPU方面的多线程处理关系不大。

还要注意,即使您正在使用多个线程发出绘图命令,它们也将被Direct3D驱动程序序列化并按顺序执行。一些新的图形API(如Direct3D 12Vulkan)确实提供了多线程驱动程序,允许您有效地从不同的线程绘制不同的内容,但它们具有更高的复杂性。

如果您坚持使用Direct2D,最后只能使用单个线程逐个绘制每个形状。

但可以通过测试每个形状与其他所有形状的遮挡情况来消除实际上被遮挡的形状。因此,遮挡的形状可以从列表中丢弃,并且根本不会被渲染。这里的诀窍是,由于透明区域(如文本)或者形状是复杂的多边形,有些形状无法完全填充其边界矩形,因此不能轻松地进行测试,或者需要更复杂的算法。

因此,您必须遍历全部形状,如果当前形状是矩形,则只需对其前面所有形状的边界矩形进行遮挡测试。

以下代码应被视为伪代码,旨在演示该思想。

#define RECTANGLE 0
#define TEXT      1
#define TRIANGLE  2
//etc

typedef struct {
    int type; //We have a type field
    Rect bounds_rect; //Bounds rect
    Rect coordinates; //Coordinates, which count vary according to shape type
    //Probably you have many other fields here
} Shape;

//We have all shapes in a vector
std::vector<Shape> shapes;

迭代所有形状。

for (int i=1; i<shapes.size; i++) {
  if(shape[i].type != RECTANGLE) {
    //We will not perform testing if the current shape is not rectangle.
    continue; 
  }

  for(int j=0; j<i; j++) {
    if(isOccluded(&shape[j], &shape[i])) {
      //shape[j] is totally invisible, so remove it from 'shapes' list
    }
  }
}

遮挡测试就像这样。
bool isOccluded(Shape *a, Shape *b) {
  return (a.bounds_rect.left > b.coordinates.left && a.bounds_rect.right < b.coordinates.right &&
          a.bounds_rect.top > b.coordinates.to && a.bounds_rect.bottom < b.coordinates.bottom);
}

您不需要使用单个线程迭代所有形状,可以创建多个线程来测试形状列表的不同部分。当从列表中删除形状时,当然需要一些锁定技术,如互斥锁,但这是另一个话题。


0
深度缓冲区用于丢弃在3D空间中被前面的物体遮挡的基元,通过不处理不会被看到的东西来节省重绘时间。如果你想象一下一个场景,有一个高而瘦的蜡烛在球面前面,面向相机,整个球体并没有被绘制,然后再将蜡烛绘制在其上,只有球体的可见侧面被绘制。这就是为什么绘制顺序不重要的原因。
我从未听说过在D2D中使用深度缓冲区,因为它有点毫无意义;在D2D中,所有东西都绘制在一个平面上,怎么可能有东西在另一件东西的前面或后面?API可能支持它,但我怀疑它是否有抽象意义。每个形状上的深度信息本质上只是绘制它的顺序,而这已经存在了。
相反,你可以将你的形状分割和分配到你的线程中,同时保持顺序,即
t1 { shape1, shape2, shape3 } = shape123
t2 { shape4, shape5, shape6 } = shape456
...

将形状绘制到一个新对象上(而不是后备缓冲区),根据您的形状类,您可以将结果表示为形状。这将使您拥有许多仍然按顺序但已经并行计算的形状。然后,您可以通过按顺序绘制结果来逐步组合最终结果,即

t1 { shape123, shape456, shape789 }
t2 { shape101112, shape131415 }

t1 { shape123456789, shape101112131415 } = final shape

现在你已经有了最终的形状,可以像平常一样绘制它。


你的程序看起来非常有趣,但是除非你明确指定应该在哪里绘制这些形状的新对象,否则整个过程可能是不可行的。请注意,我绘制几何图形以及线条、椭圆、矩形和文本(这些可以分开处理)。 - Nick
你可以将每个“组合形状”绘制到纹理对象上,然后像上面那样将纹理组合在一起,将最终结果绘制到四边形上,这将允许你以通用形式表示任何基元(无论是文本、几何图形还是其他图像)。这可能需要深入了解d3d,但我不确定d2d的范围有多广。 - Madden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接