¿Qué es WebGL?

Antes de empezar directamente a entender ThreeJS es importante saber lo que es WebGL ya que ThreeJS usa WebGL por debajo.

A grandes rasgos, WebGL es mecanismo que tienen los navegadores para usar toda la potencia de la tarjeta gráfica de los usuarios que visitan las páginas web para poder pintar escenas en 3D.

Antiguamente, antes de que existiera WebGL se usaba la propia propia API del canvas del DOM y Flash lo que hacía que las escenas 3D fueran muy simples.

Hoy en día, con WebGL puedes crear escenas 3D complejas ya que todo se va a renderizar en la tarjeta gráfica del usuario que entre en la web (queda en manos del programador ajustar la calidad de los gráficos dependiendo de la potencia de la tarjeta gráfica del usuario).

Para usar WebGL no necesitas ningún framework, lo puedes hacer con Javascript sin instalar nada, pero programar en WebGL es tan complejo que lo mejor es usar alguna librería tipo ThreeJS o PixiJS para facilitar la tarea de programación

¿Qué es ThreeJS?

Logo de ThreeJS

ThreeJS es una librería Javascript para el manejo de escenas 3D dentro del canvas del DOM. Por debajo usa WebGL para ofrecer toda su potencia y rendimiento pero con una API mucho más sencilla de usar para el desarrollador.

ThreeJS es open source, creada en 2010 y es una de las librerías más populares para crear este tipo de escenas 3D.

Lo bueno de esta librería es que ofrece un montón de utilidades y abstracciones propias de motores 3D: cámaras, objetos, escenas, animaciones, materiales, luces, texturas, etc.

Instalación de ThreeJS

Cómo cualquier librería de Javascript, podemos instalarla de varias formas:

Con NPM

Con NPM puedes usar su paquete oficial:

npm install --save three

Una vez instalada la librería puedes importarla en el proyecto de dos formas.

Puedes importar toda la librería para poder acceder a todas las funciones:

import * as THREE from "three";

const scene = new THREE.Scene();

O puedes importar solo las funciones que necesites:

import { Scene } from "three";

const scene = new Scene();

Lo ideal es la segunda opción, sobre todo en código en producción, ya que solo usas lo que necesitas y no importas todo. La primera opción, la de importar todo, solo la recomiendo si estás haciendo pruebas o sketches, ya que facilita no tener que andar importanto cada cosa a usar.

Con CDN

Con CDN también lo puedes instalar, es la opción que recomiendo si quieres hacer proyectos rápidos o pruebas ya que puedes abrir directamente el HTML con la escena que hayas creado sin tener que hacer build o compilaciones.

<script type="module">
  import * as THREE from "https://unpkg.com/three/build/three.module.js";

  const scene = new THREE.Scene();
</script>

Fíjate que en lugar de colocarlo en el head de la web lo hacemos desde un script de tipo módulo. Esto se hace para aprovechar la potencia de los ES Modules de Javascript. Si lo haces así tienes que tener en cuenta que todos los script que crees tienen que tener el tipo module.

La URL que se importa apunta a la versión más reciente de la librería.

Primeros pasos. Hello world.

Una vez instalada la librería ya podemos empezar a programar nuestro hello world, aunque en el sector del modelado 3D al hello world se le llama hello cube porque lo primero que se suele hacer es crear un cubo en 3D.

Para crear estos ejemplos yo voy a usar codepen. Si tu también lo quieres usar, para importar ThreeJS tienes que ir a Settings (arriba a la derecha), luego en la sección JS, dentro de "Add External Scripts/Pens" tienes que buscar ThreeJS para añadirlo.

Si NO estás usando Codepen, en el HTML te recomiendo que pongas esto:

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Hello Cube Three</title>
    <style>
      html,
      body {
        margin: 0;
        width: 100%;
        height: 100%;
      }
      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <script type="module" src="app.js"></script>
  </body>
</html>

Dentro del app.js tienes que importar ThreeJS del CDN:

import * as THREE from "https://unpkg.com/three/build/three.module.js";

Si estás usando Codepen no tienes que hacer nada de esto porque nosotros ya hemos importado la librería. Lo que si que te recomiendo es que en la parte de CSS pongas esto para que se vea todo el canvas a pantalla completa.

