Выбор графического процессора несовместим на разных устройствах - PullRequest
0 голосов
/ 11 февраля 2020

Я пытаюсь реализовать выбор GPU с помощью очков, используя код, который я изменил во второй половине этой статьи https://threejsfundamentals.org/threejs/lessons/threejs-picking.html

У меня на рабочем столе все работает нормально, но я начал тестировать разные браузеры и устройства, и он не работает согласованно. Я сделал Codepen для иллюстрации https://codepen.io/deklanw/pen/OJVVmEd?editors=1111

body {
  margin: 0;
}
#c {
  width: 100vw;
  height: 100vh;
  display: block;
}
<canvas id="c"></canvas>
<script type="module">
// Three.js - Picking - RayCaster w/Transparency
// from https://threejsfundamentals.org/threejs/threejs-picking-gpu.html

import * as THREE from "https://threejsfundamentals.org/threejs/resources/threejs/r113/build/three.module.js";

function main() {
  const canvas = document.querySelector("#c");
  const renderer = new THREE.WebGLRenderer({ canvas });

  const fov = 60;
  const aspect = 2; // the canvas default
  const near = 0.1;
  const far = 200;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 30;

  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0);
  const pickingScene = new THREE.Scene();
  pickingScene.background = new THREE.Color(0);

  // put the camera on a pole (parent it to an object)
  // so we can spin the pole to move the camera around the scene
  const cameraPole = new THREE.Object3D();
  scene.add(cameraPole);
  cameraPole.add(camera);

  function randomNormalizedColor() {
    return Math.random();
  }

  function getRandomInt(n) {
    return Math.floor(Math.random() * n);
  }

  function getCanvasRelativePosition(e) {
    const rect = canvas.getBoundingClientRect();
    return {
      x: e.clientX - rect.left,
      y: e.clientY - rect.top
    };
  }

  const textureLoader = new THREE.TextureLoader();
  const particleTexture =
    "https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/sprites/ball.png";

  const vertexShader = `
    attribute float size;
    attribute vec3 customColor;

    varying vec3 vColor;

    void main() {
        vColor = customColor;
        vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
        gl_PointSize = size * ( 100.0 / length( mvPosition.xyz ) );
        gl_Position = projectionMatrix * mvPosition;
    }
`;

  const fragmentShader = `
    uniform sampler2D texture;
    varying vec3 vColor;

    void main() {
        vec4 tColor = texture2D( texture, gl_PointCoord );
        if (tColor.a < 0.5) discard;
        gl_FragColor = mix( vec4( vColor.rgb, 1.0 ), tColor, 0.1 );
    }
`;

  const pickFragmentShader = `
    uniform sampler2D texture;
    varying vec3 vColor;

    void main() {
      vec4 tColor = texture2D( texture, gl_PointCoord );
      if (tColor.a < 0.25) discard;
      gl_FragColor = vec4( vColor.rgb, 1.0);
    }
`;

  const materialSettings = {
    uniforms: {
      texture: {
        type: "t",
        value: textureLoader.load(particleTexture)
      }
    },
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    blending: THREE.NormalBlending,
    depthTest: true,
    transparent: false
  };

  const createParticleMaterial = () => {
    const material = new THREE.ShaderMaterial(materialSettings);
    return material;
  };

  const createPickingMaterial = () => {
    const material = new THREE.ShaderMaterial({
      ...materialSettings,
      fragmentShader: pickFragmentShader,
      blending: THREE.NormalBlending
    });
    return material;
  };

  const geometry = new THREE.BufferGeometry();
  const pickingGeometry = new THREE.BufferGeometry();
  const colors = [];
  const sizes = [];
  const pickingColors = [];
  const pickingColor = new THREE.Color();
  const positions = [];

  for (let i = 0; i < 30; i++) {
    colors[3 * i] = randomNormalizedColor();
    colors[3 * i + 1] = randomNormalizedColor();
    colors[3 * i + 2] = randomNormalizedColor();

    const rgbPickingColor = pickingColor.setHex(i + 1);
    pickingColors[3 * i] = rgbPickingColor.r;
    pickingColors[3 * i + 1] = rgbPickingColor.g;
    pickingColors[3 * i + 2] = rgbPickingColor.b;

    sizes[i] = getRandomInt(20);

    positions[3 * i] = getRandomInt(20);
    positions[3 * i + 1] = getRandomInt(20);
    positions[3 * i + 2] = getRandomInt(20);
  }

  geometry.setAttribute(
    "position",
    new THREE.Float32BufferAttribute(positions, 3)
  );
  geometry.setAttribute(
    "customColor",
    new THREE.Float32BufferAttribute(colors, 3)
  );
  geometry.setAttribute("size", new THREE.Float32BufferAttribute(sizes, 1));

  geometry.computeBoundingBox();

  const material = createParticleMaterial();
  const points = new THREE.Points(geometry, material);

  // setup geometry and material for GPU picking
  pickingGeometry.setAttribute(
    "position",
    new THREE.Float32BufferAttribute(positions, 3)
  );
  pickingGeometry.setAttribute(
    "customColor",
    new THREE.Float32BufferAttribute(pickingColors, 3)
  );
  pickingGeometry.setAttribute(
    "size",
    new THREE.Float32BufferAttribute(sizes, 1)
  );

  pickingGeometry.computeBoundingBox();

  const pickingMaterial = createPickingMaterial();
  const pickingPoints = new THREE.Points(pickingGeometry, pickingMaterial);

  scene.add(points);
  pickingScene.add(pickingPoints);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  class GPUPickHelper {
    constructor() {
      // create a 1x1 pixel render target
      this.pickingTexture = new THREE.WebGLRenderTarget(1, 1);
      this.pixelBuffer = new Uint8Array(4);
    }
    pick(cssPosition, pickingScene, camera) {
      const { pickingTexture, pixelBuffer } = this;

      // set the view offset to represent just a single pixel under the mouse
      const pixelRatio = renderer.getPixelRatio();
      camera.setViewOffset(
        renderer.getContext().drawingBufferWidth, // full width
        renderer.getContext().drawingBufferHeight, // full top
        (cssPosition.x * pixelRatio) | 0, // rect x
        (cssPosition.y * pixelRatio) | 0, // rect y
        1, // rect width
        1 // rect height
      );
      // render the scene
      renderer.setRenderTarget(pickingTexture);
      renderer.render(pickingScene, camera);
      renderer.setRenderTarget(null);
      // clear the view offset so rendering returns to normal
      camera.clearViewOffset();
      //read the pixel
      renderer.readRenderTargetPixels(
        pickingTexture,
        0, // x
        0, // y
        1, // width
        1, // height
        pixelBuffer
      );

      const id =
        (pixelBuffer[0] << 16) | (pixelBuffer[1] << 8) | pixelBuffer[2];
      
      console.log(`You clicked sphere number ${id}`);
      
      return id;
    }
  }

  const pickHelper = new GPUPickHelper();

  function render(time) {
    time *= 0.001; // convert to seconds;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    cameraPole.rotation.y = time * 0.1;

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);

  function onClick(e) {
    const pickPosition = getCanvasRelativePosition(e);
    const pickedID = pickHelper.pick(pickPosition, pickingScene, camera);
  }

  function onTouch(e) {
    const touch = e.touches[0];
    const pickPosition = getCanvasRelativePosition(touch);
    const pickedID = pickHelper.pick(pickPosition, pickingScene, camera);
  }

  window.addEventListener("mousedown", onClick);
  window.addEventListener("touchstart", onTouch);
}

