gpu pick - 精灵周围的隐形像素

时间:2015-12-04 18:48:23

标签: three.js picking mouse-picking

我正在渲染一个包含精灵的拾取场景。当我的光标接近精灵时,它会注册为一种颜色并被选中"。这个不可见的边框越来越大,你可以放大精灵。

打开控制台,查看实时打印的ID。将光标移近,远离大大小小的精灵。您将看到精灵在一个不可见的边框上被选中。常规几何体不会发生这种情况,仅使用精灵。

这很奇怪,因为我正在渲染renderer.readRenderTargetPixels实际看到的内容。

如何摆脱不可见的边框以获得更准确的拣选?

enter image description here



var renderer, scene, camera, controls;

var particles, uniforms;

var PARTICLE_SIZE = 50;

var raycaster, intersects;
var mouse, INTERSECTED;

var pickingTexture;

var numOfVertices;

init();
animate();

function init() {

    container = document.getElementById('container');

    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.z = 150;

    //

    var geometry1 = new THREE.BoxGeometry(200, 200, 200, 4, 4, 4);
    var vertices = geometry1.vertices;
    numOfVertices = vertices.length;

    var positions = new Float32Array(vertices.length * 3);
    var colors = new Float32Array(vertices.length * 3);
    var sizes = new Float32Array(vertices.length);

    var vertex;
    var color = new THREE.Color();

    for (var i = 0, l = vertices.length; i < l; i++) {

        vertex = vertices[i];
        vertex.toArray(positions, i * 3);

        color.setHex(i + 1);
        color.toArray(colors, i * 3);

        sizes[i] = PARTICLE_SIZE * 0.5;

    }

    var geometry = new THREE.BufferGeometry();
    geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
    geometry.addAttribute('customColor', new THREE.BufferAttribute(colors, 3));
    geometry.addAttribute('size', new THREE.BufferAttribute(sizes, 1));

    //

    var material = new THREE.ShaderMaterial({

        uniforms: {
//                texture: {type: "t", value: THREE.ImageUtils.loadTexture("../textures/circle.png")}
            texture: {type: "t", value: THREE.ImageUtils.loadTexture("../textures/disc.png")}
        },
        vertexShader: document.getElementById('vertexshader').textContent,
        fragmentShader: document.getElementById('fragmentshader').textContent,
        depthTest: false,
        transparent: false
//            alphaTest: 0.9

    });

    //

    particles = new THREE.Points(geometry, material);
    scene.add(particles);

    //

    renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true
    });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setClearColor(0xffffff);
    container.appendChild(renderer.domElement);

    //

    raycaster = new THREE.Raycaster();
    mouse = new THREE.Vector2();

    //


    //

    window.addEventListener('resize', onWindowResize, false);
    document.addEventListener('mousemove', onDocumentMouseMove, false);

    // defaults are on the right (except minFilter)
    var options = {
        format: THREE.RGBAFormat,       // THREE.RGBAFormat
        type: THREE.UnsignedByteType,   // THREE.UnsignedByteType
        anisotropy: 1,                  // 1
        magFilter: THREE.LinearFilter,  // THREE.LinearFilter
        minFilter: THREE.LinearFilter,  // THREE.LinearFilter
        depthBuffer: true,              // true
        stencilBuffer: true             // true
    };

    pickingTexture = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, options);
    pickingTexture.texture.generateMipmaps = false;

    controls = new THREE.OrbitControls(camera, container);
    controls.damping = 0.2;
    controls.enableDamping = false;

}

function onDocumentMouseMove(e) {

//        event.preventDefault();

    mouse.x = e.clientX;
    mouse.y = e.clientY;

}

function onWindowResize() {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);

}

function animate() {

    requestAnimationFrame(animate);


    controls.update();

    render();

}

function render() {

    pick();
    renderer.render(scene, camera);


}

