by Oscar D. Soto
Primero, necesitarás una librería llamada “Three.js”, una librería de Javascript que permite realizar animaciones en 3 dimensiones dentro de la web. Así es que tendrás que referenciar la librería en una etiqueta script dentro de tu archivo html.
<script src=”https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js”></script>
Antes de que iniciemos, necesitas conocer la librería antes de usarla. Dale una ojeada a su documentación para que te familiarices con los términos que voy a utilizar. Aun así, explicaré lo que usé para que tú también lo apliques.
Para iniciar este tutorial, debes entender que para realizar una animación, necesitas tres cosas indispensables: una escena, en donde colocarás todos tus elementos visuales, una cámara, que es la que visualizará todos los elementos dentro de su perspectiva, y un proceso para renderizar lo que la cámara está viendo.
El término renderización (del inglés rendering) es un anglicismo para representación gráfica, usado en la jerga informática para referirse al proceso de generar imagen fotorrealista, o no, a partir de un modelo 2D o 3D (o en lo que colectivamente podría llamarse un archivo de escena) por medio de programas informáticos. Además, los resultados de mostrar dicho modelo pueden llamarse render.
Declararemos nuestras variables correspondientes para nuestra escena. Declaramos nuestra variable de escena por medio de la clase THREE.Scene(), nuestra variable de cámara con la clase THREE.WebGLRenderer(), y nuestra variable de la cámara con la clase THREE.PerspectiveCamera()
var escena = new THREE.Scene();
var camara = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight);
var render = new THREE.WebGLRenderer();
Nota como al momento de declarar la clase para la perspectiva de la cámara hay dos parámetros: el primero indica el FOV (Field of View), que es el campo de visión de la cámara, representado en grados. No voy a adentrarme mucho en el tema, pero aquí hay un artículo que te explica más a detalle el FOV, y el segundo indica la relación del aspecto, o sea la escala que se usará para la perspectiva. Siempre colocarás este valor con la relación del ancho por el alto de tu navegador, para que tu rénder no muestre solo un fragmento de lo que estás haciendo.
Para visualizar el rénder, tendremos que colocar el resultado en el documento Html. Para ello, colocamos las siguientes líneas:
render.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(render.domElement);
La primera línea indica el ancho y el alto del rénder y la segunda anexa el resultado de la renderización al “<body>“ de nuestro documento.
Una vez agregada la escena al documento Html, vamos a incorporar los cubos. Para ello, necesitamos definir tres cosas:
Para el material, utilizaremos el tipo básico y le pondremos el color de nuestra preferencia en hexadecimal. En este caso, usaré el verde.
var material = new THREE.MeshBasicMaterial({ color: 0xb9fbc0 });
La geometría es fácil, solo hay que determinar el ancho, alto y profundidad del cubo. Ocuparé cubos pequeños, así es que basta con declararlos como 1 x 1 x 1.
var geometria = new THREE.BoxGeometry(1, 1, 1);
La posición dentro de la escena se tiene que definir al momento de instanciar el objeto. Para ello, necesitaremos del tipo de material y la geometría ya definidos:
var cubo = new THREE.Mesh(geometria, material);
Tendremos que acceder a la propiedad “cubo.position.x”, “cubo.position.y” y “cubo.position.z” para cambiar la posición. Puedes colocarlo en donde gustes, yo colocaré el cubo en el origen del plano.
cubo.position.x = 0;
cubo.position.y = 0;
cubo.position.z = 0;
Finalmente, agregamos nuestro cubo a la escena, y colocaremos la posición de la cámara.
escena.add(cubo);
Al momento de renderizar, tendrás una pantalla en negro. Eso es porque falta agregar los fotogramas (frames) de la animación. El cubo existe, pero no hay ningún fotograma para que se visualice en ningún momento.
Un fotograma (frame) es únicamente definido como la combinación entre una imagen a visualizar, y el tiempo/momento en que esa imagen será visualizada. Una secuencia de fotogramas crea una animación.
Para agregarlos, necesitamos del apoyo de una función que permita pedir esos fotogramas. La definiremos como animacion(), y está permitirá renderizar los fotogramas que emita la función. Sin embargo, la cámara necesita de una posición en el plano también, para que esta tome la perspectiva que queramos renderizar.
camara.position.z = 5;
var animacion = function(){
requestAnimationFrame(animacion);
render.render(escena, camara);
};
animacion();
Una vez ejecutado, tendremos nuestro cubo. A pesar de que parezca un cuadrado:
Para verlo en perspectiva, tendremos que rotarlo un poco. Eso lo debemos hacer dentro de la función de animacion():
var animacion = function(){
requestAnimationFrame(animacion);
cubo.rotation.x = 0.5; // Vamos a rotar el cubo en el eje X
render.render(escena, camara);
};
Obteniendo el siguiente resultado:
Lo que estamos haciendo, en perspectiva, es lo que vemos en la imagen. La cámara estará hacia arriba, apuntando hacia nuestro cubo que se encuentra en el origen. Para realizar la animación de los cubos de abajo hacia arriba, tendremos que moverlos a lo largo del eje de las Y (siendo la línea verde), y ubicarlos a lo largo del eje X (siendo la línea roja). Lo que está en la imagen de abajo, es lo que llevamos hasta ahora.
Vamos a declarar un array que contenga todos nuestros cubos, y los crearemos mediante un ciclo for. Declararé 100, pero tú puedes agregar los que gustes. Tendremos que instanciarlos de manera en que la cámara pueda tener una perspectiva de todos (o de la mayoría), por lo que los alinearemos en el eje Y de manera consecutiva, tomando una distancia de 1 en cada uno (ya que cada cubo mide 1 x 1 x 1) desde -50 a 50. Así es como quedaría:
var cantidad_cubos = 100;
var cubos = [];
for (var i = 0; i < cantidad_cubos; i++)
{
cubos.push(new THREE.Mesh(geometria, material));
cubos[i].position.y = i - 50;
}
for (var i = 0; i < cantidad_cubos; i++)
escena.add(cubos[i]);
Pero si ejecutas esto, verás una línea verde, que representa todos los cubos alineados uno en uno. Para solucionar esto, vamos a acomodarlos al azar usando una función que tendremos que colocar en el tope de la etiqueta script, que nos permita obtener un número entero al azar de un determinado rango.
function obtenEnteroAleatorio(min, max) {
var valor = Math.floor(Math.random() * (max - min)) + min;
return (valor !== 0) ? valor : 1;
}
Esto lo usaremos en conjunto con la función Math.random() para generar las posiciones aleatorias, en conjunto con la siguiente función:
y = a * 50 * f(-1, 1);
En donde:
El valor decimal puede ir desde 0 hasta 1. Si multiplicamos ese valor por 50, el valor decimal se convertiría de 0 a 50, que corresponde a los 50 puntos positivos sobre el eje Y que mencionaba al principio. Si ese valor lo multiplicamos por el resultado de la función (cuyos únicos posibles valores son -1 o 1), entonces ya podemos recorrer los valores negativos del eje.
Una vez aplicado:
var cantidad_cubos = 100;
var cubos = [];
for (var i = 0; i < cantidad_cubos; i++)
{
cubos.push(new THREE.Mesh(geometria, material));
cubos[i].position.y = Math.random() * 50 * obtenEnteroAleatorio(-1, 1);
}
for (var i = 0; i < cantidad_cubos; i++)
escena.add(cubos[i]);
Obtenemos posiciones aleatorias en Y:
Pero solo logramos ver una línea verde cortada en cachos. Eso es porque nos hace falta darle una posición en Z para que tome perspectiva.
Una vez más, lo vamos a hacer con valores aleatorios. Esta vez, desde -25 a 30 para que tome una perspectiva profunda, pero también necesitaremos incrementar el valor de la posición en Z de la cámara, para que ningún cubo se cruce en su perspectiva. Yo lo dejaré en 35.
var cantidad_cubos = 100;
var cubos = [];
for (var i = 0; i < cantidad_cubos; i++)
{
cubos.push(new THREE.Mesh(geometria, material));
cubos[i].position.y = Math.random() * 50 * obtenEnteroAleatorio(-1, 1);
cubos[i].position.z = obtenEnteroAleatorio(-25, 30); // Agregamos posición aleatoria en Z a cada cubo
}
for (var i = 0; i < cantidad_cubos; i++)
escena.add(cubos[i]);
camara.position.z = 35; // Y agregamos más altura a la camara.
Lo ejecutamos en nuestro navegador de preferencia, y tendremos una perspectiva mejor:
De hecho, cada vez que recargas la página, los cubos ahora aparecen en posiciones diferentes. ¡Esa es la magia de trabajar con aleatorios!
Ya que tenemos los cubos en nuestro escenario, vamos a hacer que se muevan. Para ello, necesitaremos de la función animacion() que declaramos en el paso 2.
Dentro de ella, realizaremos un ciclo for para cambiar las posiciones y rotar los cubos. Para rotarlos, accederemos a su propiedad rotation y cambiaremos sus valores en los tres ejes (X, Y, Z).
var animacion = function(){
requestAnimationFrame(animacion);
for (var i = 0; i < cantidad_cubos; i++)
{
cubos[i].rotation.x += Math.random() * 0.1;
cubos[i].rotation.y += Math.random() * 0.1;
cubos[i].rotation.z += Math.random() * 0.1;
}
render.render(escena, camara);
};
Se agrega la función random, porque no nos interesa que gire en una cantidad en específico, y lo multiplicamos por un decimal para que la rotación no sea tan rápida. Para hacer que se desplacen hacia arriba, tendremos que moverlos en el eje de las Y. Lo pondré de 0.05 para que vaya lento y podamos ver la animación.
var animacion = function(){
requestAnimationFrame(animacion);
for (var i = 0; i < cantidad_cubos; i++)
{
cubos[i].rotation.x += Math.random() * 0.1;
cubos[i].rotation.y += Math.random() * 0.1;
cubos[i].rotation.z += Math.random() * 0.1;
cubos[i].position.y += 0.05; // Posición en Y
}
render.render(escena, camara);
};
Ahora bien, tendremos que mover los cubos sobre el eje X para que se distribuyan a lo largo de la pantalla. Quiero que se vea cada uno sobre un punto en el eje, por lo que también usaré el rango de -50 a 50 para cubrir los 100 cubos.
var animacion = function(){
requestAnimationFrame(animacion);
for (var i = 0; i < cantidad_cubos; i++)
{
cubos[i].rotation.x += Math.random() * 0.1;
cubos[i].rotation.y += Math.random() * 0.1;
cubos[i].rotation.z += Math.random() * 0.1;
cubos[i].position.x = i - (cantidad_cubos / 2); // Posición en X
cubos[i].position.y += 0.05;
}
render.render(escena, camara);
};
Con esa fórmula, si yo quiero menos de 100 cubos, no me tengo que preocupar por modificar el valor de esta posición.
Si lo ejecutamos, podremos ver nuestros cubos en movimiento:
Pero solo se desplazan una vez. Para que se desplacen indefinidamente, tenemos que regresarlos hacia un punto inicial, y para ello necesitamos de una condición que indique hasta donde el cubo puede ir hacia arriba, y en el caso en que se cumpla, mover el cubo hasta el fondo permitido (es decir -50) y cambiar la posición en Z (solamente para fines estéticos). Es necesario incluir la condición dentro del for.
var animacion = function(){
requestAnimationFrame(animacion);
for (var i = 0; i < cantidad_cubos; i++)
{
cubos[i].rotation.x += Math.random() * 0.1;
cubos[i].rotation.y += Math.random() * 0.1;
cubos[i].rotation.z += Math.random() * 0.1;
cubos[i].position.x = i - (cantidad_cubos / 2);
cubos[i].position.y += 0.05;
// Condición para retornar los cubos
if (cubos[i].position.y >= window.innerHeight / 18)
{
cubos[i].position.y = -50;
cubos[i].position.z = obtenEnteroAleatorio(-20, 35);
}
}
render.render(escena, camara);
};
Y con esto tendremos nuestra secuencia de cubos flotando. Puedes modificar el color de los cubos a tu gusto, y también del fondo (si no quieres un fondo negro) con la siguiente línea, después de haber instanciado la variable de la escena:
escena.background = new THREE.Color(color_en_hexadecimal);