2

What's the best way to lock the camera position to a single point in WebVR/WebXR using three.js?

The user would still need to be able to rotate their head, but their head movement shouldn't change the position (x,y,z) of the camera.

rvgmiller
  • 23
  • 3

2 Answers2

1

See update for simple solution

This ability was misguidedly, and explicitly removed from the WebXR spec.

Their "trivial" stripping of positional data example is in their 360-photos.html example, and it's skyboxMaterial class's vertex shader that's eaten by their convoluted renderer.

specifically:

get vertexSource() {
    return `
    uniform int EYE_INDEX;
    uniform vec4 texCoordScaleOffset[2];
    attribute vec3 POSITION;
    attribute vec2 TEXCOORD_0;
    varying vec2 vTexCoord;

    vec4 vertex_main(mat4 proj, mat4 view, mat4 model) {
      vec4 scaleOffset = texCoordScaleOffset[EYE_INDEX];
      vTexCoord = (TEXCOORD_0 * scaleOffset.xy) + scaleOffset.zw;
      // Drop the translation portion of the view matrix
      view[3].xyz = vec3(0.0, 0.0, 0.0);
      vec4 out_vec = proj * view * model * vec4(POSITION, 1.0);

      // Returning the W component for both Z and W forces the geometry depth to
      // the far plane. When combined with a depth func of LEQUAL this makes the
      // sky write to any depth fragment that has not been written to yet.
      return out_vec.xyww;
    }`;
  }

Nice and trivial... /s

Hopefully this helps, I'm currently working through the same issue, and if/when I overcome it, I'll update this answer.

UPDATE 2: As promised, instead of modifying each shader to support this capability. Do the following when processing each xrPose's view:

    //NOTE: Uses the gl-matrix.js library, albeit slightly modified
    //to add vec3.multiplyBy. Which is used to multiply a vector
    //by a single value.
    
    let dist;
    let poseMaxDist = 0.4; //0.4M or 1.2ft
    
    let calculatedViewPos;
    let viewRotAsQuat;
    let vector;

    let origin = vec3.create();
    let framePose = vec3.create();
    let poseToBounds = vec3.create();
    let elasticTransformMatrix = mat4.create();

    let view = pose.views[viewIdx];
    //If positionDisabled, negate headset position changes, while maintaining
    //eye offset which allows for limited translation as users head does
    //move laterally when looking around.
    if(_positionDisabled){
        //DOMPoint to vec3 easier calculations.
        framePose = vec3.fromValues(
            pose.transform.position.x,
            pose.transform.position.y,
            pose.transform.position.z);

        //Distance from the origin
        dist = vec3.distance(origin, framePose);

        if(dist >= poseMaxDist){
            //calculation 'origin' == A
            //framePose == B
            let AB = vec3.create();
            let AC = vec3.create();
            let C = vec3.create();
            let CB = vec3.create();

            //Vector from origin to pose
            vec3.subtract(AB, framePose, origin);

            //Unit vector from origin to pose
            vec3.normalize(AB, AB);

            //Max allowed vector from origin to pose
            vec3.multiplyBy(AC, AB, poseMaxDist);

            //Limit point from origin to pose using max allowed vector  
            vec3.add(C, origin, AC);
      
            //vector from pose to limit point, use to shift view
            vec3.subtract(poseToBounds, C, framePose);

            //vector from limit point to pose, use to shift origin
            vec3.subtract(CB, framePose, C);

            //Shift calculation 'origin'
            vec3.add(origin, origin, CB);

            //adjust view matrix using the caluclated origin,
            //and the vector from the pose to the limit point.
            calculatedViewPos = vec4.fromValues(
                view.transform.position.x - origin[0] + poseToBounds[0],
                view.transform.position.y - origin[1] + poseToBounds[1],
                view.transform.position.z - origin[2] + poseToBounds[2],
                view.transform.position.w);

        }else{
            //adjust view matrix using the caluclated origin
            calculatedViewPos = vec4.fromValues(
                view.transform.position.x - origin[0],
                view.transform.position.y - origin[1],
                view.transform.position.z - origin[2],
                view.transform.position.w);
        }

        //Changing the DOMPoint to a quat for easier matrix calculation.
        viewRotAsQuat = quat.fromValues(
            view.transform.orientation.x,
            view.transform.orientation.y,
            view.transform.orientation.z,
            view.transform.orientation.w
        );

        mat4.fromRotationTranslation(elasticTransformMatrix, viewRotAsQuat, calculatedViewPos)

        mat4.invert(elasticTransformMatrix, elasticTransformMatrix);
            
        mat4.multiply(modelViewMatrix, elasticTransformMatrix, entity.transformMatrix);

    }else{
        mat4.multiply(modelViewMatrix, view.transform.inverse.matrix, entity.transformMatrix);
    }

FYI: you will want to optimize the variable use to avoid extraneous allocations. I left them in to better visualize what each calculation is using on.

Reahreic
  • 596
  • 2
  • 7
  • 26
  • Brilliant, very much appreciated. I'm not in a position to look into this right now, but will when I get a chance. I hope others find this useful. It is strange that it was removed from the WebXR spec when photospheres seem to be quite a common use-case. – rvgmiller Jun 14 '21 at 10:57
0

You can set the camera's position xyz values to zero on every frame to lock it in place. However, please be aware that this is uncomfortable for many users when viewing actual 3D scenes. It's essentially only useful for 180/360 degree video viewers where the source material doesn't support spatial movement, but even in that case you should use the head position if there are any floating UI elements for interactions.

klausw
  • 336
  • 1
  • 4
  • Thank you, this may be a basic question, but how would I do that? I've found that once WebVR is initiated, changing the camera position with something like camera.position.y = 0 has no effect. – rvgmiller Jan 03 '20 at 09:05
  • To revisit this, how would you manually change the position of the camera on every frame during a VR session? The position from the HMD seems to override anything I try. – rvgmiller Apr 10 '20 at 08:38
  • 1
    I fully understand, and 100% agree that for anything other than video/photospheres the camera should be allowed it's freedom. That said, I'm specifically working on video/photospheres for this project. (Although I plan on allowing some elastic head movement of about 6-12" so that turning one's head allows for the body's slight natural translation, while not allowing the user to lean all the way to the edge of the sphere.) – Reahreic May 05 '21 at 14:21