OpenGL重叠丑陋的渲染

OpenGL overlapping ugly rendering

我正在尝试使用 OpenGL 2.1 渲染场景,但重叠形状的边框很奇怪。我测试了一些 OpenGL 初始化但没有任何变化。我将我的问题简化为具有 2 个球体且结果相同的简单测试应用程序。

我尝试了一些关于 Gl_DEPTH_TEST、enable/disable 平滑的方法,但没有成功。

这是我使用 2 个 gluSphere 的结果:

当一条线足以分隔蓝色和红色面孔时,我们可以看到某种混叠...

我使用 SharpGL 但我认为它并不重要(因为我只将它用作 OpenGL 包装器)。这是我呈现相同内容的最简单代码(您可以将其复制到表单中进行测试):

OpenGL gl;
IntPtr hdc;
int cpt;

private void Init()
{
    cpt = 0;
    hdc = this.Handle;

    gl = new OpenGL();
    gl.Create(SharpGL.Version.OpenGLVersion.OpenGL2_1, RenderContextType.NativeWindow, 500, 500, 32, hdc);

    gl.Enable(OpenGL.GL_DEPTH_TEST);
    gl.DepthFunc(OpenGL.GL_LEQUAL);

    gl.ClearColor(1.0F, 1.0F, 1.0F, 0);
    gl.ClearDepth(1);

    gl.MatrixMode(OpenGL.GL_PROJECTION);
    gl.Perspective(30, 1, 0.1F, 1.0E+7F);

    gl.MatrixMode(OpenGL.GL_MODELVIEW);
    gl.LookAt(0, 3000, 0, 0, 0, 0, 0, 0, 1);
}

private void Render(int angle)
{
    gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT | OpenGL.GL_STENCIL_BUFFER_BIT);

    RenderSphere(gl, 0, 0, 0, 0, 300, Color.Red);
    RenderSphere(gl, 0, 0, 100, angle, 300, Color.Blue);

    gl.Blit(hdc);
}

private void RenderSphere(OpenGL gl, int x, int y, int z, int angle, int radius, Color col)
{
    IntPtr obj = gl.NewQuadric();

    gl.PushMatrix();
    gl.Translate(x, y, z);
    gl.Rotate(angle, 0, 0);

    gl.Color(new float[] { col.R / 255f, col.G / 255f, col.B / 255f, col.A / 255f });
    gl.QuadricDrawStyle(obj, OpenGL.GLU_FILL);
    gl.Sphere(obj, radius, 20, 10);

    gl.Color(new float[] { 0, 0, 0, 1 });
    gl.QuadricDrawStyle(obj, OpenGL.GLU_SILHOUETTE);
    gl.Sphere(obj, radius, 20, 10);

    gl.DeleteQuadric(obj);

    gl.PopMatrix();
}

提前感谢您的建议!

编辑:

我测试了但没有成功:

gl.Enable(OpenGL.GL_LINE_SMOOTH);
gl.Enable(OpenGL.GL_POLYGON_SMOOTH);
gl.ShadeModel(OpenGL.GL_SMOOTH);

gl.Hint(OpenGL.GL_LINE_SMOOTH_HINT, OpenGL.GL_NICEST);
gl.Hint(OpenGL.GL_POLYGON_SMOOTH_HINT, OpenGL.GL_NICEST);
gl.Hint(OpenGL.GL_PERSPECTIVE_CORRECTION_HINT, OpenGL.GL_NICEST);

EDIT2:有更多面孔,图像有线和无线

它……不同……但不讨人喜欢。

解决方案 1(不是很好的解决方案):将 gl.Scale(0.0001, 0.0001, 0.0001); 应用于 ModelView 矩阵

方案二:近平面要尽可能远,避免z值被压缩到很小的范围内。在这种情况下,使用 10 而不是 0.1 就足够了。最好的方法是根据对象距离计算一个自适应值(在这种情况下,最近的对象是 2700)

我认为我们可以关注@PikanshuKumar link 中的 z is stored non-linearly 和隐含的后果。

结果: 只有面被一条线切割:赤道处有一条直线作为分隔符。

当我们增加面数时,这些线条会按预期消失。

这个问题有两个原因。

第一个确实是Z-fighting问题,这是由近平面和远平面之间的巨大距离引起的

gl.Perspective(30, 1, 0.1F, 1.0E+7F);

并且在透视投影中,深度不是线性的。另见 How to render depth linearly ....

这可以通过将近平面尽可能靠近几何体来改善。由于到物体的距离为3000.0,球体的半径为300,所以近平面必须小于2700.0:

例如

gl.Perspective(30, 1, 2690.0F, 5000.0F);

第二个问题是由球体由三角形基元组成的事实引起的。正如您在回答中所建议的那样,您可以通过增加基元数量来改进它。

我将提供另一种解决方案,即使用裁剪平面。夹住底部的红色球体和顶部的蓝色球体。恰好在球体相交的平面上,以便从每个球体上切下一个帽。
裁剪平面可以通过 glClipPlane 设置并通过 glEnable.
启用 裁剪平面的参数被解释为 Plane Equation。 平面方程的前 3 个分量是剪切平面的法向量。第 4 个分量是到原点的距离。

因此,红色球体的裁剪平面方程必须是 {0, 0, -1, 50},而蓝色球体的剪平面方程必须是 {0, 0, 1, -50}
请注意,当调用 glClipPlane 时,方程将通过模型视图矩阵的逆矩阵进行变换。所以裁剪平面必须在旋转,平移和缩放等模型转换之前设置。

例如

private void Render(int angle)
{
    gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT | OpenGL.GL_STENCIL_BUFFER_BIT);

    double[] plane1 = new double[] {0, 0, -1, 50};
    RenderSphere(gl, 0, 0, 0, 0, 300, Color.Red, plane1);

    double[] plane2 = new double[] {0, 0, 1, -50};
    RenderSphere(gl, 0, 0, 100, angle, 300, Color.Blue, plane2);

    gl.Blit(hdc);
}
private void RenderSphere(
    OpenGL gl, int x, int y, int z, int angle, int radius,
    Color col, double[] plane)
{
    IntPtr obj = gl.NewQuadric();

    gl.ClipPlane(OpenGL.GL_CLIP_PLANE0, plane);
    gl.Enable(OpenGL.GL_CLIP_PLANE0);

    gl.PushMatrix();
    gl.Translate(x, y, z);
    gl.Rotate(angle, 0, 0);

    gl.Color(new float[] { col.R / 255f, col.G / 255f, col.B / 255f, col.A / 255f });
    gl.QuadricDrawStyle(obj, OpenGL.GLU_FILL);
    gl.Sphere(obj, radius, 20, 10);

    gl.Color(new float[] { 0, 0, 0, 1 });
    gl.QuadricDrawStyle(obj, OpenGL.GLU_SILHOUETTE);
    gl.Sphere(obj, radius, 20, 10);

    gl.DeleteQuadric(obj);

    gl.PopMatrix();

    gl.Disable(OpenGL.GL_CLIP_PLANE0);
}

您设置投影矩阵的方式正在扼杀深度缓冲精度

gl.MatrixMode(OpenGL.GL_PROJECTION);
gl.Perspective(30, 1, 0.1F, 1.0E+7F);

基本上这将几乎所有的深度缓冲区精度压缩到 0.1 到 0.2 左右的范围内(我没有做数学,只是在这里目测)。

一般来说,你应该选择近裁剪平面的距离尽可能远,仍然保持场景中的所有物体。远平面的距离并不重要(事实上,使用正确的矩阵魔法你可以把它放在无穷远处),但一般来说,让它尽可能靠近也是一个好主意。