我正在创建一个具有高度的 3D 地球仪:
我现在想要添加点击检测,以从 2D 屏幕 xy + 深度(通过点击)获取 3D 世界 xyz 位置。我使用以下代码(灵感来自从深度缓冲区值获取世界位置)将 2D 坐标 + 深度(使用 gl-matrix 进行计算)投影到 3D 空间中:
static unProject(screenX: number, screenY: number, depth: number, projMatrix: mat4, viewMatrix: mat4): vec3 {
// Convert x, y, depth from [0 .. 1] to NDC [-1 .. 1]
const clipSpacePosition: vec4 = vec4.fromValues(
screenX * 2.0 - 1.0,
screenY * 2.0 - 1.0,
depth * 2.0 - 1.0,
1.0
);
let viewSpacePosition: vec4 = vec4.transformMat4(vec4.create(), clipSpacePosition, mat4.invert(mat4.create(), projMatrix));
// Normalize eye coordinates
viewSpacePosition[0] /= viewSpacePosition[3];
viewSpacePosition[1] /= viewSpacePosition[3];
viewSpacePosition[2] /= viewSpacePosition[3];
viewSpacePosition[3] /= viewSpacePosition[3];
const worldSpacePosition: vec4 = vec4.transformMat4(vec4.create(), viewSpacePosition, mat4.invert(mat4.create(), viewMatrix));
// Normalize world coordinates
worldSpacePosition[0] /= worldSpacePosition[3];
worldSpacePosition[1] /= worldSpacePosition[3];
worldSpacePosition[2] /= worldSpacePosition[3];
return vec3.fromValues(worldSpacePosition[0], worldSpacePosition[1], worldSpacePosition[2]);
}
使用此函数,我单击了法国东南部并绘制了 1000 个深度在 [0 .. 1] 范围内的立方体:
没有球体:
立方体似乎在我的 z 近平面和 z 远平面之间正确显示,但 z 近平面附近的分辨率远高于 z 远平面附近的分辨率(立方体沿轴分布不均匀)。如果我减少 z 轴附近,则会恶化这种效果(并且在极端情况下,所有立方体都绘制在 z 轴附近)。根据https://learnopengl.com/Advanced-OpenGL/Depth-testing,这是正常的,但可能表明存在问题?
根据经验,我找到了与我的球体相交的立方体的深度值(紫色的那个)。我现在需要从 OpenGL 深度缓冲区获取它。因此,我使用简化的片段着色器再次绘制了场景,将深度 (gl_FragCoord.z) 编码为 RGBA:
#version 300 es
precision highp float;
vec4 encode(float depth) {
vec4 bitSh = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);
vec4 bitMsk = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
vec4 enc = fract(depth * bitSh);
enc -= enc.xxyz * bitMsk;
return enc;
}
void main(void) {
color = encode(gl_FragCoord.z);
}
并以这种方式解码 JS 端的值:
decode(color: vec4) {
const bitSh: vec4 = vec4.fromValues(1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0);
return vec4.dot(color, bitSh);
}
我还在JS中编写了encode方法,以确保编码/解码一个值正常工作,它确实有效(有一点精度误差,例如,如果我编码/解码0.962,我得到0.9619999999999997)。
使用此方法,从 OpenGL 检索到的深度值太小(在我的示例中,z-near 为 1,z-far 为 500,我预计约为 0.962,实际结果约为 0.268)。
我是否缺少深度缓冲区值的转换?
好吧,这是一个愚蠢的错误,我没有读取缓冲区映射中的正确值(我忘记反转 y)。
上面的代码正在工作,尽管我在 gl_FragCoord.z 的编码/解码中似乎存在精度问题,当单击网格时,该点放置在它的正下方。
我现在通过启用 EXT_color_buffer_float 和 EXT_float_blend 扩展来修复它,这允许我直接将 gl_FragCoord.z 作为浮点数读取,现在这些点已正确放置在网格上。