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

Ase*_*der 3 opengl-es glsl objective-c ios

我正在尝试使用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);
}
Run Code Online (Sandbox Code Playgroud)

但是,这将导致以下输出:

结果输出 如您所见,越远,您得到的深度值越“条纹”(尤其是在船后)。如果地形图块靠近相机,那么输出就可以了。

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

float linearizeDepth(float depth) {
    return 2.0 * nearz * farz / (farz + nearz - (2.0 * depth - 1.0) * (farz - nearz));
}
Run Code Online (Sandbox Code Playgroud)

这导致价值过高,因此我将其除以以下比例:

float linearized = (linearizeDepth(depth) - 2.0) / 40.0;
Run Code Online (Sandbox Code Playgroud)

第二结果输出

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

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

Spe*_*tre 5

问题是您存储了被截断的非线性值,因此当您稍后查看深度值时,会得到断断续续的结果,因为您失去的精确度越是远离znear平面。不管您如何评价,除非:

  1. 降低精度损失

    您可以更改znear,zfar值,使它们靠得更近。尽可能扩大znear,以便更精确的区域覆盖更多场景。

    另一个选择是每个深度缓冲区使用更多位(16位太低),不确定在OpenGL ES中是否可以这样做,但是在标准OpenGL中,大多数卡上可以使用24.32位。

  2. 使用线性深度缓冲区

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

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

[Edit1]线性深度缓冲区

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

顶点:

varying float depth;
void main()
    {
    vec4 p=ftransform();
    depth=p.z;
    gl_Position=p;
    gl_FrontColor = gl_Color;
    }
Run Code Online (Sandbox Code Playgroud)

分段:

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;
    }
Run Code Online (Sandbox Code Playgroud)

在CPU侧线性化了非线性深度缓冲区(与您一样): 中央处理器

线性深度缓冲区GPU端(如您所愿): 显卡

场景参数为:

// 24 bits per Depth value
const double zang =   60.0;
const double znear=    0.01;
const double zfar =20000.0;
Run Code Online (Sandbox Code Playgroud)

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

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

z = znear + (zfar-znear)*depth_value;
Run Code Online (Sandbox Code Playgroud)

我使用古老的东西只是为了保持简单,所以将其移植到您的个人资料中...

请注意,我不会在OpenGL ESIOS编写代码,所以我希望我不要错过与此相关的内容(我习惯于Win和PC)。

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

相交

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