#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 необходимо их отобразить.