如何正确地在软件中进行z排序

时间:2010-10-10 09:46:44

标签: 3d

我通过在软件中进行所有必要的计算来在2D画布上渲染3D对象。我没有使用图形加速。

最初所有的对象都是相同大小的立方体,所以我可以根据它们在相机Z中的距离对它们进行排序,这样就可以正确地对它们进行排序。但现在我正在尝试绘制不同尺寸的立方体。这使得我的简单z排序算法在透视投影中失败。

我查看了计算机图形书并发现了所使用的技术,他们最终建议使用基于像素的两个多边形比较来确定哪一个超前于其他多边形。可能这就是他们在显卡中做的事情。但是在软件中这样做似乎过于困难,我想即使我能做到这一点,实际使用也会很慢。

在软件中有一个简单的技巧吗? 3D图形早期的任何例子,当图形卡不可用时?

虽然这是一般的3D图形问题,但如果有帮助,我会在HTML5 Canvas 2D API之上进行此操作。

3 个答案:

答案 0 :(得分:4)

正如@ybungalobill已经提到的,z-buffer是最容易实现的算法。当您填充构成立方体的三角形/多边形时,在它们之间插入Z坐标并按像素存储它。如果稍后填充另一个在相同的X,Y坐标上渲染的多边形,请检查其Z是否小于已存储在缓冲区中的Z.在重新绘制之前,不要忘记将Z缓冲区清除到无穷大。伪代码:

foreach (scanline in polygon)  {
  int length = scanline.right.x - scanline.left.x;
  foreach (pixel in scanline)  {
    float t = (float)pixel.x / length;
    float z = (1 - t) * scanline.left.z + t * scanline.right.z;  // Interpolate the Z coordinate
    if (z < zbuffer[scanline.y][pixel.x])
      drawPixel(pixel.x, scanline.y, polygon.color);  // The pixel is closer, paint it
  }
}

通过不绘制可覆盖的像素而在CPU上执行更好的Z缓冲区的修改方法称为段缓冲区http://www.gamedev.net/reference/articles/article668.asp

另一种方法是 Warnock的算法。它使用递归,这使得它很难在GPU上使用,但如果您使用自己的堆栈来避免堆栈溢出,CPU应该可以正常运行。这个想法在于将场景分成4个部分并检查是否只有一个多边形覆盖整个部分。如果没有再次拆分,直到满足条件(在最坏的情况下将在像素级别满足)。伪代码:

void warnock(Rectangle rect)
{
  float minZ = infinity;
  foreach (polygon in polygons)  {
    if (rect is inside polygon)  {
      float z = interpolateZ(polygon, rect.x + rect.width / 2, rect.y + rect.height / 2);  // Get Z coordinate at the centre of the rectangle
      if (z < minZ)  {  // If there are more polygons in this rectangle, make sure the topmost one gets drawn last
        fillRect(polygon.color);
        minZ = z;
      }
    } else {
      // Divide to 4 subrectangles
      warnock(Rectangle(rect.x, rect.y, rect.width / 2, rect.height / 2));  // Top left
      warnock(Rectangle(rect.x, rect.y + rect.height / 2, rect.width / 2, rect.height / 2));  // Bottom left
      warnock(Rectangle(rect.x + rect.width / 2, rect.y, rect.width / 2, rect.height / 2));  // Bottom right
      warnock(Rectangle(rect.x + rect.width / 2, rect.y + rect.height / 2, rect.width / 2, rect.height / 2));  // Top right
    }
  }
}

画家的算法是您对立方体所做的事情,唯一的区别是它对多边形进行排序而不是整个对象。即使这样,也很难解决各种多边形交叉点,我个人不会将它用于非平凡的场景。

您可能使用的另一种算法是背面剔除算法。这仅适用于不重叠的凸对象。该算法计算每个多边形的法线,如果它指向相机的方向,则将其删除。

投射是另一种确定每像素可见度的方法。但是,它非常占用CPU。基本思想是检查屏幕的每个像素,哪个多边形与它相交(哪个多边形被从当前像素投射的光线击中)。光线的起源是眼睛的位置。伪代码:

