1

我一直在编写一个简单的 3d 渲染器,并且一直在研究绘制顺序。引擎将 3d 多边形(按正确绘制顺序的 3d 点组)渲染到 2d 空间中,返回代表给定多边形投影的 2d 点列表。我这样做的方法可能有点不正统,因为我想看看我是否能够自己做,所以我在下面附上了我的投影代码:

public class Camera {
/*position is the position of the camera, x, y, z;
cameraRotation is the rotation of the camera, in the order of rotation about x, rotation about y, rotation about z
the camera initially faces the +x direction
*/
private double focalAngle;
private double[] position, cameraRotation, cameraDirectionVector, cameraXVector, cameraZVector;
private double[][][] rotationMatrices = new double[3][3][3];
private double[][] compoundedRotationMatrices;

public Camera(double[] positionIn, double[] cameraRotationIn, double focalAngleIn){
    position = positionIn;
    focalAngle = focalAngleIn;
    cameraRotation = cameraRotationIn;
    updateRotation();
}

private void updateRotation(){
    updateRotationMatrices();
    updateCameraDirectionVector();
}

private void updateRotationMatrices(){
    compoundedRotationMatrices = Matrix.getCompoundedRotationMatrix(cameraRotation[0], cameraRotation[1], cameraRotation[2]);
}

private void updateCameraDirectionVector(){
    double[] xVector = {1,0,0};
    double[] yVector = {0,-1,0};
    double[] zVector = {0,0,1};
    cameraDirectionVector = Matrix.vecMultiply(compoundedRotationMatrices, xVector);
    cameraXVector = Matrix.vecMultiply(compoundedRotationMatrices, yVector);
    cameraZVector = Matrix.vecMultiply(compoundedRotationMatrices, zVector);
}

public ArrayList<int[][]> getPolygonProjections(ArrayList<double[][]> polySets, double screenWidth, double screenHeight){
   ArrayList<int[][]> outPoints = new ArrayList();
   for(int i = 0; i < polySets.size(); i++){
       int[][] polyPoints = new int[2][polySets.get(i).length];
       /*in the calculation of proejctions, divide by zeros and NaNs can pop up,
       polygonsLegitimate boolean keeps track of whether the polygon being drawn can be drawn without error,
       and the while loop stops calcuating the polygon once it determines it cannot be properly drawn
       */
       boolean polygonsLegitimate = true;
       int j = 0;
       while(j < polyPoints[0].length && polygonsLegitimate){
           int[] xy = getVectorProjection(polySets.get(i)[j], screenWidth, screenHeight);
           if(xy != null){
               polyPoints[0][j] = xy[0];
               polyPoints[1][j] = xy[1];

           }else{
               polygonsLegitimate = false;
           }
           j++;
       }
       if(polygonsLegitimate){
           outPoints.add(polyPoints);
       }
   }
   return outPoints;
}

private int[] getVectorProjection(double[] vector, double screenWidth, double screenHeight){
    double[] subVector = Vector.subtract(vector, position);
    double zDepth = getZDepthOfVector(subVector);
    if(zDepth > 0){
        double sliceSize = getSliceSizeAtDepth(zDepth);
        double cameraXProj = Vector.dot(subVector, cameraXVector);
        double cameraZProj = Vector.dot(subVector, cameraZVector);
        double xPercent = (cameraXProj+(sliceSize/2))/sliceSize;
        double zPercent = (cameraZProj+(sliceSize/2))/sliceSize;
        int[] xy = {(int)(xPercent * screenWidth),(int)((((1-zPercent) * screenWidth))-(screenHeight/2))};
        return xy;
    }
    return null;
}

public double getZDepthOfVector(double[] vector){
    return Vector.dot(cameraDirectionVector, vector);
}

private double getSliceSizeAtDepth(double zDepth){
    return 2.0*Math.cos(focalAngle)*zDepth;
}

目前,我通过按多边形最近角到相机的距离对三维多边形进行排序来确定绘制顺序,然后按照最远多边形到最近多边形的顺序绘制。但是,由于绘制顺序完全取决于多边形上最近点到相机的距离,因此有时会出现一些极端情况会阻止算法正常工作,如本视频所示:

https://youtu.be/olTOTOCw42M

我对 Z Buffer 做了很多研究,这个概念很简单——实际上与我正在做的非常相似。据我了解,对于每个渲染像素,都会比较在同一像素上渲染的所有点,并显示距离相机最近的 z 深度。但是,鉴于在这种情况下,我正在使用的唯一点是构成每个多边形角的点,我不知道一个比较包含在多边形内的任何点的 z 深度的好方法不仅在角落。

对于这个问题,我有两种可能的解决方案:

1)将每个多边形分成多个较小的多边形。当我在 python 中模拟渲染器时,我从未添加过 Z 深度排序,但我确实将每个多边形划分为多个较小的多边形,以便我可以非常轻松地单独点亮每个多边形,结果如下所示:

