Z缓冲算法未正确绘制100%

时间:2014-10-14 23:29:10

标签: java algorithm graphics 3d zbuffer

我正在使用Java编写软件渲染器,并尝试使用Z缓冲来计算每个像素的深度。但是,它似乎工作不一致。例如,使用犹他茶壶示例模型,手柄将根据我旋转的方式绘制一半。

我的z-buffer算法:

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);
}

错误绘制的茶壶示例:

enter image description here

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

2 个答案:

答案 0 :(得分:1)

  1. 这一定非常慢

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

  2. 我不喜欢您的zValueOfPoint功能

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

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

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

  3. Z缓冲区实施

    看起来不错

  4. <强> [提示]

    你有太多次像x + getWidth()/2这样的术语,为什么不将它们只计算一次变量?我知道现代编译器应该这样做,但代码也会更可读,更短......至少对我而言

答案 1 :(得分:1)

我的zValueOfPoint方法无法正常工作。我不确定为什么:(但是,我更改为一种稍微不同的方法来计算平面中某个点的值,在此处找到:http://forum.devmaster.net/t/interpolation-on-a-3d-triangle-using-normals/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

然后,从上面的链接复制,我们做: &#34;注意,对于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值可以计算为:&#34;

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轴下降)来优化它。这意味着我们显着减少了每个多边形的计算。