GPU拾取-精灵周围的不可见像素

Jar*_*red 0 picking mouse-picking three.js

我正在渲染一个包含精灵的采摘场景。当我的光标靠近精灵时,它注册为一种颜色并被“选中”。放大小精灵时,此不可见的边框会变大。

打开控制台以查看实时打印的ID。将光标移近或移远,移至大和小的精灵。您会看到精灵在不可见的边框上被选中。对于常规几何图形,仅对于精灵,不会发生此行为。

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

如何摆脱看不见的边界,以进行更准确的拾取?

在此处输入图片说明

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);

}
Run Code Online (Sandbox Code Playgroud)
body {
    color: #ffffff;
    background-color: #000000;
    margin: 0px;
    overflow: hidden;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

gma*_*man 5

问题是您所使用的设备的devicePixelRatio!= 1.0和three.js大小差不多。

因为你打电话 renderer.setPixelRatio现在魔术在幕后发生。您画布的大小不是您要求的大小,而是根据three.js代码中隐藏的某些公式得出的其他大小。

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

删除对的呼叫render.setPixelRatio,它将开始工作。

IMO解决此问题的正确方法是使用devicePixelRatio自己,因为这样您可以100%看到所有发生的事情。幕后没有发生任何魔术。

所以,

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

    <canvas id="c"></canvas>
    
    Run Code Online (Sandbox Code Playgroud)
  2. 设置画布以100vw用于宽度,100vh高度和主体margin: 0;

    canvas { width: 100vw; height: 100vh; display: block; }
    body { margin: 0; }
    
    Run Code Online (Sandbox Code Playgroud)

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

  3. 使用浏览器拉伸画布的大小选择其drawingBuffer应为的大小并乘以devicePixelRatio。假设您实际上要支持设备像素比率。无需重复执行两次,因此在DRY之后,只需在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);  
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 将鼠标坐标转换为设备坐标

        renderer.readRenderTargetPixels(
            pickingTexture, 
            mouse.x * window.devicePixelRatio, 
            pickingTexture.height - mouse.y * window.devicePixelRatio,
            1, 1, pixelBuffer);
    
    Run Code Online (Sandbox Code Playgroud)

这是解决方案

<canvas id="c"></canvas>
Run Code Online (Sandbox Code Playgroud)
canvas { width: 100vw; height: 100vh; display: block; }
body { margin: 0; }
Run Code Online (Sandbox Code Playgroud)
    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);  
}
Run Code Online (Sandbox Code Playgroud)

注意其他一些事情。

  1. 我猜您真的想将采摘纹理清除为零而不是白色。这样,0 =那里什么都没有,其他什么都=这里的东西。

    renderer.setClearColor(0);
    renderer.render(scene, camera, pickingTexture);
    renderer.setClearColor(0xFFFFFF);
    
    Run Code Online (Sandbox Code Playgroud)
  2. 不知道是什么id <= numOfVertices意思

    因此,鉴于现在清除为零,代码只是

    if (id) console.log(id);
    
    Run Code Online (Sandbox Code Playgroud)
  3. 我没有在初始化时设置渲染器尺寸,pickingTexture尺寸或相机外观。

    为什么重复我自己。onWindowResize已经设置好了

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

  5. 我删除了对window.innerWidth和的大多数引用window.innerHeight

    我将删除所有这些代码,但是我不想为该示例更改更多代码。使用window.innerWidth将代码绑定到窗口。例如,如果您想在非全尺寸窗口中使用代码,可以假设您创建了编辑器。您必须更改代码。

    以一种可以在更多情况下工作的方式编写代码并不难,所以为什么以后要自己做更多的工作。

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

  1. 您可以调用render.setPixelRatio,然后使用以下命令设置pickingTexture渲染目标的大小window.devicePixelRatio

    我没有选择此解决方案,因为您必须猜测three.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>
    
    Run Code Online (Sandbox Code Playgroud)

    当然,您需要devicePixelRatio穿上制服。

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

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

  • 内容和迷你咆哮都加一。:-) (2认同)