Direct2D 深度缓冲区

Direct2D Depth Buffer

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

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

list<Shape> shapes;

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

很明显,如果我有两个形状,我不能颠倒绘图操作的顺序,这意味着我必须确保 shape2 总是在 shape1 之后绘制等等。随之而来的是我不能使用多线程来绘制我的形状,这在性能方面是一个巨大的劣势。

我读到 Direct3D 支持深度缓冲区(或 z 缓冲区),它为每个像素指定其 z 坐标,这样只有 "visible" 个像素(离观察者更近的那些)将被显示绘制,不管形状的绘制顺序如何。而且我在读文件的时候就有了每个形状的深度信息。

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

深度缓冲区用于丢弃在 3D 中会被其前面的东西遮挡的图元 space,通过不打扰无论如何都不会看到的东西来节省重绘时间。如果您想到一个场景,球前有一根又高又细的蜡烛,面向摄像机,则不会先绘制整个球,然后再绘制蜡烛,只是绘制球的可见面。这样画的顺序就无所谓了

我没有听说过在D2D中使用深度缓冲区,因为它有点没有意义;在 D2D 中,所有东西都被绘制到一个平面上,怎么会有东西在其他东西的前面或后面? API 可能支持它,但我对此表示怀疑,因为它没有抽象意义。每个形状的深度信息只是您已经拥有的本质上绘制它的顺序

相反,您可以在保持顺序的同时将形状划分并分配给线程,即

t1 { shape1, shape2, shape3 } = shape123
t2 { shape4, shape5, shape6 } = shape456
...

然后将形状绘制到新对象(但不是后缓冲区)上,具体取决于您的形状 class 您也许可以将结果表示为形状。这将为您留下 t 多个形状,这些形状仍然有序但已被并行计算。然后你可以通过按顺序绘制结果来逐渐组成你的最终结果,即

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

t1 { shape123456789, shape101112131415 } = final shape

现在你有了最终的形状,你可以正常绘制它了

Is there a way to use the depth buffer in Direct2D, or a similar technique which allows me the use of multiple threads to draw my shapes?

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

另请注意,即使您使用多线程发出绘图命令,它们也会由 Direct3D 驱动程序序列化并按顺序执行。一些较新的图形 API 像 Direct3D 12 and Vulkan 确实提供了多线程驱动程序,使您可以有效地从不同的线程绘制不同的内容,但它们具有更高的复杂性。

所以最终,如果您坚持使用 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);
}

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