выбор графического процессора - невидимые пиксели вокруг спрайтов

Я визуализирую сцену выбора, содержащую спрайты. Когда мой курсор приближается к спрайту, он регистрируется как цвет и «выбирается». Эта невидимая граница становится больше при увеличении масштаба спрайтов.

Откройте консоль, чтобы увидеть распечатанные идентификаторы в реальном времени. Перемещайте курсор все ближе и дальше к большим и маленьким спрайтам. Вы увидите, что спрайты выделяются на невидимой границе. Такого поведения не происходит с обычной геометрией, только со спрайтами.

Это странно, потому что я показываю то, что на самом деле видит 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>


person Jared    schedule 04.12.2015    source источник
comment
У меня отлично работает. У вас страница увеличена?   -  person WestLangley    schedule 04.12.2015
comment
Я только что проверил коробку Windows. Сработало как положено. Что-то может быть не так с моим MBP, работающим с последней версией Chrome.   -  person Jared    schedule 05.12.2015
comment
На всякий случай неясно, почему он работает в Windows, а не в MBP, потому что в вашем окне Windows devicePixelRatio равно 1, а у MBP - 2.   -  person gman    schedule 05.12.2015


Ответы (1)


Проблема в том, что вы используете устройство, на котором devicePixelRatio! = 1.0 и three.js врут о размере.

Потому что вы позвонили renderer.setPixelRatio, теперь магия происходит за кулисами. Ваш холст не того размера, который вы запрашивали, это какой-то другой размер, основанный на какой-то формуле, скрытой в коде three.js.

Итак, что происходит. Ваш холст имеет один размер, но ваша цель рендеринга - другого размера. Ваш шейдер использует gl_PointSize для рисования точек. Этот размер указан в пикселях устройства. Поскольку ваша цель рендеринга имеет другой размер, размер точек в вашей цели рендеринга отличается от размера на экране.

Уберите вызов render.setPixelRatio, и он заработает.

ИМО, правильный способ исправить это - использовать devicePixelRatio самостоятельно, потому что в этом случае все, что происходит, на 100% видно вам. Никакого волшебства за кулисами не происходит.

So,

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

Вот это решение

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 ошибки. В этом случае, если холст был вдвое больше pickingTexture, тогда 3 из каждых 4 пикселей холста не существуют в pickingTexture. Однако в зависимости от вашего приложения это может быть нормально. Вы не можете выбрать 1/2 пикселя, по крайней мере, с помощью мыши.

    Еще одна причина, по которой я бы, вероятно, не выбрал это решение, заключается в том, что проблема просто появляется в других местах. lineWidth - одно, gl_FragCoord - другое. То же самое с настройками окна просмотра и ножниц. Кажется, лучше сделать так, чтобы размер цели рендеринга соответствовал этому холсту, чтобы все было одинаково, чем делать все больше и больше обходных решений и помнить, где использовать один размер по сравнению с другим. Завтра начну пользоваться PointsMaterial. Также есть проблемы с devicePixelRatio. Если не позвонить renderer.setPixelRatio, эти проблемы исчезнут.

person gman    schedule 05.12.2015
comment
Плюс один и за содержание, и за мини-тираду. : -) - person WestLangley; 05.12.2015
comment
@gman, БОЛЬШОЕ спасибо за то, что пролили свет на setPixelRatio и нашли время не только на написание решения, но и на то, чтобы дать представление о других возможных решениях и методах. Огромное спасибо ребятам вроде вас и Westlangley, которые помогли нам изучить все тонкости JS и three.js. - person Jared; 06.12.2015