I'm trying to port ShaderToy VR shaders to WebXR (the new browser API for AR/VR devices).

ShaderToy VR shaders expect two extra parameters: ray origin (view translation) and ray direction (unit vector from ray origin to each pixel). I could easily calculate this ray direction if I had fovX + fovY + distance to projection plane.

The problem: WebXR does not expose FOVs and instead just provides us with a projection matrix (apparently some devices might need more complex projection matrices with shear, roll, etc.) WebXR also exposes zNear and zFar, but that's all the frustum info available if I'm not mistaken.

Is there any way to obtain the zNear plane corners in world-space with this information?

  • 1
    \$\begingroup\$ Did you consider inverting the view-projection matrix and then multiplying it by the vectors representing the corners of the normalized device coordinates cube? \$\endgroup\$ – DMGregory Nov 5 '20 at 17:22
  • \$\begingroup\$ @DMGregory huh, I was thinking in screen space but didn't consider NDC (facepalm). \$\endgroup\$ – kaoD Nov 5 '20 at 17:27
  • 1
    \$\begingroup\$ If you're able to solve your problem that way, a code sample in an Answer could be helpful to future WebXR devs who get snagged on the same issue. \$\endgroup\$ – DMGregory Nov 5 '20 at 17:32
  • \$\begingroup\$ @DMGregory thanks, it worked as expected. I posted the code and explanation as an answer. \$\endgroup\$ – kaoD Nov 7 '20 at 11:10

Thanks @DMGregory for the pointer in the comments.

I'm constructing a fullscreen quad at (x, y, -1, 1) in NDC. Notice z = -1 is the near plane in NDC (left-handed unlike normal coordinate systems in OpenGL-land).

In the vertex shader:

attribute vec2 aVertexPosition;

uniform mat4 uInvViewMatrix;
uniform mat4 uInvProjMatrix;

varying vec3 vZNearPos;
varying vec3 vRayOrig;

void main() {
  // Vertices in the near plane in NDC
  gl_Position = vec4(aVertexPosition.xy, -1.0, 1.0);

  // Vertices in the near plane in world-space
  vec4 vZNearPosW = uInvViewMatrix * uInvProjMatrix * gl_Position;
  vZNearPos = vZNearPosW.xyz / vZNearPosW.w;

  // Ray origin in world-space
  // This could be a uniform but I was lazy :P
  vec4 rayOrigW = uInvViewMatrix * vec4(0, 0, 0, 1);
  vRayOrig = rayOrigW.xyz / rayOrigW.w;

The uniforms in JS:

Then in the fragment shader:

varying vec3 vZNearPos;
varying vec3 vRayOrig;

void main() {
  // I'm not sure if this can be safely interpolated per-vertex
  // so I kept in the fragment shader just in case
  vec3 rayDir = normalize(vZNearPos - vRayOrig);

  // Paint your VR here!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.