html,
body {
  margin: 0;
  width: 100%;
  height: 100%;
}
canvas {
  width: 100%;
  height: 100%;
}

Bien, hecho esto, ya podemos empezar a crear el cubo en 3D, vamos paso a paso.

Creación de la escena

Lo primero en cualquier proyecto ThreeJS es crear la escena. La escena, como su nombre indica, es como el escenario que vamos a montar. Dentro de la escena insertaremos todo: la cámara, el cubo, las luces, etc.

ThreeJS lo que hace es renderizar escenas, todo lo que no esté dentro de la escena no será renderizado.

const scene = new THREE.Scene();

Como hemos importado todas las funciones de Three, para poder acceder a sus funciones, lo tenemos que hacer con el objeto THREE.

Creación de la cámara

A continuación se crea la cámara. La cámara sirve para poder indicar la parte que se va a renderizar de la escena, es decir, ThreeJS renderizará en pantalla lo que "vea" la cámara.

Piensa en lo que vamos a crear como un escenario de una película. Creamos al escenario y a los actores y luego con la cámara grabamos la escena desde la posición que queramos. Con ThreeJS es igual, vamos a crear el cubo y con la cámara vamos a poder pintarlo en pantalla desde la posición que queramos.

const fov = 75; // Grados
const aspect = window.innerWidth / window.innerHeight; // Relación de aspecto
const near = 0.1; // Si el objeto está más cerca que esta distancia no se renderiza
const far = 5; // Si el objeto está más lejos que esta distancia no se renderiza

const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);

camera.position.z = 2;

Bien, lo primero que se hace es crear el fov, que es un campo númerico que indica los grados que va a cubrir la cámara. Con mucho fov lo que ocurre es que la cámara va a pintar mucha más parte de la escena, es como si el zoom estuviera en el mínimo, como un gran angular. En cambio con un fov más pequeño lo que ocurre es que se renderiza menos escena, es lo contrario a gran angular, ves menos parte de la escena, como desde más cerca. Yo recomiento un número entre 55 y 80.

El siguiente campo es la relación de aspecto, como pasa en las pantallas indica la relación entre el ancho y el alto, en este caso entre el ancho y el alto de lo que va a grabar la cámara. En nuestro ejemplo será el alto y el ancho de la ventana para que ocupe todo.

Los siguientes dos campos están relacionados, near y far. Son dos números que indican distancia, en concreto la distancía más cercana y más lejana que va a renderizar la cámara. Esto es importate configurarlo bien porque ayuda mucho al rendimiento y optimización de la escena, ya que si un objeto está más cerca de la cámara que la distancia near no se va a renderizar y lo mismo pasa si está más lejos que far.

Piensa que creas una escena y pones objetos tan lejos de la cámara que casi no se ven, si ajustas el far bien puedes hacer que efectivamente no se rendericen ya que no merece la pena que gasten recursos, pero que cuando la cámara se acerque y por tanto entre dentro del rango near -far si que se renderice.

Por último se crea la cámara, aunque esto lo veremos con más detalle en próximos artículos, por el momento tienes que saber que en ThreeJS hay varios tipos de cámara. La PerspectiveCamera es la ḿas utilizada porque lo que hace es renderizar la escena tal y como lo verían nuestros ojos.

Creación del renderer

A continuación lo que hacemos es crear el motor de render de WebGL para que renderice nuestra escena, para ello:

const renderer = new THREE.WebGLRenderer({ antialias: true });

En este caso está activado el antialiasing, que se encarga de suavizar los bordes de las figuras para que no haya "picos" o "dientes de sierra" cuando se renderizan objetos en bajas resoluciones.

También configuramos el tamaño del rendered para que ocupe toda la pantalla:

renderer.setSize(window.innerWidth, window.innerHeight);

Por último añadimos el rendered al DOM para que se cree al canvas:

document.body.appendChild(renderer.domElement);

Tienes que saber que también puedes pasar al rendered el canvas si ya lo has creado en el HTML para no tener que hacer appendChild:

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

Creación del cubo en 3D