http://imgur.com/a/U3Xke

然而,这是非常昂贵的,因为许多投影点被多次投影,因为它们的值是通过计算相邻多边形的投影来确定的。也许有一种合法的方法可以解决这个问题,但对我来说这似乎太粗略以至于不正确。

2) 找到每个 3d 多边形所在的平面,将其绑定到多边形的形状,然后求解通过视角定向的各个扫描线与这些平面的交点,然后选择 z 深度最近的交点要在该扫描线的像素处显示的相机。这样,不是每个多边形的点被投影然后使用java的多边形填充方法填充,每个像素将被单独渲染。但是,我不确定如何“绑定”一个平面,使其不会超出多边形的边界,而且我理解起来有点棘手,因为目前数学对我来说有点太高级了。如果这是应该的方式,我可以学习它,我只是想事先确保它是一种可行的方法。

3) 将每个多边形分割成许多点而不是更小的多边形:我认为这种方法会有缺陷,因为良好渲染所需的点数(即每个像素一个 3d 点,不需要有多个 3d 点)相同的多边形形状渲染到完全相同的像素上,或者具有太少的 3d 点以致在渲染过程中“跳过”像素)随 z 深度而变化,并且计算放置这些点的位置的公式似乎很难制定每次移动相机时运行成本很高。

4

2 回答 2

1

我认为您误解了 Z 缓冲区的想法,最接近事实的是您的解决方案。3) - 将你的多边形分割成单个像素。

Z 缓冲区按像素工作,是的,有很多 Z 比较,但这就是它的工作方式。您无法将其简化为仅使用多边形的特定顶点。

我假设你有一些网格,比如说“颜色”结构,你要用你的渲染填充。这将是您的目标图像。您需要添加另一个相同大小的浮动网格 - 这将是您的 Z 缓冲区。开始时,你用一些大的值填充你的 Z 缓冲区,比如 1000000。

现在忽略多边形的排序 - Z 缓冲区会为您解决它。您可以稍后添加排序以测试不同绘图顺序之间的性能差异,但不需要使其工作。

现在您需要光栅化阶段,在该阶段您通过多边形角并取回该多边形覆盖的所有像素的列表。正如您在 2) 中提到的,您可以使用扫描线来计算它。我建议您只为单个三角形编写光栅化并将所有多边形拆分为三角形,这将使您的代码更简单。如果你愿意,你可以从这个阶段返回一个像素列表(在这种情况下列表会很慢,但它可以用于学习目的。在这个阶段直接填充像素网格会更好,而不是在内存中累积这些数据) 但您需要进行一项重要更改 - 除了 X 和 Y 之外,像素还需要正确的 Z 值。

当您拥有这样的光栅化像素列表时,您只需将它们放入像素网格中,这就是 Z 测试发生的地方。遍历每个像素并: 使用像素屏幕位置 (X,Y) 从 Z 缓冲区读取当前 Z 值。如果该值大于当前像素的 Z,则将像素颜色写入颜色缓冲区,将像素 Z 写入 Z 缓冲区。

于 2017-05-05T10:15:39.550 回答
1

使用排序表,Playstation 1 使用的算法。

将 Z 范围分成 N 个大小相等的部分。

  • 创建一个包含 N 个指向多边形的指针的数组。
  • 每一帧,将该数组清零。
  • 对于您要绘制的每个三角形:
    • 计算相应的 Z-Index i。
    • 像使用链表一样将其插入 Order[i] 中。
  • 反向遍历数组(从后到前),在遍历时绘制三角形

C代码

Triangle *Order[256];

void Clear() {
    memset(Order,0,sizeof(Order));
}

void Insert(Triangle *tri) {
    int index = (tri->averageZ-zNear) * 256 / (zFar - zNear);
    tri->next = Order[index];
    Order[index] = tri;
}

void Paint() {
    for(int i=255;i>=0;i--)
        for(Triangle *tri=Order[i];tri;tri=tri->next)
            DrawTriangle(tri);
}

资料来源:Fabien "ryg" Giesen(Farbrausch 成名)有一个题为“当光速不够快时”的演讲,他提出了这个想法,包括这段代码。

于 2017-05-05T10:03:11.580 回答