javascript - Usar texturas en THREE.js
(4)
Estoy empezando con THREE.js, y estoy tratando de dibujar un rectángulo con una textura, iluminado por una sola fuente de luz. Creo que esto es tan simple como se consigue (HTML omitido por brevedad):
function loadScene() {
var world = document.getElementById(''world''),
WIDTH = 1200,
HEIGHT = 500,
VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000,
renderer = new THREE.WebGLRenderer(),
camera = new THREE.Camera(VIEW_ANGLE, ASPECT, NEAR, FAR),
scene = new THREE.Scene(),
texture = THREE.ImageUtils.loadTexture(''crate.gif''),
material = new THREE.MeshBasicMaterial({map: texture}),
// material = new THREE.MeshPhongMaterial({color: 0xCC0000});
geometry = new THREE.PlaneGeometry(100, 100),
mesh = new THREE.Mesh(geometry, material),
pointLight = new THREE.PointLight(0xFFFFFF);
camera.position.z = 200;
renderer.setSize(WIDTH, HEIGHT);
scene.addChild(mesh);
world.appendChild(renderer.domElement);
pointLight.position.x = 50;
pointLight.position.y = 50;
pointLight.position.z = 130;
scene.addLight(pointLight);
renderer.render(scene, camera);
}
El problema es que no puedo ver nada. Si cambio el material y uso el comentario, aparece un cuadrado como era de esperar. Tenga en cuenta que
- La textura es 256x256, por lo que sus lados son poder de dos
- La función se llama realmente cuando el cuerpo está cargado; de hecho, funciona con un material diferente.
- No funciona aunque sirva el archivo desde un servidor web, por lo que no es un problema de política entre dominios el no permitir cargar la imagen.
¿Qué estoy haciendo mal?
En el momento en que se carga la imagen, el renderizador ya ha dibujado la escena, por lo tanto, es demasiado tarde. La solución es cambiar
texture = THREE.ImageUtils.loadTexture(''crate.gif''),
dentro
texture = THREE.ImageUtils.loadTexture(''crate.gif'', {}, function() {
renderer.render(scene);
}),
En la versión r75 de three.js, debe usar:
var loader = new THREE.TextureLoader();
loader.load(''texture.png'', function ( texture ) {
var geometry = new THREE.SphereGeometry(1000, 20, 20);
var material = new THREE.MeshBasicMaterial({map: texture, overdraw: 0.5});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
En la versión r82 de Three.js, TextureLoader es el objeto que se debe usar para cargar una textura.
Cargando una textura ( código fuente , demo )
Extracto ( test.js ):
var scene = new THREE.Scene();
var ratio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight,
0.1, 50);
var renderer = ...
[...]
/**
* Will be called when load completes.
* The argument will be the loaded texture.
*/
var onLoad = function (texture) {
var objGeometry = new THREE.BoxGeometry(20, 20, 20);
var objMaterial = new THREE.MeshPhongMaterial({
map: texture,
shading: THREE.FlatShading
});
var mesh = new THREE.Mesh(objGeometry, objMaterial);
scene.add(mesh);
var render = function () {
requestAnimationFrame(render);
mesh.rotation.x += 0.010;
mesh.rotation.y += 0.010;
renderer.render(scene, camera);
};
render();
}
// Function called when download progresses
var onProgress = function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + ''% loaded'');
};
// Function called when download errors
var onError = function (xhr) {
console.log(''An error happened'');
};
var loader = new THREE.TextureLoader();
loader.load(''texture.jpg'', onLoad, onProgress, onError);
Cargando múltiples texturas ( código fuente , demo )
En este ejemplo, las texturas se cargan dentro del constructor de la malla, las texturas múltiples se cargan con Promises .
Extracto ( Globe.js ):
Cree un contenedor nuevo usando Object3D
para tener dos mallas en el mismo contenedor:
var Globe = function (radius, segments) {
THREE.Object3D.call(this);
this.name = "Globe";
var that = this;
// instantiate a loader
var loader = new THREE.TextureLoader();
Un mapa llamado textures
donde cada objeto contiene la url
de un archivo de textura y val
para almacenar el valor de un objeto de texture Three.js.
// earth textures
var textures = {
''map'': {
url: ''relief.jpg'',
val: undefined
},
''bumpMap'': {
url: ''elev_bump_4k.jpg'',
val: undefined
},
''specularMap'': {
url: ''wateretopo.png'',
val: undefined
}
};
El conjunto de promesas, para cada objeto en el mapa llamado textures
empuja una nueva Promesa en la texturePromises
del array texturePromises
, cada Promesa llamará a loader.load
. Si el valor de entry.val
es un objeto THREE.Texture
válido, entonces resuelve la promesa.
var texturePromises = [], path = ''./'';
for (var key in textures) {
texturePromises.push(new Promise((resolve, reject) => {
var entry = textures[key]
var url = path + entry.url
loader.load(url,
texture => {
entry.val = texture;
if (entry.val instanceof THREE.Texture) resolve(entry);
},
xhr => {
console.log(url + '' '' + (xhr.loaded / xhr.total * 100) +
''% loaded'');
},
xhr => {
reject(new Error(xhr +
''An error occurred loading while loading: '' +
entry.url));
}
);
}));
}
Promise.all
toma la promesa array texturePromises
como argumento. Hacerlo hace que el navegador espere a que se cumplan todas las promesas, cuando lo hacen podemos cargar la geometría y el material.
// load the geometry and the textures
Promise.all(texturePromises).then(loadedTextures => {
var geometry = new THREE.SphereGeometry(radius, segments, segments);
var material = new THREE.MeshPhongMaterial({
map: textures.map.val,
bumpMap: textures.bumpMap.val,
bumpScale: 0.005,
specularMap: textures.specularMap.val,
specular: new THREE.Color(''grey'')
});
var earth = that.earth = new THREE.Mesh(geometry, material);
that.add(earth);
});
Para la esfera de la nube, solo se necesita una textura:
// clouds
loader.load(''n_amer_clouds.png'', map => {
var geometry = new THREE.SphereGeometry(radius + .05, segments, segments);
var material = new THREE.MeshPhongMaterial({
map: map,
transparent: true
});
var clouds = that.clouds = new THREE.Mesh(geometry, material);
that.add(clouds);
});
}
Globe.prototype = Object.create(THREE.Object3D.prototype);
Globe.prototype.constructor = Globe;
La solución de Andrea tiene toda la razón, solo escribiré otra implementación basada en la misma idea. Si echó un vistazo a la source THREE.ImageUtils.loadTexture () encontrará que utiliza el objeto Image de JavaScript. El evento $ (window) .load se activa después de cargar todas las imágenes. entonces en ese evento podemos renderizar nuestra escena con las texturas ya cargadas ...
CoffeeScript
$(document).ready -> material = new THREE.MeshLambertMaterial(map: THREE.ImageUtils.loadTexture("crate.gif")) sphere = new THREE.Mesh(new THREE.SphereGeometry(radius, segments, rings), material) $(window).load -> renderer.render scene, camera
JavaScript
$(document).ready(function() { material = new THREE.MeshLambertMaterial({ map: THREE.ImageUtils.loadTexture("crate.gif") }); sphere = new THREE.Mesh(new THREE.SphereGeometry(radius, segments, rings), material); $(window).load(function() { renderer.render(scene, camera); }); });
Gracias...