This method belongs to geometric calculation. Ray is used to create Ray and geometry through an accelerated structure (BVH or kdTree), and finally intersects with a determined triangle. This method has the following advantages:Copy the code
  • The calculation result is accurate, and the accuracy of ray picking is very high because it belongs to geometric calculation
  • The detailed information returned can not only calculate the coordinates of the current pick point, but also return the mesh to which the current triangle belongs and the UV of the current point……
  • .

Another problem caused by high accuracy is high performance consumption. If a slightly complex scene is encountered, the whole calculation process will be time-consuming, and it is impossible to pick up coordinates during mouse movement. At the same time, since ray picking depends on geometric data, the geometry buffer data needs to be resident in memory. This leads to serious performance problems. For the conventional function of coordinate picker, there is no need to return too much information like ray picker in most cases, but only need to calculate coordinates. Therefore, we can calculate the world coordinates of deep acid reflux by the main method as follows: converting NDC coordinates to world coordinates

The main steps are as follows:

  1. Since it is the first step of depth coordinate picking to calculate depth, the key code is as follows
        let currentRenderTarget = pickRenderer.getRenderTarget();
        pickRenderer.getClearColor(oldClearColor);
        let oldClearAlpha = pickRenderer.getClearAlpha();
        let oldAutoAClear = pickRenderer.autoClear;
        let oldSceneEnv = scene.environment;
        let oldBackground = scene.background;
        pickRenderer.autoClear = false;
        pickRenderer.setClearColor(0x000000.1);
        scene.environment = undefined;
        scene.background = undefined;
        // Render under the depth material
        scene.overrideMaterial = this.depthMaterial;

        scene.frameState.passes.pick = true;
        scene.executeUpdate();

        this.pickScene.add(scene._renderCollection);

        pickRenderer.setSize(bufferSize.width, bufferSize.height);

        // The depth information will be saved to the target
        pickRenderer.setRenderTarget(pickTarget);

        pickRenderer.clear();
        pickRenderer.render(this.pickScene, scene.camera);
        pickRenderer.clear();

        pickRenderer.setRenderTarget(currentRenderTarget);
        scene.frameState.passes.pick = false;
        pickRenderer.setRenderTarget(currentRenderTarget);
        pickRenderer.setClearColor(oldClearColor);
        pickRenderer.setClearAlpha(oldClearAlpha);
        pickRenderer.autoClear = oldAutoAClear;
        scene.background = oldBackground;
        scene.environment = oldSceneEnv;
        scene.frameState.passes.pick = false;
        scene.overrideMaterial = null;
Copy the code

2. After calculating the depth, it is necessary to obtain the depth value of the click area. The key codes for calculating the depth according to the screen coordinates are as follows

        // Get the size
        let bufferSize = scene.drawingBufferSize;
        let pickRenderer = scene.renderer;
        let pickTarget = this.renderTarget;

        x = Math.max(defaultValue(x, 0), 0);
        // For the sake of the axes, notice that the y coordinate is also the flipY property of the renderTarget
        y = Math.max(defaultValue(bufferSize.height - y, 0), 0);
        
        // Read the color value, since it is rendered under the depth material, the color indicates the depth
        let pixels = new Float32Array(4);
        pickRenderer.readRenderTargetPixels(pickTarget, x, y, 1.1, pixels); ,let depth;
        // If depthMaterial pack is RGBADepthPacking
        if (this.pickDepth.depthMaterial.depthPacking === RGBADepthPacking) {
            let packedDepth = Vector4.unpack(pixels, 0, scratchPackedDepth);
            depth = packedDepth.dot(UnpackFactors) * 2 - 1;
        } else{
            depth = -pixels[0] * 2.0 + 1.0;
        } 
Copy the code

Vector4. Unpack method

((Vector4 as any).unpack = function(array: Number[], startingIndex: number, result: any) :Vector4 {
    Check.defined('array', array);

    if(! defined(result)) { result =new Vector4();
    }
    result.x = array[startingIndex++];
    result.y = array[startingIndex++];
    result.z = array[startingIndex++];
    result.w = array[startingIndex];
    return result;
});
Copy the code

3. Invert the world coordinates according to the depth

   scene.renderer.getCurrentViewport(viewport);

   let ndc = new Vector3();
   ndc.x = (drawingBufferPosition.x / viewport.width) * 2.0 - 1.0;
   ndc.y = -(drawingBufferPosition.y / viewport.height) * 2.0 + 1.0;
   ndc.z = depth;

   ndc.unproject(camera);

   if(! defined(result)) { result =new Vector3();
   }
   result.set(ndc.x, ndc.y, ndc.z);
Copy the code

Above methods there is a performance problem, that is, this step we obtain depth under the simple and crude the entire scene in the depth of material apply colours to a drawing again, but we finally you just need to get the depth of the specified screen coordinates, so you just need to render click there is a part of the pixel area, this part can go to optimization, function when we were doing, Getting depth is a very common feature, so you can tailor your performance to your business scenario, or make sure you update only a minimum range of pixels