2

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

打开您的控制台以查看实时打印的 ID。将光标移近和远离大小精灵。您会看到精灵在不可见的边框上被选中。这种行为不会发生在常规几何体上,只会发生在 sprite 上。

这很奇怪,因为我正在渲染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);

}
body {
    color: #ffffff;
    background-color: #000000;
    margin: 0px;
    overflow: hidden;
}
<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>

4

1 回答 1

11

问题是您使用的设备具有 devicePixelRatio != 1.0 和 three.js 的大小。

因为你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. 使用浏览器拉伸画布的大小来选择它的绘图缓冲区应该是的大小并乘以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);  
    }
    
  4. 将鼠标坐标转换为设备坐标

        renderer.readRenderTargetPixels(
            pickingTexture, 
            mouse.x * window.devicePixelRatio, 
            pickingTexture.height - mouse.y * window.devicePixelRatio,
            1, 1, pixelBuffer);
    

这是解决方案

var renderer, scene, camera, controls;

var particles, uniforms;

var PARTICLE_SIZE = 50;

var raycaster, intersects;
var mouse, INTERSECTED;

var pickingTexture;

var numOfVertices;
var info = document.querySelector('#info');

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.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    geometry.setAttribute('customColor', new THREE.BufferAttribute(colors, 3));
    geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));

    //

    var loader = new THREE.TextureLoader();
    var material = new THREE.ShaderMaterial({

        uniforms: {
//                texture: {type: "t", value: THREE.ImageUtils.loadTexture("../textures/circle.png")}
            texture: {value: loader.load("https://i.imgur.com/iXT97XR.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.setRenderTarget(pickingTexture);
    renderer.setClearColor(0);
    renderer.render(scene, camera);
    renderer.setClearColor(0xFFFFFF);
    renderer.setRenderTarget(null)

    //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);
    info.textContent = id;

}
body {
    color: #ffffff;
    background-color: #000000;
    margin: 0;
}
canvas { width: 100vw; height: 100vh; display: block; }
#info { position: absolute; left: 0; top: 0; color: red; background: black; padding: 0.5em; font-family: monospace; }
<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>
<div id="info"></div>

注意其他一些事情。

  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.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>
    

    当然,你需要devicePixelRatio穿上你的制服。

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

    我可能不会选择这个解决方案的另一个原因是它只会让问题出现在其他地方。lineWidth 是一个,gl_FragCoord是另一个。视口和剪刀设置也是如此。使渲染目标大小与该画布匹配似乎更好,这样一切都相同,而不是做出越来越多的变通方法,并且必须记住在哪里使用一种尺寸与另一种尺寸。明天我开始使用PointsMaterial. 它也有 devicePixelRatio 的问题。通过不叫renderer.setPixelRatio这些问题消失。

于 2015-12-05T06:31:50.947 回答