如何在iOS中使用OpenGL ES正确线性化深度?

7
我正在尝试使用OpenGL为iOS应用程序渲染一幅森林场景。为了使其更美观,我想将深度效果应用于场景中。然而,为此我需要从OpenGL深度缓冲区获取一个线性化深度值。目前,我在片段着色器中使用一个计算公式(从这里找到)。
因此,我的地形片段着色器如下所示:
#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;

第二个输出结果

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

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


我在我的回答中添加了一个简单的着色器示例,作为解决此问题的Edit1。 - Spektre
1个回答

9
问题在于您存储的是非线性值,这些值会被截断,因此当您稍后查看深度值时,结果会变得不流畅,因为与znear平面越远,您失去了更多的精度。除非:

  1. 减少精度损失
    您可以更改znear,zfar值使它们更加接近。尽可能扩大znear,这样更精确的区域就可以覆盖您的场景。
    另一个选项是使用更多位的深度缓冲区(16位太低)。我不确定OpenGL ES是否支持此操作,但在标准OpenGL中,您可以在大多数显卡上使用24、32位。
  2. 使用线性深度缓冲区
    将线性值存储到深度缓冲区中。有两种方式。一种是在所有底层操作之后计算深度,以获得线性值。
    另一个选项是使用单独的纹理/FBO,并直接将线性深度存储到其中。问题是您无法在同一渲染过程中使用其内容。
[编辑1] 线性深度缓冲区
要使深度缓冲区本身变为线性(而不仅仅是从中读取的值),请尝试以下方法:
Vertex:
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;

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

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

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

为了简单起见,我使用了古老的东西,所以请将其移植到您的个人资料中...

请注意,我不会编写OpenGL ESIOS代码,因此希望我没有漏掉与此相关的内容(我习惯于Win和PC)。

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

intersect

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


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