function pick() {

    renderer.render(scene, camera, pickingTexture);

    //create buffer for reading single pixel
    var pixelBuffer = new Uint8Array(4);

    //read the pixel under the mouse from the texture
    renderer.readRenderTargetPixels(pickingTexture, mouse.x, pickingTexture.height - mouse.y, 1, 1, pixelBuffer);

    //interpret the pixel as an ID

    var id = ( pixelBuffer[0] << 16 ) | ( pixelBuffer[1] << 8 ) | ( pixelBuffer[2] );
    if (id <= numOfVertices) console.log(id);

}
&#13;
body {
    color: #ffffff;
    background-color: #000000;
    margin: 0px;
    overflow: hidden;
}
&#13;
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>



<script type="x-shader/x-fragment" id="fragmentshader">

uniform sampler2D texture;
varying vec3 vColor;

void main() {

    // solid squares of color
    gl_FragColor = vec4( vColor, 1.0 );

}

</script>

<script type="x-shader/x-vertex" id="vertexshader">

attribute float size;
attribute vec3 customColor;
varying vec3 vColor;

void main() {

    vColor = customColor;

    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );

    gl_PointSize = size * ( 300.0 / length( mvPosition.xyz ) );

    gl_Position = projectionMatrix * mvPosition;

}

</script>
<div id="container"></div>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:4)

问题是你的设备上有一个devicePixelRatio!= 1.0和三个大小不等的设备。

因为你现在调用了renderer.setPixelRatio魔法,所以魔法发生在幕后。你的画布不是你要求它的尺寸,而是基于three.js代码中隐藏的一些公式的其他尺寸。

那么,会发生什么。您的画布是一个尺寸,但您的渲染目标是不同的大小。您的着色器使用gl_PointSize绘制其点。该大小是设备像素。由于渲染目标的大小不同,因此渲染目标中的点大小与屏幕上的不同。

移除对render.setPixelRatio的调用,它将开始工作。

IMO解决此问题的正确方法是自己使用devicePixelRatio,因为这样一切正在发生的事情对您来说是100%可见的。幕后没有任何魔法发生。

