2

我正在用 Java 编写软件渲染器,并尝试使用 Z 缓冲来计算每个像素的深度。但是,它似乎工作不一致。例如,对于犹他茶壶示例模型,手柄可能会拉出一半,这取决于我如何旋转它。

我的 z 缓冲区算法:

for(int i = 0; i < m_triangles.size(); i++)
{
    if(triangleIsBackfacing(m_triangles.get(i))) continue; //Backface culling
        for(int y = minY(m_triangles.get(i)); y < maxY(m_triangles.get(i)); y++)
        {
            if((y + getHeight()/2 < 0) || (y + getHeight()/2 >= getHeight())) continue; //getHeight/2 and getWidth/2 is for moving the model to the centre of the screen
            for(int x = minX(m_triangles.get(i)); x < maxX(m_triangles.get(i)); x++)
            {
                if((x + getWidth()/2 < 0) || (x + getWidth()/2 >= getWidth())) continue;
                rayOrigin = new Point2D(x, y);
                if(pointWithinTriangle(m_triangles.get(i), rayOrigin))
                {
                    zDepth = zValueOfPoint(m_triangles.get(i), rayOrigin);
                    if(zDepth > zbuffer[x + getWidth()/2][y + getHeight()/2])
                    {
                        zbuffer[x + getWidth()/2][y + getHeight()/2] = zDepth;
                        colour[x + getWidth()/2][y + getHeight()/2] = m_triangles.get(i).getColour();
                        g2.setColor(m_triangles.get(i).getColour());
                        drawDot(g2, rayOrigin);
                    }
               }
          }
     }
}

给定一个三角形和射线原点,计算一个点的 z 值的方法:

private double zValueOfPoint(Triangle triangle, Point2D rayOrigin) 
{
    Vector3D surfaceNormal = getNormal(triangle);
    double A = surfaceNormal.x;
    double B = surfaceNormal.y;
    double C = surfaceNormal.z;
    double d = -(A * triangle.getV1().x + B * triangle.getV1().y + C * triangle.getV1().z);
    double rayZ = -(A * rayOrigin.x + B * rayOrigin.y + d) / C;
    return rayZ;
}

计算射线原点是否在投影三角形内的方法:

private boolean pointWithinTriangle(Triangle triangle, Point2D rayOrigin)
{
    Vector2D v0 = new Vector2D(triangle.getV3().projectPoint(modelViewer), triangle.getV1().projectPoint(modelViewer));
    Vector2D v1 = new Vector2D(triangle.getV2().projectPoint(modelViewer), triangle.getV1().projectPoint(modelViewer));
    Vector2D v2 = new Vector2D(rayOrigin, triangle.getV1().projectPoint(modelViewer));
    double d00 = v0.dotProduct(v0);
    double d01 = v0.dotProduct(v1);
    double d02 = v0.dotProduct(v2);
    double d11 = v1.dotProduct(v1);
    double d12 = v1.dotProduct(v2);

    double invDenom = 1.0 / (d00 * d11 - d01 * d01);
    double u = (d11 * d02 - d01 * d12) * invDenom;
    double v = (d00 * d12 - d01 * d02) * invDenom;

    // Check if point is in triangle
    if((u >= 0) && (v >= 0) && ((u + v) <= 1))
    {
         return true;
    }
    return false;
}

三角形曲面法线的计算方法:

private Vector3D getNormal(Triangle triangle)
{
    Vector3D v1 = new Vector3D(triangle.getV1(), triangle.getV2()); 
    Vector3D v2 = new Vector3D(triangle.getV3(), triangle.getV2());
    return v1.crossProduct(v2);
}

画错茶壶的例子:

在此处输入图像描述

我究竟做错了什么?我觉得这一定是一件小事。鉴于三角形完全绘制,我怀疑它是 pointWithinTriangle 方法。背面剔除似乎也可以正常工作,所以我怀疑是这样。对我来说最可能的罪魁祸首是 zValueOfPoint 方法,但我不知道它有什么问题。

4

2 回答 2

1

我的 zValueOfPoint 方法无法正常工作。我不确定为什么:(但是,我改用一种稍微不同的方法来计算平面中一个点的值,在这里可以找到:http: //forum.devmaster.net/t/interpolation-on-a-3d-三角形使用法线/20610/5

为了使这里的答案完整,我们有一个平面方程: A * x + B * y + C * z + D = 0 其中 A、B 和 C 是表面法线 x/y/z 值,而 D是-(Ax0 + By0 + Cz0)。

x0、y0 和 z0 取自三角形的顶点之一。x、y 和 z 是射线与平面相交点的坐标。x 和 y 是已知值(rayOrigin.x,rayOrigin.y),但 z 是我们需要计算的深度。从上面的等式我们得出:z = -A / C * x - B / C * y - D

然后,从上面的链接复制,我们这样做:“请注意,对于 x 方向上的每一步,z 递增 -A / C,同样它对于 y 方向上的每一步递增 -B / C。所以这些是我们正在寻找执行线性插值的梯度。在平面方程中,(A, B, C) 是平面的法向量。它可以很容易地用叉积计算。

现在我们有了梯度,让我们称它们为 dz/dx(即 -A / C)和 dz/dy(即 -B / C),我们可以轻松地计算三角形上任何地方的 z。我们知道所有三个顶点位置的 z 值。我们称第一个顶点为 z0,它的位置坐标为 (x0, y0)。然后一个点 (x, y) 的通用 z 值可以计算为:"

z = z0 + dz/dx * (x - x0) + dz/dy * (y - y0)

这正确地找到了 Z 值并修复了我的代码。新的 zValueOfPoint 方法是:

private double zValueOfPoint(Triangle triangle, Point2D rayOrigin) 
{
    Vector3D surfaceNormal = getNormal(triangle);
    double A = surfaceNormal.x;
    double B = surfaceNormal.y;
    double C = surfaceNormal.z;
    double dzdx = -A / C;
    double dzdy = -B / C;
    double rayZ = triangle.getV1().z * modelViewer.getModelScale() + dzdx * (rayOrigin.x - triangle.getV1().projectPoint(modelViewer).x) + dzdy * (rayOrigin.y - triangle.getV1().projectPoint(modelViewer).y);
    return rayZ;
}

我们可以通过只计算一次来优化它,然后添加 dz/dx 以获得下一个像素的 z 值,或 dz/dy 获得下面的像素(y 轴向下)。这意味着我们显着减少了每个多边形的计算。

于 2014-10-15T09:50:05.340 回答
1
  1. 这一定很慢

    每次迭代/像素都进行了如此多的冗余计算,只是为了迭代它的坐标。您应该计算 3 个投影顶点并在​​它们之间进行迭代,而不是看这里:

  2. 我不喜欢你的zValueOfPoint功能

    在其中的主循环中找不到任何x,y坐标的使用,那么它如何正确计算 Z 值?

    或者它只是计算每个整个三角形的平均 Z 值?还是我错过了什么?(我自己不是JAVA编码器)无论如何,这似乎是你的主要问题。

    如果您的 Z 值计算错误,则Z-Buffer无法正常工作。要测试在渲染后将深度缓冲区视为图像,如果它不是阴影茶壶而是一些不连贯或恒定的混乱,那么很明显......

  3. Z 缓冲区实现

    看起来不错

[提示]

你有太多的时间术语,比如x + getWidth()/2为什么不对某个变量只计算一次?我知道现代编译器无论如何都应该这样做,但代码也会更具可读性和更短......至少对我来说

于 2014-10-15T08:05:42.207 回答