如何在iOS中正确地线性化OpenGL ES中的深度?

时间:2017-02-28 13:20:18

标签: ios objective-c opengl-es glsl

我正在尝试使用OpenGL为iOS应用程序渲染forrest场景。为了使它更好一些,我想在场景中实现深度效果。但是我需要OpenGL深度缓冲区的线性化深度值才能这样做。目前我在片段着色器中使用了一个计算(我发现here)。

因此我的地形片段着色器看起来像这样:

#version 300 es

precision mediump float;
layout(location = 0) out lowp vec4 out_color;

float linearizeDepth(float depth) {
    return 2.0 * nearz / (farz + nearz - depth * (farz - nearz));
}

void main(void) {
    float depth = gl_FragCoord.z;
    float linearized = (linearizeDepth(depth));
    out_color = vec4(linearized, linearized, linearized, 1.0);
}

但是,这会产生以下输出:

resulting output 正如你所看到的,你越远“越远”,得到的深度值就越“条纹”(特别是在船后面)。如果地形图块靠近相机,输出就可以了。

我甚至尝试了另一种计算:

float linearizeDepth(float depth) {
    return 2.0 * nearz * farz / (farz + nearz - (2.0 * depth - 1.0) * (farz - nearz));
}

这导致了一个太高的价值,所以我通过划分来缩小它:

float linearized = (linearizeDepth(depth) - 2.0) / 40.0;

second resulting output

然而,它给出了类似的结果。

那么如何在近平面和远平面之间实现平滑,线性的过渡,没有任何条纹?有没有人有类似的问题?

1 个答案:

答案 0 :(得分:4)

问题是你存储了被截断的非线性值,所以当你稍后偷看深度值时,你会得到不稳定的结果,因为你失去了准确性,你离znear平面越远。无论你评价什么,你都不会得到更好的结果,除非:

  1. 精度下降

    您可以更改znear,zfar值,使它们更加接近。尽可能多地放大znear,以便更准确的区域覆盖更多的场景。

    另一个选择是每个深度缓冲区使用更多位(16位太低)不确定是否可以在OpenGL ES中执行此操作,但在标准OpenGL中,您可以在大多数卡上使用24,32位。

  2. 使用线性深度缓冲

    因此将线性值存储到深度缓冲区中。有两种方法。一个是计算深度,因此在所有基础操作之后,您将获得线性值。

    另一种选择是使用单独的纹理/ FBO并将线性深度直接存储到它。问题是你不能在同一个渲染过程中使用它的内容。

  3. [Edit1]线性深度缓冲区

    要线性化深度缓冲区本身(不仅仅是从中获取的值),请尝试:

    <强>顶点:

    varying float depth;
    void main()
        {
        vec4 p=ftransform();
        depth=p.z;
        gl_Position=p;
        gl_FrontColor = gl_Color;
        }
    

    <强>片段:

    uniform float znear,zfar;
    varying float depth; // original z in camera space instead of gl_FragCoord.z because is already truncated
    void main(void)
        {
        float z=(depth-znear)/(zfar-znear);
        gl_FragDepth=z;
        gl_FragColor=gl_Color;
        }
    

    非线性深度缓冲区在CPU端线性化(与您一样): CPU

    线性深度缓冲GPU侧(如您所愿): GPU

    场景参数为:

    // 24 bits per Depth value
    const double zang =   60.0;
    const double znear=    0.01;
    const double zfar =20000.0;
    

    和简单的旋转板覆盖整个深度视野。展位图像由glReadPixels(0,0,scr.xs,scr.ys,GL_DEPTH_COMPONENT,GL_FLOAT,zed);拍摄,并在 CPU 侧转换为 2D RGB 纹理。然后渲染为单个QUAD覆盖单位矩阵上的整个屏幕...

    现在要从线性深度缓冲区获取原始深度值,您只需执行以下操作:

    z = znear + (zfar-znear)*depth_value;
    

    我使用古老的东西只是为了保持这个简单,所以把它移到你的个人资料......

    注意我不会在 OpenGL ES 中编码,也不会在 IOS 中编码,所以我希望我没有错过与此相关的内容(我习惯于Win和PC)。

    为了显示差异,我将另一个旋转的平板添加到同一场景(因此它们相交)并使用彩色输出(不再获取深度):

    intersect

    正如您所见,线性深度缓冲区要好得多(对于覆盖大部分深度FOV的场景)。