所以,

  1. 摆脱容器并直接使用画布

    <canvas id="c"></canvas>
    
  2. 将画布设置为使用100vw表示宽度,100vh表示高度,并使正文margin: 0;

    canvas { width: 100vw; height: 100vh; display: block; }
    body { margin: 0; }
    

    这将使您的画布自动拉伸以填充窗口。

  3. 使用浏览器拉伸画布的大小来选择其drawingBuffer应该的大小并乘以devicePixelRatio。这假设您实际上想要支持设备像素比率。不需要在D.R.Y.之后执行此操作两次,因此只需在onWindowResize中执行此操作即可。

        canvas = document.getElementById("c");
        renderer = new THREE.WebGLRenderer({
            antialias: true,
            alpha: true,
            canvas: canvas,
        });
        pickingTexture = new THREE.WebGLRenderTarget(1, 1, options);
    
        onWindowResize(); 
    
    ...
    
    function onWindowResize() {
    
        var width = canvas.clientWidth * window.devicePixelRatio;
        var height = canvas.clientHeight * window.devicePixelRatio;
    
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
    
        renderer.setSize(width, height, false);  // YOU MUST PASS FALSE HERE otherwise three.js will muck with the CSS
        pickingTexture.setSize(width, height);  
    }
    
  4. 将鼠标坐标转换为设备坐标

        renderer.readRenderTargetPixels(
            pickingTexture, 
            mouse.x * window.devicePixelRatio, 
            pickingTexture.height - mouse.y * window.devicePixelRatio,
            1, 1, pixelBuffer);
    
  5. 这是解决方案

    var renderer, scene, camera, controls;
    
    var particles, uniforms;
    
    var PARTICLE_SIZE = 50;
    
    var raycaster, intersects;
    var mouse, INTERSECTED;
    
    var pickingTexture;
    
    var numOfVertices;
    
    init();
    animate();
    
    function init() {
    
        canvas = document.getElementById('c');
    
        scene = new THREE.Scene();
    
        camera = new THREE.PerspectiveCamera(45, 1, 1, 10000);
        camera.position.z = 150;
    
        //
    
        var geometry1 = new THREE.BoxGeometry(200, 200, 200, 4, 4, 4);
        var vertices = geometry1.vertices;
        numOfVertices = vertices.length;
    
        var positions = new Float32Array(vertices.length * 3);
        var colors = new Float32Array(vertices.length * 3);
        var sizes = new Float32Array(vertices.length);
    
        var vertex;
        var color = new THREE.Color();
    
        for (var i = 0, l = vertices.length; i < l; i++) {
    
            vertex = vertices[i];
            vertex.toArray(positions, i * 3);
    
            color.setHex(i + 1);
            color.toArray(colors, i * 3);
    
            sizes[i] = PARTICLE_SIZE * 0.5;
    
        }
    
        var geometry = new THREE.BufferGeometry();
        geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
        geometry.addAttribute('customColor', new THREE.BufferAttribute(colors, 3));
        geometry.addAttribute('size', new THREE.BufferAttribute(sizes, 1));
    
        //
    
        var material = new THREE.ShaderMaterial({
    
            uniforms: {
    //                texture: {type: "t", value: THREE.ImageUtils.loadTexture("../textures/circle.png")}
                texture: {type: "t", value: THREE.ImageUtils.loadTexture("../textures/disc.png")}
            },
            vertexShader: document.getElementById('vertexshader').textContent,
            fragmentShader: document.getElementById('fragmentshader').textContent,
            depthTest: false,
            transparent: false
    //            alphaTest: 0.9
    
        });
    
        //
    
        particles = new THREE.Points(geometry, material);
        scene.add(particles);
    
        //
    
        renderer = new THREE.WebGLRenderer({
            antialias: true,
            alpha: true,
            canvas: canvas,
        });
        renderer.setClearColor(0xffffff);
        //
    
        raycaster = new THREE.Raycaster();
        mouse = new THREE.Vector2();
    
        //
    
    
        //
    
        window.addEventListener('resize', onWindowResize, false);
        document.addEventListener('mousemove', onDocumentMouseMove, false);
    
        // defaults are on the right (except minFilter)
        var options = {
            format: THREE.RGBAFormat,       // THREE.RGBAFormat
            type: THREE.UnsignedByteType,   // THREE.UnsignedByteType
            anisotropy: 1,                  // 1
            magFilter: THREE.LinearFilter,  // THREE.LinearFilter
            minFilter: THREE.LinearFilter,  // THREE.LinearFilter
            depthBuffer: true,              // true
            stencilBuffer: true             // true
        };
    
        pickingTexture = new THREE.WebGLRenderTarget(1, 1, options);
        pickingTexture.texture.generateMipmaps = false;
    
        controls = new THREE.OrbitControls(camera, canvas);
        controls.damping = 0.2;
        controls.enableDamping = false;
    
        onWindowResize();
    
    }
    
    function onDocumentMouseMove(e) {
    
    //        event.preventDefault();
    
        mouse.x = e.clientX;
        mouse.y = e.clientY;
    
    }
    
    function onWindowResize() {
    
        var width = canvas.clientWidth * window.devicePixelRatio;
        var height = canvas.clientHeight * window.devicePixelRatio;
    
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
    
        renderer.setSize(width, height, false);  // YOU MUST PASS FALSE HERE!
        pickingTexture.setSize(width, height);  
    }
    
    function animate() {
    
        requestAnimationFrame(animate);
    
    
        controls.update();
    
        render();
    
    }
    
    function render() {
    
        pick();
        renderer.render(scene, camera);
    
    
    }
    
    function pick() {
        renderer.setClearColor(0);
        renderer.render(scene, camera, pickingTexture);
        renderer.setClearColor(0xFFFFFF);
    
        //create buffer for reading single pixel
        var pixelBuffer = new Uint8Array(4);
    
        //read the pixel under the mouse from the texture
        renderer.readRenderTargetPixels(pickingTexture, mouse.x * window.devicePixelRatio, pickingTexture.height - mouse.y * window.devicePixelRatio, 1, 1, pixelBuffer);
    
        //interpret the pixel as an ID
    
        var id = ( pixelBuffer[0] << 16 ) | ( pixelBuffer[1] << 8 ) | ( pixelBuffer[2] );
        if (id > 0) console.log(id);
    
    }
    body {
        color: #ffffff;
        background-color: #000000;
        margin: 0;
    }
    canvas { width: 100vw; height: 100vh; display: block; }
    <script src="https://threejs.org/build/three.min.js"></script>
    <script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
    
    
    
    <script type="x-shader/x-fragment" id="fragmentshader">
    
    uniform sampler2D texture;
    varying vec3 vColor;
    
    void main() {
    
        // solid squares of color
        gl_FragColor = vec4( vColor, 1.0 );
    
    }
    
    </script>
    
    <script type="x-shader/x-vertex" id="vertexshader">
    
    attribute float size;
    attribute vec3 customColor;
    varying vec3 vColor;
    
    void main() {
    
        vColor = customColor;
    
        vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    
        gl_PointSize = size * ( 300.0 / length( mvPosition.xyz ) );
    
        gl_Position = projectionMatrix * mvPosition;
    
    }
    
    </script>
    <canvas id="c"></canvas>

    请注意其他一些事项。

    1. 我猜你真的想把采摘纹理清零而不是白色。那样0 =没有任何东西,其他东西=那里的东西。

      renderer.setClearColor(0);
      renderer.render(scene, camera, pickingTexture);
      renderer.setClearColor(0xFFFFFF);
      
    2. 不知道id <= numOfVertices意味着什么

      因此,现在它已清零,代码只是

      if (id) console.log(id);
      
    3. 我没有在初始时设置渲染器大小,pickingTexture大小和相机方面。

      为什么重复自己。 onWindowResize已设置

    4. 当画布调整大小以使其大小匹配时,您需要调整pickingTexture渲染目标的大小。

    5. 我删除了对window.innerWidthwindow.innerHeight

      的大多数引用

      我会删除所有这些但我不想为此示例更改更多代码。使用window.innerWidth将代码绑定到窗口。如果您希望在不是窗口的全尺寸的内容中使用代码,例如,请假设您创建an editor。你必须改变代码。

      以更多情况下的代码编写代码并不困难,所以为什么以后为自己做更多的工作。

    6. 我没有选择的其他解决方案

      1. 您可以致电render.setPixelRatio,然后使用pickingTexture

        设置window.devicePixelRatio渲染目标的尺寸

        我没有选择这个解决方案,因为你必须猜测三个.js在幕后做了什么。你今天的猜测可能是正确的,但明天就错了。如果你告诉three.js 制作一些width by height 它似乎更好,它应该只是让它width by height而不是让它成为别的东西。类似地,你必须猜测three.js何时应用pixelRatio,何时不应用。如上所述,它不会将它应用于渲染目标的大小,它不能,因为它不知道你的目的是什么。你正在制作渲染目标吗?为了全屏效果?捕获?对于非全屏效果?既然它无法知道它无法为你应用pixelRatio。这遍及three.js代码。有些地方它应用pixelRatio,其他地方则不适用。你猜不到了。如果您从未设置pixelRatio问题消失。

      2. 您可以将devicePixelRatio传入您的着色器

        <script type="x-shader/x-vertex" id="vertexshader">
        
        attribute float size;
        attribute vec3 customColor;
        varying vec3 vColor;
        uniform float devicePixelRatio;  // added
        
        void main() {
            vColor = customColor;
            vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
            gl_PointSize = size * ( 300.0 / length( mvPosition.xyz ) ) * devicePixelRatio;
            gl_Position = projectionMatrix * mvPosition;
        
        }
        </script>
        

        当然你需要在你的制服中设置devicePixelRatio

        可能选择此解决方案。小问题是如果pickingTexture与画布的后备缓冲区的分辨率不同,则可以通过1个错误得到。在这种情况下,如果画布是pickingTexture的2倍,则画布中每4个像素中的3个不存在于pickingTexture中。根据您的应用程序可能没问题。你不能选择1/2像素,至少不能用鼠标。

        我可能不会选择这个解决方案的另一个原因是它只是让问题弹出其他地方。 lineWidth是1,gl_FragCoord是另一个。视口和剪刀设置也是如此。最好使渲染目标大小与画布匹配,以便所有内容都相同,而不是制作越来越多的变通方法,并且必须记住在哪里使用一个尺寸与另一个尺寸。明天我开始使用PointsMaterial。它还有devicePixelRatio的问题。通过不调用renderer.setPixelRatio,这些问题就会消失。