main();
</script>

Если щелкнуть (или коснуться) узлы, их идентификаторы должны появиться в консоли. На некоторых устройствах я просто получаю 0, как при выборе фона.

Кто-нибудь знает почему?

Также, если есть способ сделать выбор в этом случае (Укажите меня sh с точками переменного размера через ShaderMaterial) с более простым методом, который все еще работает, мне любопытно, как

EDIT:

Я удалил целевую оптимизацию рендеринга 1x1, и, кажется, это исправило ее. Теперь я хотел бы знать, что из-за этой оптимизации вызывает проблему ..

1 Ответ

1 голос
/ 19 февраля 2020

проблема в том, что вы не можете использовать точки таким образом на разных устройствах.

Независимо от того, нарисована ли точка, когда ее центр находится вне экрана или нет, независимо от устройства (в OpenGL ES / WebGL spe c сказано, что это все еще должен быть нарисован, OpenGL spe c говорит, что это не так. Для него нет тестов, поэтому каждый драйвер индивидуален), и было бы слишком много работы для внедрения WebGL, чтобы они этого не делали. AFAIK Intel и NVidia действительно рисуют их. AMD и PowerVR (iPhone) не рисуют их.

Вы можете увидеть эту проблему, если вы сделаете круги большими и убедитесь, что они go за кадром (и вам может понадобиться сделать ваш холст маленьким). ). На некоторых устройствах они будут плавно go за кадром, на других устройствах, как только их центр выйдет за пределы экрана, они исчезнут (часто в зависимости от размера точки и размера области просмотра)

Это означает, что ваш пример на самом деле не работает ни в одном случае, с или без цели рендеринга пикселей 1x1, просто с целью рендеринга пикселей 1x1 почти все круги имеют центр за пределами этой области пикселей 1x1, поэтому они не рисуются на некоторых устройствах. Когда вы сделаете так, чтобы цель рендеринга соответствовала размеру холста, то большинство центров окружностей находятся внутри, но вы все равно будете получать ошибки выбора по краям.

Чтобы решить эту проблему, вам нужно нарисовать ваши точки используя четырехугольники вместо очков. Есть много способов сделать это. Нарисуйте каждый четырехугольник как отдельный me sh или спрайт, или объедините все четырехугольники в другой me sh, или используйте InstancedMesh, где вам понадобится матрица для каждой точки, или напишите собственные шейдеры для выполнения точек (см. последний пример этой статьи )

Обратите внимание, что у точек есть и другие проблемы. По умолчанию они не масштабируются относительно размера холста (конечно, вы можете исправить это в своем шейдере, и три. js также имеет эту опцию). Они также имеют независимый от устройства максимальный размер, который согласно спецификации c может составлять всего 1 пиксель. Они плохо реагируют на настройки соотношения пикселей на устройстве (хотя это можно исправить и в коде). По всем этим причинам точки имеют ограниченное использование. Большие круги, которые рисует код, возможно, выходят за этот предел.

...