OpenGL重叠丑陋渲染

5

我试图使用OpenGL 2.1渲染场景,但重叠形状的边界很奇怪。我尝试了一些OpenGL初始化,但没有任何改变。我将我的问题简化为只有两个球体的简单测试应用程序,并出现了相同的结果。

我尝试了一些关于Gl_DEPTH_TEST、启用/禁用平滑处理的方法,但都没有成功。

这是我使用两个gluSphere得到的结果:

enter image description here

当一条线足以分开蓝色和红色表面时,我们可以看到一些锯齿...

我使用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);

编辑2:使用更多的面孔,有线和无线图像

在此输入图片描述

它是...不同的...但不令人愉悦。


@Rabbid76 那么答案就是增加渲染面的数量,直到边界看起来像一条线,对吗? - YesThatIsMyName
2
这是一种 Z-Fighting 的情况。请参考链接 - Pikanshu Kumar
在@PikanshuKumar的链接中,有一句话似乎非常重要:“z以非线性方式存储”。使用比300小得多的因子(半径为0.003而不是300),可以得到预期的结果。我将在我的主应用程序中处理它(我将在所有地方应用一个因子……),并回来反馈。 - KiwiJaune
2
@KiwiJaune 试着降低近平面和远平面之间的比率,例如 gl.Perspective(30, 1, 0.1F, 1.0E+7F); -> gl.Perspective(30, 1, 1.0F, 1.0E+7F); (从1:1e8到1:1e7)。 - Andreas
1
重点在于深度缓冲区(其精度、远近平面)和 Z-Fighting: https://learnopengl.com/Advanced-OpenGL/Depth-testing - L.C.
显示剩余6条评论
3个回答

3

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

解决方案2:近平面必须尽可能远,以避免在一个小范围内压缩z值。在这种情况下,使用10而不是0.1就足够了。最好根据物体距离计算出一个适合的值(在这种情况下,最近的物体距离为2700)。

我认为我们可以关注@PikanshuKumar链接中z存储非线性和隐含的结果。

结果: 只有面部被一条线切开:赤道有一条直线作为分隔器。 enter image description here

当我们增加面数时,这些线会如预期般消失。enter image description here


3
问题有两个原因。
第一个原因确实是 Z-fighting问题,这是由于近平面和远平面之间的巨大距离引起的。
gl.Perspective(30, 1, 0.1F, 1.0E+7F);

而且在透视投影中,深度不是线性的。另请参见如何线性渲染深度...

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

例如:

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

第二个问题是由于球体由三角形基元组成。正如您在答案中所建议的那样,您可以通过增加基元数量来改善它。
我将提供一种替代方案,使用剪切平面。在底部剪切红色球体,在顶部剪切蓝色球体。恰好在球体相交的平面上,从每个球体上截取一个盖子。 可以通过 glClipPlane 来设置剪切平面,并通过 glEnable 启用。 剪切平面的参数被解释为 平面方程式。 平面方程式的前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);
}

感谢您的回答。第一部分很有趣(并且足够),但是裁剪不是真正的解决方案,因为您试图专门解决这两个球之间的问题,而这只是一个简单的示例,以突出在更复杂的应用程序中遇到的问题。 - KiwiJaune
@KiwiJaune 好的,我明白了。 - Rabbid76

2

你设置投影矩阵的方式会降低深度缓冲精度

最初的回答

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

基本上,这将几乎所有深度缓冲精度压缩到0.1到0.2左右的范围内(我没有计算,只是在这里眼测)。
通常情况下,应该选择近裁剪平面的距离尽可能远,同时保持场景中的所有对象。远裁剪平面的距离并不那么重要(实际上,通过正确的矩阵变换,您可以将其放置在无限远处),但通常也最好尽可能靠近。

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