OpenGL 中不需要的三角形 --- Z 冲突?

Unwanted triangles in OpenGL --- Z-fighting?

我目前正在编写一个使用 LWJGL 生成低多边形场景的小程序。我首先渲染了一座房子。然后我渲染了地形。最后,我渲染了水。水原本是一个平面的顶点。我使用 Perlin 噪声算法在顶点着色器中做了一些位移以生成一些波(即我无法获得特定点的实际水位)。当我渲染场景时,我观察到一些锯齿状的边缘。

从更远的距离观看时,情况更糟。

它们是什么?我怎样才能删除它们?对不起,我无法上传代码。因为行数太多了。

编辑 1:

private float FOV = 70;
private float NEAR_PLANE = .1f;
private float FAR_PLANE = 1000f;

private void createProjectionMatrix() {

    float aspectRatio = (float) Display.getWidth() / (float) Display.getHeight();
    float y_scale = (float) ( (1f/ Math.tan(FOV/2f))*aspectRatio );
    float x_scale = y_scale / aspectRatio;
    float frustum_length = FAR_PLANE - NEAR_PLANE;

    projectionMatrix = new Matrix4f();
    projectionMatrix.m00 = x_scale;
    projectionMatrix.m11 = y_scale;
    projectionMatrix.m22 = -((FAR_PLANE + NEAR_PLANE) / frustum_length);
    projectionMatrix.m23 = -1;
    projectionMatrix.m32 = -((2*NEAR_PLANE * FAR_PLANE) / frustum_length);
    projectionMatrix.m33 = 0;

}

您正在获得 z-fighting。这意味着两个重叠三角形之间的 z 差值非常接近,而计算机的精度只有这么多。所以你失去了足够的深度精度来正确渲染你的场景。

原因是您的投影 near-z 非常接近。将其移至 1.0f 会有所帮助。当然,如果您将 far-z 移至 10,000,您将遇到类似的问题(尽管没有那么严重)。

如果您可以访问 OpenGL 4.5 或 ARB_clip_control,您可以 极大地 改善这种情况而无需更改投影 near/far 值。但这需要做三件事:

  1. 渲染到浮点深度缓冲区。这通常需要 rendering to an FBO (I don't know of a way to create a default framebuffer with a floating-point depth format). The depth attachment formatGL_DEPTH_COMPONENT32F 或类似的。

  2. 颠倒你的 near/far 价值观。由于 floating-point 值具有更接近零的精度,并且深度函数已经将精度偏向近值,因此通过反转 near/far 范围,您将 floating-point 精度应用于远值而不是already-precise 附近的。

  3. 设置剪辑 space 使用 [0, 1] Z 范围:

    glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
    

    如果您想了解其作用的全部细节,以及所有数学知识 go here。总而言之,您正在将 OpenGL 的 NDC-space Z 范围从 [-1, 1] 更改为 [0, 1]。这会影响剪裁和 window 坐标变换。通过这样做,您可以避免让 window 坐标变换对值执行 + 0.5f,否则会破坏您的 floating-point 指数。这使得上一步实际上很有帮助;没有这个,前面的步骤将一事无成。

    当然,您还需要更改构建透视投影矩阵的方式,因为它被设计为进入 [-1, 1] 范围:

    projectionMatrix = new Matrix4f();
    projectionMatrix.m00 = x_scale;
    projectionMatrix.m11 = y_scale;
    projectionMatrix.m22 = -(NEAR_PLANE / frustum_length);
    projectionMatrix.m23 = -1;
    projectionMatrix.m32 = -((NEAR_PLANE * FAR_PLANE) / frustum_length);
    projectionMatrix.m33 = 0;
    

    此外,请记住,您需要反转传入此函数的近距和远距 z 值。

问题是由于 Z-buffer 存储的片段的 Z 值精度低。看看你的价值观:

private float NEAR_PLANE = .1f;
private float FAR_PLANE = 1000f;

所以可见深度范围是 z = < 0.1 , 1000.0 >,这是归一化到 < 0.0 , 1.0> 范围。现在,如果你的 pixelformat 只使用 16 位 Z-buffer 那么(假设线性 Z-buffer 不是这种情况)你得到的精度 ~(1000.0/0.1)/2^16 = 0.6 这是可能的最小Z-step。现在,如果您考虑到 Z-buffer 值是非线性映射的,那么离您的 Z_Near 越远,精度就会更低。

要改善这一点,您可以:

  1. 如果 gfx 支持,将深度缓冲区的像素格式更改为 32bits
  2. 降低Zfar/Znear比率

    Znear 更重要(由于非线性 Z 值)。越接近零,事情就越糟糕。如果您正在渲染分辨率为米的对象,那么 z_near < 1.0

  3. 就没有意义了
  4. 堆叠更多的截头体

    如果您需要覆盖非常高的动态范围,您可以在不止一次通过的情况下渲染场景。有关详细信息,请参阅

    特别是子弹#1

  5. 使用线性Z-buffer

    这可以通过着色器消除非线性来完成。