我正在使用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);
}
错误绘制的茶壶示例:
我做错了什么?我觉得它一定是件小事。鉴于三角形绘制,我怀疑它是pointWithinTriangle方法。背面剔除似乎也能正常工作,所以我对此表示怀疑。对我来说最可能的罪魁祸首是zValueOfPoint方法,但我不知道它有什么问题。
答案 0 :(得分:1)
这一定非常慢
每次迭代/像素的冗余计算只是迭代其坐标。您应该计算3个投影顶点并在它们之间进行迭代,而不是在这里查看:
我不喜欢您的zValueOfPoint
功能
找不到主循环中x,y
坐标的任何使用,以便它如何正确计算Z值?
或者它只是计算每个三角形的平均Z值?还是我错过了什么? (不管是 JAVA 编码器),无论如何,这似乎是你的主要问题。
如果您的Z值计算错误,则 Z-Buffer 无法正常工作。 要测试渲染后将深度缓冲区视为图像,如果它没有阴影茶壶,而是一些不连贯或不断混乱,那么很明显......
Z缓冲区实施
看起来不错
<强> [提示] 强>
你有太多次像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轴下降)来优化它。这意味着我们显着减少了每个多边形的计算。