three.js Object Picking Object Picking / GPU


Example

Object picking using Raycasting might be a heavy task for your CPU depending on your setup (for example if you don't have an octree like setup) and number of objects in the scene.

If you don't need the world coordinates under the mouse cursor but only to identify the object under it you can use GPU picking.

Short explanation, GPU can be a powerful tool for computation but you need to know how to get the results back. The idea is, if you render the objects with a color that represents their id, you can read the color of the pixel under the cursor and findout the id of the object that is picked. Remember RGB is just a hex value so there is a conversion exists between id (integer) and color (hex).

  1. Create a new scene and a new rendering target for your object
var pickingScene = new THREE.Scene();
var pickingTexture = new THREE.WebGLRenderTarget(renderer.domElement.clientWidth, renderer.domElement.clientHeight);
    pickingTexture.texture.minFilter = THREE.LinearFilter;
  1. Create a new shader Material for object picking;
var vs3D = `
attribute vec3 idcolor;
varying vec3 vidcolor;
void main(){
vidcolor = idcolor;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0);
}`;

var fs3D = `
varying vec3 vidcolor;
void main(void) {
gl_FragColor = vec4(vidcolor,1.0);
}`;

var pickingMaterial = new THREE.ShaderMaterial(
    {
        vertexShader: vs3D,
        fragmentShader: fs3D,
        transparent: false,
        side: THREE.DoubleSide
    });
  1. Add your mesh/line geometries a new attribute that represents their id in RGB, create the pickingObject using the same geometry and add it to the picking scene, and add the actual mesh to a id->object dictionary
var selectionObjects = [];

for(var i=0; i<myMeshes.length; i++){
    var mesh = myMeshes[i];
    var positions = mesh.geometry.attributes["position"].array;
    var idColor = new Float32Array(positions.length);

    var color = new THREE.Color();
    color.setHex(mesh.id);

    for (var j=0; j< positions.length; j+=3){
        idColor[j] = color.r;
        idColor[j+1] = color.g;
        idColor[j+2] = color.b;
    }

    mesh.geometry.addAttribute('idcolor', new THREE.BufferAttribute(idColor, 3));

    var pickingObject = new THREE.Mesh(mesh.geometry, pickingMaterial);
    
    pickingScene.add(pickingObject);
    selectionObjects[mesh.id] = mesh;
}
  1. Finally, on your mouse click handler
renderer.render(pickingScene, camera, pickingTexture);
var pixelBuffer = new Uint8Array(4);
renderer.readRenderTargetPixels(pickingTexture, event.pageX, pickingTexture.height - event.pageY, 1, 1, pixelBuffer);
var id = (pixelBuffer[0] << 16) | (pixelBuffer[1] << 8) | (pixelBuffer[2]);

if (id>0){
    //this is the id of the picked object
}else{
    //it's 0. clicked on an empty space
}