OpenGL ES 2.0读取深度缓冲区

7
据我所知,在OpenGL ES 2.0中,我们无法读取Z(depth)值。那么,我想知道如何从2D屏幕上的一个点获取3D世界坐标?
实际上,我有一些随机的想法可能会起作用。由于我们可以使用glReadPixels读取RGBA值,那么我们是否可以复制深度缓冲区并将其存储在颜色缓冲区中(例如ColorforDepth)?当然,需要一些良好的约定,以便不会丢失深度缓冲区的任何信息。然后,当我们需要一个点的世界坐标时,我们将这个ColorforDepth颜色缓冲区附加到帧缓冲区上,然后进行渲染。因此,当我们使用glReadPixels在此帧中读取深度信息时。
但是,这将导致1帧闪烁,因为colorbuffer是从深度缓冲区转换而来的奇怪缓冲区。我仍然想知道是否有一些标准方法可以在OpenGL ES 2.0中获取深度?
提前感谢您的回答! :)
4个回答

9
使用FBO,可以在不显示结果的情况下进行渲染。如果您使用的是ES 2.0,则片段着色器可以通过gl_FragCoord访问当前片段的深度(在窗口坐标中),因此您可以将其写入颜色缓冲区,使用glReadPixels获取结果并继续处理。或者,您可以将世界空间z作为变量加载,并从片段着色器中写入该变量,以便更轻松地解决问题。
为了验证自己的想法,请尝试编写一个快速着色器,以低精度放出gl_FragCoord.z,例如:
gl_FragColor = vec4(vec3(gl_FragCoord.z), 1.0);

你应该得到一种灰度图,颜色的强度代表深度。由于你处于窗口坐标系中,强度将从0.0(最近的未裁剪片段)到1.0(最远的未裁剪片段)范围内变化。为了不失去很多精度,最好将值在组件之间分割,因为你的供应商几乎肯定不支持浮点目标缓冲区。

1
Tommy,请问您能详细说明一下如何将 gl_FragCoord.z 分成多个部分吗?我没有理解。谢谢。 - dugla
2
如果深度是一个32位整数,你可以将其中的8位放入R、G、B和A中的每一位。所以实际上你会做的事情是乘以向量(1, 256, 65536, 16777216),然后将该向量的每个分量对1.0取模,并存储到相关的通道中。稍后你可以通过除以相关的分量和相加来重新组合。 - Tommy
@Tommy 很有趣!!你能详细说明一下vec4转换为float的反向过程吗?可以给个例子吗? - Chego

0

正如Tommy所指出的那样,将深度数据写入颜色缓冲区是可能的。我已经为24位深度编写了以下片段着色器:

float z = gl_FragCoord.z * 16777215.0; // * 0xFFFFFF

// gl_FragColor = vec4(float((z >> 0) & 0xFF) / 255.0, float((z >> 8) & 0xFF) / 255.0, float((z >> 16) & 0xFF) / 255.0, 1.0);
z = floor(z);
float c1 = mod(z, 256.0);
z -= c1;
z /= 256.0;
float c2 = mod(z, 256.0);
z -= c2;
z /= 256.0;
float c3 = mod(z, 256.0);
gl_FragColor = vec4(c1 / 255.0, c2 / 255.0, c3 / 255.0, 1.0);

然后将其反转

depth = 1.0 * (color[0] * 0x1 + color[1] * 0x100 + color[2] * 0x10000) / 0xFFFFFF

0

我使用基本的射线投射来从屏幕触摸中选择3D对象。实际上,我计算触摸点处屏幕法线与包含我的对象的球体之间的交点。对于非常精确的拾取或复杂的形状,您必须使用多个球体。

您还可以将对象的一些关键点投影到屏幕的2D空间(通过将您的3D点乘以变换矩阵),然后使用2D比较(距离)与触摸点进行比较。


0

我也想能够读取深度缓冲区的值,但研究表明这是不可能的。

正如文森特所建议的,如果你有简单的形状,比如球体,那么射线投射可能更好。

对于更复杂的形状,我考虑将对象渲染到一个(可能更小的)离屏缓冲区中,手动分配每个顶点的颜色分量之一为该顶点的深度,然后读取颜色值。这种方法有些不太优雅和麻烦,需要你能够将对象空间转换为屏幕空间(我使用自己的四元数来驱动矩阵,因此可以解决这个问题)。可能有一种方法可以使用着色器将深度信息写入颜色或模板缓冲区(GL ES甚至有模板缓冲区吗?)

如果有人有更简洁的方法,我很乐意听听。


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