WebGL screen to world coordinate
Asked Answered
E

1

0

I'm creating a 3D globe with elevation:

enter image description here

I now want to add click detection to get a 3D world xyz position from a 2D screen xy + depth (from a click). I'm using the following code (inspired from Getting World Position from Depth Buffer Value) to unproject the 2D coordinate + depth (using gl-matrix for computations) into 3D space:

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]);
}

Using this function I clicked on the south-east of France and plotted 1000 cubes having a depth in the [0 .. 1] range:

enter image description here

Without the sphere:

enter image description here

Cubes seems correctly displayed between my z-near & z-far planes, but the resolution near z-near is much higher than near z-far (cubes are not evenly distributed along the axis). If I reduce z-near, it worsen this effect (and at the extreme all the cubes are drawn on z-near). This is normal according to https://learnopengl.com/Advanced-OpenGL/Depth-testing , but may indicate a problem?

Empirically I found the depth value of the cube intersecting my sphere (the one in purple). I now need to get it from OpenGL depth buffer. I thus drawn my scene again with a simplified fragment shader encoding the depth (gl_FragCoord.z) into 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);
}

And decoded the value on JS side this way:

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);
}

I also written the encode method in JS to ensure that encoding / decoding a value is working, it does (with a little precision error, for instance if I encode / decode 0.962 I get 0.9619999999999997).

Using this method, the depth value retrieved from OpenGL is too small (in my example with a z-near of 1 & z-far of 500, I'm expecting ~0.962 and I have ~0.268).

Am I missing a transformation on my depth buffer value?

Evolution answered 2/6 at 11:40 Comment(0)
E
0

Alright, it was a silly mistake, I was not reading the right value in the buffer map (I forgot to reverse y).

The above code is working, although it seems I have a precision problem in the encode/decode of gl_FragCoord.z, when clicking on the mesh the point is placed just below it.

I fixed it for now by enabling EXT_color_buffer_float and EXT_float_blend extensions which allow me to directly read gl_FragCoord.z as a float, and now the points are correctly placed on the mesh.

Evolution answered 3/6 at 12:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.