Muy bien, ya lo tenemos todo pero no falta lo más importante, crear la figura que queremos renderizar. En este ejemplo al querer hacer un HelloCube vamos a crear un cubo.

Antes de continuar es importante que sepas que en ThreeJS las figuras (un cubo por ejemplo) se las conoce como Mesh. Un mesh tiene dos partes: geometría y material. La geometría define la forma que va a tener, es decir, vértices, aristas y su posición en 3D. El material es el color o imagen de textura que va a tener la figura en sus caras.

Empecemos por la geometría del Mesh. Lo bueno de Three es que tiene geometrías ya hechas para formas básicas como cubos, esferas y ese tipo de cosas:

const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;

const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

Como es un cubo, pasamos 3 medidas para cada una de las dimensiones del cubo: el ancho de el cubo, el alto y la profundidad.

Vamos con el material, para no complicarlo vamos a crear un material básico de ThreeJS con un color:

const material = new THREE.MeshPhongMaterial({ color: 0x44aa88 });

Por último creamos el cubo y lo añadimos a la escena que creamos antes:

const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

Lo que queda, y es la parte más importante es hacer que el motor de render pinte la escena con lo que ve la cámara. Al crear la cámara hemos puesto su posición en el eje Z a 2 para que la cámara esté delante del cubo y por lo tanto lo vea la cámara.

renderer.render(scene, camera);

Creación de una luz

Si abres la web verás que de momento solo se ve un fondo oscuro. Esto es porque no hemos metido ninguna luz y el cubo no se ve en la escena. Vamos a meter una luz básica:

const color = 0xffffff;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);

Con esto creamos una luz, de color blanco y intensidad 1 de tipo direccional y la alejamos del punto 0,0,0 para que ilumine la escena. Con esto si que deberías ver en la web un cuadrado verde:

Se ve un cuadrado verde en 2D

Parece un cuadrado, pero es un cubo en realidad, solo que no tiene rotación y solo vemos una de sus caras. Si quieres, puedes cambiar la rotación antes de renderizar la escena:

cube.rotation.x = 35;
cube.rotation.y = 45;

renderer.render(scene, camera);
Se ve un cubo verde en 3D

Creación de una animación de rotación

Ya que estamos vamos a rizar el rizo. Vamos a meterle una pequeña animación de rotación al cubo. La manera más simple es crear una función render que se ejecute todo el rato para actualizar la rotación y volver a renderizar la esena. Simplemente tienes que crear esta función tras crear todo:

function render(time) {
  time *= 0.001;

  cube.rotation.x = time;
  cube.rotation.y = time;

  renderer.render(scene, camera);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

requestAnimationFrame sirve para ejecutar una función en el siguiente frame que procese el ordenador. Como dentro de la función volvemos a llamar a requestAnimationFrame lo que conseguimos es que esta función se ejecute todo el rato (normalmente 60 veces por segundo).

Dentro, modificamos la rotación del cubo usando la varible time que es una variable que aumentaremos en cada ejecución de render.

Por último, llamamos al método render del renderer para que renderice la esena todo el rato y se vea la animación.

Se ve un cubo en 3D con rotando de forma indefinida

Si quieres ver el código completo de lo que hemos hecho en este tutorial lo puedes hacer aquí:

https://codepen.io/Frostq/pen/ExgBxjZ

Conclusiones

Con unas pocas líneas de código hemos conseguido crear un cubo en 3D con luz y movimiento, piensa en las posibilidades de esta librería y en general de WebGL.

En este artículo hemos visto poquísimo de todo lo que puede ofrecer ThreeJS. Si te ha gustado programar este tipo de cosas tienes que saber que las posibilidades son infinitas, la imaginación es el único límite.

Si quieres continuar creando cosas en 3D te recomiendo que practiques las matemáticas. Aunque no lo creas, programar este tipo de cosas requiere de cálculos, vectores, trigonometría, etc.

Si te quieres quedar flipando, mira esta web que recopila ejemplos completamente increíbles con ThreeJS: https://www.awwwards.com/websites/three-js/

Pero sobre todo no te desanimes, aunque creas que nunca vas a llegar a hacer cosas tan increíbles como esas, con tiempo y mucha práctica podrás hacer esas cosas y mejores.