I'm working on a webgl scene in which I use "GPU picking" to determine which point the user is currently hovering on:
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 100000);
camera.position.set(0, 1, -600);
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.zoomSpeed = 0.4;
controls.panSpeed = 0.4;
// draw points
var geometry = new THREE.InstancedBufferGeometry();
var BA = THREE.BufferAttribute;
var IBA = THREE.InstancedBufferAttribute;
// add data for each observation
var n = 10000; // number of observations
var rootN = n**(1/2);
var unit = 0;
var cellSize = 20;
var color = new THREE.Color();
var translations = new Float32Array( n * 3 );
var colors = new Float32Array( n * 3 );
var translationIterator = 0;
var colorIterator = 0;
for (var i=0; i<n; i++) {
var rgb = color.setHex(i+1);
translations[translationIterator++] = (i % rootN) * cellSize;
translations[translationIterator++] = Math.floor(i / rootN) * cellSize;
translations[translationIterator++] = 0;
colors[colorIterator++] = rgb.r;
colors[colorIterator++] = rgb.g;
colors[colorIterator++] = rgb.b;
}
// add picking scene
var w = window.innerWidth;
var h = window.innerHeight;
var pickingScene = new THREE.Scene();
pickingTexture = new THREE.WebGLRenderTarget(w, h);
pickingTexture.texture.minFilter = THREE.LinearFilter;
var mouse = new THREE.Vector2();
var canvas = document.querySelector('canvas');
canvas.addEventListener('mousemove', function(e) {
renderer.render(pickingScene, camera, pickingTexture);
var pixelBuffer = new Uint8Array(4);
renderer.readRenderTargetPixels(
pickingTexture, e.clientX, pickingTexture.height - e.clientY,
1, 1, pixelBuffer );
var id = (pixelBuffer[0]<<16)|(pixelBuffer[1]<<8)|(pixelBuffer[2]) || '';
document.querySelector('#selected').textContent = id
? 'You are hovering on element number ' + (id-1)
: ''
})
// add rendered scene
var positionAttr = new BA( new Float32Array( [0, 0, 0] ), 3);
var translationAttr = new IBA(translations, 3, 1);
var colorAttr = new IBA(colors, 3, 1);
geometry.addAttribute('position', positionAttr);
geometry.addAttribute('translation', translationAttr);
geometry.addAttribute('color', colorAttr);
var material = new THREE.RawShaderMaterial({
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent,
});
var mesh = new THREE.Points(geometry, material);
mesh.frustumCulled = false; // prevent the mesh from being clipped on drag
scene.add(mesh);
pickingScene.add(mesh.clone());
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
controls.update();
};
window.addEventListener('resize', function() {
var w = window.innerWidth;
var h = window.innerHeight;
camera.aspect = w/h;
renderer.setSize(w,h);
})
render();
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
#selected { position: absolute; top: 10; left: 10; font-size: 40; color: black; background: #fff;}
<body>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.min.js'></script>
<script src='https://rawgit.com/YaleDHLab/pix-plot/master/assets/js/trackball-controls.js'></script>
<div id='selected'></div>
<script type='x-shader/x-vertex' id='vertex-shader'>
precision mediump float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
attribute vec3 position; // blueprint's vertex positions
attribute vec3 color; // only used for raycasting
attribute vec3 translation; // x y translation offsets for an instance
varying vec3 vColor;
void main() {
vColor = color;
vec3 pos = position + translation;
vec4 mvPos = modelViewMatrix * vec4(pos, 1.0);
gl_Position = projectionMatrix * mvPos;
gl_PointSize = 5000.0 / -mvPos.z;
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
varying vec3 vColor;
uniform float useColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
</script>
</body>
This works perfectly when run on my mid-2015 Macbook Pro that's plugged into a 27" Mac Thunderbolt display (graphics card: Intel Iris Pro 1536 MB graphics). However, if I unplug from the display and try the same demo on my raw Macbook Pro's built in screen, the "hover" event triggers when my mouse is pretty far from a given point.
Curiously, the hovering works fine on my early 2015 Macbook Air (graphics card: Intel HD Graphics 6000 MHz DDR3) regardless of whether it's plugged into the Thunderbolt display or not. (Also, all of these tests were run in Chrome but other browsers appear to show the same behavior.)
Does anyone know what might cause this discrepancy, or what I can do to fix it? Any insights others can offer on this question would be hugely appreciated.
Update: Aha! This discrepancy is evidently due to the high retina display on the Macbook Pro. To see for yourself, go to Applications -> Google Chrome (or any browser app file), right click -> Get Info -> check "Open in Low Resolution". Then quit the selected browser (Command + Q) and restart the browser. If you interact with the demo above, you'll see it behaves as expected.
Changing renderer.setPixelRatio(window.devicePixelRatio);
to renderer.setPixelRatio(1);
causes the gpu picking to behave as expected, but makes rendered objects look fuzzy.