Три.js/WebGL и 2D Canvas — передача массива getImageData() в Three.DataTexture()

#javascript #canvas #three.js #webgl

#javascript #холст #three.js #webgl

Вопрос:

Я новичок в WebGL (и 3D-графике в целом) и использую three.js . Что я хочу сделать, так это создать несколько текстур из 2D canvas и использовать их для рендеринга нескольких сеток, каждая с разной текстурой.

Если я просто передам холст новому THREE.Прототип Texture(), текстура изменится на то, что представляет собой холст в данный момент. Итак, все мои объекты имеют одинаковую текстуру. Мое решение состояло в том, чтобы сохранить каждый массив canvas с помощью getImageData() и создать новую текстуру, передав эти данные в новую THREE.Прототип DataTexture(). Однако chrome продолжает выдавать ошибки, и когда я запускаю его в Firefox, текстура отображается вверх ногами.

 userName = $nameInput.val();
ctx2d.fillText( userName, 256, 128); 
var canvasData = ctx2d.getImageData(0,0,512,256);

texture = new THREE.DataTexture(canvasData.data, 512, 256, THREE.RGBAFormat);
texture.needsUpdate = true;

material = new THREE.MeshBasicMaterial({ map: texture });
geometry = new THREE.PlaneGeometry(20,10);
textMesh = new THREE.Mesh( geometry, material);

scene.add(textMesh);
  

Chrome и Safari регистрируют следующую ошибку: WebGL: INVALID_OPERATION: texImage2D: введите UNSIGNED_BYTE, но ArrayBufferView не Uint8Array. Однако Firefox воспроизводит его, хотя текстура перевернута.

Согласно документации Mozilla, данные представляют собой Uint8ClampedArray. Итак, предполагая, что это проблема, я могу обойти вышеуказанную ошибку, создав новый Uint8Array() и передав в него данные, как показано ниже:

 var data = new Uint8Array(canvasData.data);

texture = new THREE.DataTexture(data, 512, 256, THREE.RGBAFormat);
  

Однако он все еще отображается в перевернутом виде. Что происходит?

Комментарии:

1. В качестве альтернативы, если у кого-нибудь есть лучший способ хранения отдельных изображений canvas в виде текстур без использования getImageData, это тоже было бы здорово.

2. Используйте gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,true); перед добавлением данных изображения в текстуру. Должен быть threee.js эквивалент, я бы надеялся.

3. это не будет работать в three.js поскольку three.js вызывает gl.pixelStore(gl.UNPACK_FLIP_Y_WEBGL, texture.flipY) перед загрузкой каждой текстуры, что означает, что она эффективно перезапишет ваши настройки.

Ответ №1:

Three.js инициализируется лениво, поэтому теоретически вы могли бы инициализировать свои текстуры, вызвав renderer.render

 "use strict";

var camera;
var scene;
var renderer;
var group;

init();
animate();

function init() {
  renderer = new THREE.WebGLRenderer({canvas: document.querySelector("canvas")});

  camera = new THREE.PerspectiveCamera(70, 1, 1, 1000);
  camera.position.z = 300;
  scene = new THREE.Scene();
  
  group = new THREE.Object3D();
  scene.add(group);

  var geometry = new THREE.BoxGeometry(50, 50, 50);

  var ctx = document.createElement("canvas").getContext("2d");
  ctx.canvas.width = 128;
  ctx.canvas.height = 128;

  for (var y = 0; y < 3;   y) {
    for (var x = 0; x < 3;   x) {
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillStyle = "rgb("   (x * 127)   ",192,"   (y * 127)   ")"  ;
      ctx.fillRect(0, 0, 128, 128);
      ctx.fillStyle = "white";
      ctx.font = "60px sans-serif";
      ctx.fillText(x   "x"   y, 64, 64);

      var texture = new THREE.Texture(ctx.canvas);
      texture.needsUpdate = true;
      texture.flipY = true;
      var material = new THREE.MeshBasicMaterial({
        map: texture,
      });
      var mesh = new THREE.Mesh(geometry, material);
      group.add(mesh);
      mesh.position.x = (x - 1) * 100;
      mesh.position.y = (y - 1) * 100;
      
      // render to force three to init the texture
      renderer.render(scene, camera);
    }
  }
}

function resize() {
  var width = renderer.domElement.clientWidth;
  var height = renderer.domElement.clientHeight;
  if (renderer.domElement.width !== width || renderer.domElement.height !== height) {
    renderer.setSize(width, height, false);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}

function animate() {
  resize();
  group.rotation.x  = 0.005;
  group.rotation.y  = 0.01;

  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}  
 body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }  
 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<canvas></canvas>  

Вы также можете вызвать renderer.setTexture(texture, slot) , который работает, но, возможно, это неправильная вещь, поскольку на основе имени, документов и подписи функции нет гарантии, что это будет работать в будущем.

 "use strict";

var camera;
var scene;
var renderer;
var group;

init();
animate();

function init() {
  renderer = new THREE.WebGLRenderer({canvas: document.querySelector("canvas")});

  camera = new THREE.PerspectiveCamera(70, 1, 1, 1000);
  camera.position.z = 300;
  scene = new THREE.Scene();
  
  group = new THREE.Object3D();
  scene.add(group);

  var geometry = new THREE.BoxGeometry(50, 50, 50);

  var ctx = document.createElement("canvas").getContext("2d");
  ctx.canvas.width = 128;
  ctx.canvas.height = 128;

  for (var y = 0; y < 3;   y) {
    for (var x = 0; x < 3;   x) {
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillStyle = "rgb("   (x * 127)   ",192,"   (y * 127)   ")"  ;
      ctx.fillRect(0, 0, 128, 128);
      ctx.fillStyle = "white";
      ctx.font = "60px sans-serif";
      ctx.fillText(x   "x"   y, 64, 64);

      var texture = new THREE.Texture(ctx.canvas);
      texture.needsUpdate = true;
      texture.flipY = true;
      var material = new THREE.MeshBasicMaterial({
        map: texture,
      });
      var mesh = new THREE.Mesh(geometry, material);
      group.add(mesh);
      mesh.position.x = (x - 1) * 100;
      mesh.position.y = (y - 1) * 100;
      
      // make three init the texture
      var slot = 0; // doesn't matter what slot as we're not 
      renderer.setTexture(texture, slot);
    }
  }
}

function resize() {
  var width = renderer.domElement.clientWidth;
  var height = renderer.domElement.clientHeight;
  if (renderer.domElement.width !== width || renderer.domElement.height !== height) {
    renderer.setSize(width, height, false);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}

function animate() {
  resize();
  group.rotation.x  = 0.005;
  group.rotation.y  = 0.01;

  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}  
 body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }  
 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<canvas></canvas>  

Что касается использования flipping texture.flipY = true;

Комментарии:

1. Это сделало это! Большое спасибо. Что это значит, что three.js лениво инициализируется? Действительно ли сетка не «создана» до тех пор, пока она не будет отображена в сцене?

2. Ленивая инициализация означает, что ни один из ресурсов WebGL, WebGLTexture, WebGLBuffer, WebGLProgram и т.д. Не будут созданы до тех пор, пока three.js необходимо их отобразить.