foreach (pixel in screen)  {
  float minZ = infinity;  // Can be zfar from the perspective projection
  Color pixelColor = backgroundColor;
  foreach (polygon in projectedPolygons)  {
    if (polygon contains Point(pixel.x, pixel.y))  {
      float z = interpolateZ(polygon, pixel.x, pixel.y);  // Get the current Z for (x, y) and this polygon using bilinear interpolation
      if (z < minZ)  {
        minZ = z;
        pixelColor = polygon.color;
      }
    }
  }
}

答案 1 :(得分:0)

是的,今天你在GPU上使用Z缓冲来进行逐像素深度比较。你也可以在软件中做到这一点。

排序技术一般不起作用,请参阅wikipedia。您可以通过将立方体分成单个面并对它们进行排序而不是立方体来改进它。

许多早期游戏(例如Doom)中使用的更通用的技术是BSP trees。它们不适用于动态场景,因为创建它们很昂贵。然而,他们总体上解决了订购问题。

答案 2 :(得分:0)

我发现适合我的是与Warnock合并的固定网格。将屏幕区域(包括平截头体中的模型)划分为单元格:

enter image description here

为此,您可以使用您插入的基元的边界矩形。这种结构可以非常快速地更新,因为你所要做的就是操纵整数将事物从一个单元移动到另一个单元。为避免不断分配和重新分配数据,请使用免费列表方法:

enter image description here

现在渲染每个网格单元格,如果它足够简单&#34; (Warnock标准)。如果没有,请申请Warnock。

网格单元足够简单&#34;如果单元格的矩形完全包含在为该单元格渲染的三角形内,例如并且给定三角形内的矩形的所有4个交叉点都在所有其他交叉点之前(具有最小深度值)...或者如果单元格为空或只有一个基元。

那就是说,我不是真的这么做是为了实时显示。在复杂的网格上实时有效地完成这项工作可能非常困难。

我主要做的事情就是在非常密集的网格物体上做三维软件中的选框和套索选择顶点/边/多边形,我们不想通过用固定的像素分辨率近似来错过未被遮挡的基元。在这种情况下,用户可以远离网格缩小,我们不希望我们的套索和选框选择错过一大堆子像素基元,所以在这里使用与分辨率无关的Warnock的吸引力在于你可以递归地应用算法,直到你得到那个&#34;足够简单&#34;结果,可以是比像素小得多的矩形。对于具有合理有效的子采样的抗锯齿也可能是有用的(因为如果像素具有完全覆盖,则它不会进行子采样,例如)。我从未真正将它用于光栅化上下文。

光线追踪也很有趣,因为它可以打开所有选项,只要做间接照明,焦散,自由度等,尽管卡雷尔指出它的计算成本非常高。也就是说,我发现有一个很好的BVH我现在可以在相当高的分辨率下进行实时光线追踪,如果我只是直接照明。

这是一个小例子我在几年前掀起的CPU上实时挖掘了一百万个三角形网格。这是我的i3和1600x1200像素。只花了一天时间来编码。 GIF真的降低了质量和帧速率(最初超过120 FPS),但希望你能得到这个想法:

enter image description here

我在CPU(以及GPU)上实时光线跟踪的主要缺点实际上不是光栅化部分。虽然我可以使用i3轻松实时渲染基本材质和光照(这甚至不是优化代码,只是C中的一些基本SIMD和并行循环),如果每个变形百万个三角形网格将会更加困难帧。然后我必须能够以超过100 FPS的速度更新存储超过一百万个三角形的BVH,我不知道如何做得足够快。

也就是说,有一个软件实际上是光栅化数百万个实时变形数据的多边形。它被称为ZBrush:http://i1.wp.com/www.cgmeetup.net/home/wp-content/uploads/2013/11/Zbrush-Character-Modeling-for-The-Last-of-Us-8.jpg

我不知道他们是如何管理它的。当用户用画笔或其他东西使网格变形时,他们可能会使用LOD或者对网格进行超级体素化;对我而言,它并不重要,因为它们让你可以控制每个顶点级别的事物,每个多边形级别,让你看到线框,并让你在加载和保存时输入和输出多边形网格。无论哪种方式,它都具有处理数百万个多边形数据的效果(它甚至设法对17年前超过2000万个多边形的网格进行光栅化和变形,这是无与伦比的;人们甚至可以在17年后与之相匹配)和允许用户以某种方式在每顶点级别对结果进行雕刻和控制,同时保持交互式帧速率,而无需使用GPU进行光栅化。据我所知,他们在那里进行了某种伏都教编程,尽管我可能会用手指来了解他们是如何做到的。