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

3
我正在使用LWJGL编写一个小程序生成低多边形场景。首先我渲染了一栋房子,然后是地形,最后是水。水最初是由一组平面顶点组成的。我在顶点着色器中使用Perlin噪声算法进行位移,以产生一些波浪(即我不能在特定点上获得实际水平面)。当我渲染场景时,观察到一些锯齿状的边缘。 从更远处观看情况更糟。 enter image description here 它们是什么?如何去除它们?很抱歉我无法上传代码,因为行数太多。
编辑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;

}

3
看起来像是Z缓冲冲突。你使用哪种投影矩阵? - BDL
1
是的,我同意重要的是Z-Buffer上有多少位和Z_near,Z_far值以及您的平均网格大小(三角形)。尝试降低Z_far / Z_near,例如增加几次Z_near应该会有所帮助。 - Spektre
我添加了我的投影矩阵代码。我记得它是从互联网上引用的。 - FunnyFunkyBuggy
如果这是由于z-fighting引起的,那么图像是否会闪烁?在我的情况下,“海岸线”并没有闪烁。它看起来像一个漂浮的锯齿形。 - FunnyFunkyBuggy
1
把自己放在摄像头前。最近的物体有多远?将近平面设置在那里左右。 - Ripi2
显示剩余2条评论
2个回答

4
问题是由于Z缓冲器存储的片段Z值精度较低。请查看您的数值:
private float NEAR_PLANE = .1f;
private float FAR_PLANE = 1000f;

可见深度范围为 z = < 0.1 , 1000.0 >,将其归一化到 < 0.0 , 1.0> 范围内。如果你的 像素格式 仅使用16位Z缓冲区(假设是线性Z缓冲区),则精度为 ~(1000.0/0.1)/2^16 = 0.6,这是最小的可能的Z步长。考虑到Z缓冲区的值非线性映射,当离Z_Near更远时,精度会更低。

要改善这个问题,你可以:

  1. 如果支持图形处理器,则将像素格式更改为32位用于深度缓冲区
  2. 降低 Zfar/Znear 比率

    Znear 更重要(因为Z值非线性)。越接近零,情况就越糟。如果你正在渲染以米为单位的对象,则没有必要使 z_near < 1.0

  3. 叠加更多透视体

    如果需要涵盖非常高的动态范围,则可以将场景渲染在超过一个透视体中。有关更多信息,请参见

    特别是 #1

  4. 使用线性 Z 缓冲区

    这可以通过着色器实现,摆脱非线性。


2
您正在遇到z-fighting问题。这意味着两个重叠三角形之间的z差非常小,而计算机只有有限的精度。因此,您失去了足够的深度精度来正确渲染场景。
造成这种情况的原因是您的投影近端z值非常接近。将其移动到1.0f会很有帮助。当然,如果您将远端z值移出到10,000,您将面临类似的问题(尽管不会像之前那样糟糕)。
如果您可以访问OpenGL 4.5或ARB_clip_control,则可以大大改善情况,而无需更改投影近/远值。但这需要执行三个操作:
  1. Rendering to a floating point depth buffer. That will generally require 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 format would be GL_DEPTH_COMPONENT32F or similar.

  2. Reverse your near/far values. Since floating-point values have more precision towards zero, and the depth function already biases precision towards the near values, by reversing your near/far range, you apply the floating-point precision to the far values rather than the already-precise near ones.

  3. Set your clip space to use a [0, 1] Z range:

    glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
    

    If you want the full details of what this does, along with all of the mathematics go here. To summarize, you're changing OpenGL's NDC-space Z range from [-1, 1] to [0, 1]. This affects both clipping and the window coordinate transform. By doing this, you avoid having the window coordinate transform perform a + 0.5f on the value, which would otherwise destroy your floating-point exponent. This makes the previous step actually helpful; without this, the previous step would accomplish nothing.

    Of course, you also need to change how you build your perspective projection matrix, since it was designed to go to a [-1, 1] range:

    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;
    

    Also, remember that you need to reverse the near and far z values you pass into this function.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接