Three.js Tutorial 1 – El retorno del triángulo y el cuadrado

Escrito por el 4 marzo, 2013

Bienvenidos al primer tutorial de la nueva serie centrada en Three.js, un framework de javascript exclusivamente dedicado a facilitarnos la vida a la hora de programar nuestros mundos 2D y 3D usando la especificación WebGL que trae el novedoso HTML5. Antes de empezar con él, me gustaría que leyeras antes los tutoriales que dediqué a WebGL que puedes encontrar en este enlace.

En esencia, lo que pretendo conseguir con cada lección de Three.js, es explicar cómo programar el mismo efecto que conseguíamos en uno o varios de los capítulos de aquél tutorial de WebGL. Así se podrán comparar códigos y comprenderemos mejor la gran ayuda que supone usar este framework de WebGL. En concreto, para este tutorial inicial, haremos lo mismo que hicímos en el viejo tutorial del triángulo y el cuadrado en WebGL.

Éste es el resultado de lo que se verá en un navegador que soporte webGL:

Abre este enlace para ver el Ejemplo en vivo de Three.js. Para poder verlo en acción, tendrás que usar un navegador que soporte webGL, como Mozilla Firefox o Google Chrome.

Bueno, en primer lugar habría que visitar la web del autor Mr. doob, autor de Three.js. En ella podremos encontrar el framework para descargar, su documentación, y un montón de ejemplos de las maravillas que podemos conseguir. El zip a descargar pesa más de 75 megas, pero no os asustéis, en él están contenidos un montón de ejemplos (cuyas imágenes, videos y modelos son la causa de ese excesivo tamaño). Pero en realidad, de todo el zip sólo necesitamos un único archivo: three.js o su versión mínima (comprimida), three.min.js. Ambos los podees encontar en su github (repositorio de código online) , por si no quereis descargar el zip entero.

Introducción

Asumiré que tienes conocimientos mínimos de javascript, y que has leído los tutoriales de WebGL, porque volver a explicar los mismos conceptos aquí es la mar de tedioso tanto para mí como para tí.

Básicamente, lo que necesitamos conocer para empezar a funcionar con Three.js son 4 conceptos:

  1. Obtener el render
  2. Crear una escena
  3. Crear y posicionar una cámara
  4. Crear y posicionar al menos un objeto en la escena.

Por supuesto, Three.js tiene las mismas limitaciones de soporte que WebGL. Los navegadores que no soporten WebGL no funcionará tampoco Three.js, aunque creo que Three.js tiene un modo de utilizar dibujos vectoriales SVG para dibujar en pantalla, aunque por no ser WebGL queda fuera del objetivo de esta serie de tutoriales. Personalmente uso Chrome a diario para navegar y trabajar, aunque WebGL también funciona bajo Firefox, Safari y Opera. MicroSoft de momento no da soporte a WebGL en ninguno de sus Internet Explorer. Tampoco se le echa mucho de menos.

A continuación explicaré mas pausadamente cada concepto.

El render

Un render es básicamente el objeto WebGL donde la tarjeta gráfica pintará todos los gráficos. Podríamos tomarlo como lo que llamábamos “canvas” y “contexto” en la serie WebGL. Un mundo 3D (escena) puede ser enorme, pero en pantalla sólo se pintará aquello que quede dentro del encuadre de la “cámara”. Ésto es lo que llamaremos render.

Para crear un render, necesitamos al menos decirle 3 cosas:

  • El ancho en píxeles que tendrá el render
  • El alto en píxeles
  • El color con el que “limpiar” el render

Cada vez que se dibuja una escena en el canvas, lo primero que hace WebGL es pintar todo el canvas de un color determinado, y luego dibujar todos los objetos de la escena sobre ese color. Ese color es lo que se conoce como color de “limpieza” del render. Podríamos verlo como el color del “espacio” que rodea la escena que queremos dibujar en el canvas.

Veámos cómo definirla en JavaScript usando Three.js:

var render;
var canvasWidth = 500;
var canvasHeight = 500;

render = new THREE.WebGLRenderer();
render.setClearColorHex(0x000000, 1);
render.setSize(canvasWidth, canvasHeight);

Lo que estamos haciendo es crear un render mediante WebGLRenderer, de 500×500 píxeles usando el método setSize, y estableciéndole un color de limpieza negro mediante setClearColorHex, con un alfa (índice de transparencia) de 1, es decir, totalmente opaco.

Dicho render, también tiene asociado el elemento de HTML5 CANVAS que tenemos que dibujar en pantalla. Antes teníamos que escribir nosotros en el HTML el elemento CANVAS, utilizar javascript para a través del árbol DOM acceder a él, y posteriormente sacarle el “contexto” para dibujar sobre él.

El objeto render nos proporciona el canvas directamente. Sólo tenemos que meterlo en su sito correcto; por ejemplo, para meterlo en un DIV con id=”canvas” haremos:

document.getElementById("canvas").appendChild(render.domElement);

Y cada vez que queremos dibujar el render en el canvas, esté donde esté, tenemos que llamar a su método render:

render.render(escena, camara);

Dicho método necesita dos objetos: La escena que queremos renderizar, y la cámara desde donde se renderizará la escena. Evidentemente una misma escena se verá diferente según la posición que ocupe la cámara y la dirección en la que esté enfocada.

Como se puede ver, muy sencillo.

La escena

La escena, como puedes suponer, es el contenedor de todos los gráficos que quieras dibujar. Todo proyecto Three.js debe contener al menos una escena, y dentro de dicha escena irán los objetos que pertenezcan a la misma “dimensión” de tu mundo, es decir, que puedan ser visibles entre sí dependiendo de la posición de la cámara. Los objetos pueden ser de tres tipos: Figuras, luces y cámaras; hablaremos de ellos después. A veces, según lo que quieras conseguir, puedes necesitar de varias escenas, cada una con diferentes objetos, para ir alternándolas siguiendo algún tipo de lógica, o para dibujar una escena como textura de una figura de otra escena, etc. Pero de momento, vamos a lo simple, sólo usaremos una escena.

Crear una escena es extremadamente sencillo:

var escena;
escena = new THREE.Scene();

Y para añadir objetos a dicha escena:

escena.add(objeto);

Se pueden añadir objetos a la escena del tipo Scene dinámicamente, pero como es natural sólo se dibujarán en llamadas posteriores al método render.

La cámara

Las cámaras no son más que un tipo de objetos que se pueden a añadir a la escena, pero como tienen una importancia fundamental, las trataremos con más detalle.

Una escena puede contener tantas cámaras como deseemos, pero sólo se podrá utilizar una de ellas para renderizar la escena en un momento dado, usando el método render del objeto con el mismo nombre que vimos anteriormente. Una cámara puede ser posicionada y rotada tantas veces como queramos, pero el resultado de dichos cambios sólo se dibujarán en el canvas en posteriores llamadas al método render, como debería serte evidente.

Para definir una cámara hay que tener en cuenta qué tipo de proyección queremos tener. Three.js nos proporciona tres tipos de proyecciones:

  • Proyección paralela, cámara del tipo OrthographicCamera
  • Proyección cónica, cámara del tipo PerspectiveCamera
  • Proyección combinada, cámara del tipo CombinedCamera, y que permite cambiar entre la proyección cónica y paralela en tiempo de ejecución.

La OrthographicCamera es una perspectiva que respeta el tamaño de los objetos, independientemente de la distancia a la que se encuentren de la cámara.

La PerspectiveCamera es la que deforma los objetos según la distancia y posición que se encuentren con respecto a la cámara, tal y como ocurre en el mundo real.

url

Podemos ver un ejemplo donde permite cambiar la perspectiva aquí : http://mrdoob.github.com/three.js/examples/canvas_camera_orthographic2.html

En nuestro ejemplo, usaremos la perspectiva cónica, para conseguir un efecto más realista en nuestro mundo 3D. Para crear una PerspectiveCamera necesitaremos indicarle cuatro valores básicos:

  • Ángulo del campo de visión o FOV en grados
  • Ratio de aspecto, que normalmente es la relación entre el WIDTH y el HEIGHT del canvas donde se va a renderizar la imagen
  • Distancia mínima de dibujado
  • Distancia máxima de dibujado
var camara;
camara = new THREE.PerspectiveCamera(45, canvasWidth / canvasHeight, 0.1, 100);
escena.add(camara);

Con éste código estamos creando y añadiendo la cámara a la escena. Le hemos puesto el FOV a 45 º, el ratio de aspecto que tiene el canvas donde se dibujará el render, y también le decimos que no se dibujen los objetos que están a menos de 0.1 o a más de 100 unidades de distancia de la cámara, aunque se encuentren dentro del FOV. A continuación, añadimos la cámara a la escena.

Otras propiedades de la cámara son su posición y el ángulo con el que “apunta” a la escena. Para definir su posición, basta con indicarle una coordenadas (X,Y,Z) absolutas de la escena con el método set del Vector3 que define su posición, y para “apuntar” con la cámara, tenemos el método lookAt que acepta también un objeto Vector3 (también de Three.js, y que por cierto, tiene un montón de métodos matemáticos para su manipulación) que define el ángulo y dirección en la que apuntará la cámara. Para facilitarnos la vida, todos los objetos (cámaras, figuras, luces) y escenas que creemos tienen una propiedad llamada position que corresponde a un vector que apunta al centro de dicho objeto, asi que podemos usar dicho atributo para pasárselo a la cámara para enfocarlo rápidamente.

Un ejemplo de ambos métodos:

camara.position.set(0, 0, 0);
camara.lookAt(escena.position);

Así estamos posicionando la cámara en el origen de coordenadas de la escena , y haciendo que la cámara apunte al centro de la escena, que es también (0,0,0). Los argumentos que le estamos pasando en este ejemplo son son los mismos que se ponen por defecto a una cámara recién creada, así que esas dos líneas en este caso no harían falta.

Las figuras

Ya he repetido varias veces que tenemos tres tipos de objetos para añadir a la escena: Cámaras, figuras y luces. Las cámaras ya las hemos visto, y las luces las dejaremos para otro tutorial, asi que sólo nos queda ver qué diantres son las figuras. Te lo puedes imaginar.

Una figura es la cosa que quieres pintar en la pantalla. Un punto, Una línea, un triángulo, Un cuadrado. Un cubo. Un dinosaurio. Todos son figuras, más o menos complicadas de programar. Three.js llama a este tipo de objeto como Mesh.

Para crear una figura, Three.js necesita dos cosas: Una geometría y un material.

La geometría es como te puedes imaginar, la lista de vértices y caras que forman una figura determinada. Dichos vértices y caras también tienen una serie de propiedades que se les puede definir para conseguir nuevos efectos, aunque de momento para este tutorial no los usaremos. Three.gl permite crear geometrías de muy diversas formas, como puedes encontrar en su documentación. Hay clases para definir figuras vértice a vértice, a mano, tal y como hacíamos en el viejo tutorial de WebGL; pero también hay clases que nos dibujan cubos, esferas, conos o toroides pasándole un par de argumentos, como veremos en capítulos posteriores.

Los materiales son “la piel” de las figuras; sirven para definir de qué color es cada cara de una figura y cómo la luz actúa en dicha cara. Al igual que con las geometrías, Three.js nos proporciona un montón de clases para crear distintos tipos de materiales según el efecto que queramos tener, como por ejemplo materiales que ignoran la luz, materiales para conseguir el sombreado de Lambert, otro para obtener el sombreado de Phong, etc.

Para éste primer tutorial, usaremos la clase de material y la geometría mas sencillos que nos proporciona Three.js.

Normalmente tendremos un material para cada figura, o incluso varios materiales para una misma figura, pero ahora por sencillez usaremos el mismo material para “vestir” ambas figuras, pues las dos son blancas y no tenemos ningún tipo de luz en la escena.

Para crear un material sólo necesitamos:

var material = new THREE.MeshBasicMaterial({
    color:0xFFFFFF,
    side:THREE.DoubleSide
});

El tipo de material más básico, MeshBasicMaterial, necesita que le indiquemos el color hexadecimal con el que pintar la figura, y el lado de la cara que coloreará. Permite THREE.FrontSide, por delante; THREE.BackSide, por detrás; y THREE.DoubleSide, por ambos lados. Si te acuerdas del viejo tutorial, la cara delantera o la trasera NO DEPENDE de la posición de la cámara. Dichas caras dependen del orden de cómo hayas dibujado sus vértices. La cara delantera es la que corresponde con la dirección inversa de las agujas de un reloj. Si usamos DoubleSide, seguro que no fallamos.

Y ahora definamos la geometría del triángulo:

var trianguloGeometria = new THREE.Geometry();
trianguloGeometria.vertices.push(new THREE.Vector3( 0.0,  1.0, 0.0));
trianguloGeometria.vertices.push(new THREE.Vector3(-1.0, -1.0, 0.0));
trianguloGeometria.vertices.push(new THREE.Vector3( 1.0, -1.0, 0.0));
trianguloGeometria.faces.push(new THREE.Face3(0, 1, 2));

Este código te sonará mucho del viejo tutorial. Definimos a mano cada vértice del triángulo isósceles para el tipo de geometría básica Geometry, teniendo en mente que la coordenada (0,0,0) será su centro. Para ello usamos la clase Vector3 que trae Three.js. A continuación definimos en qué orden se tienen que dibujar los vértices para formar cada cara con la clase Face3. Este tipo Face3 permite indicar más atributos de las caras, como un color único, o el vector normal (útil para indicar cómo debe incidir la luz en esta cara), que veremos en posteriores tutoriales.

Y a continuación, creamos la figura usando el material y la geometría anteriores:

var triangulo = new THREE.Mesh(trianguloGeometria, material);

Para hacer el cuadrado, más de lo mismo:

var cuadradoGeometria = new THREE.Geometry();
cuadradoGeometria.vertices.push(new THREE.Vector3(-1.0,  1.0, 0.0));
cuadradoGeometria.vertices.push(new THREE.Vector3( 1.0,  1.0, 0.0));
cuadradoGeometria.vertices.push(new THREE.Vector3( 1.0, -1.0, 0.0));
cuadradoGeometria.vertices.push(new THREE.Vector3(-1.0, -1.0, 0.0));
cuadradoGeometria.faces.push(new THREE.Face4(0, 1, 2, 3));

Aunque esta vez, usamos el tipo Face4 para la composición de la cara, que permite indicar 4 vértices para formar el cuadrilátero.

Y al igual que las cámaras, todas las figuras permiten ser posicionadas, rotadas y escaladas por medio de sus atributos y métodos. De momento nosotros sólo queremos posicionarlas en la escena.

triangulo.position.set(-1.5, 0.0, -7.0);
escena.add(triangulo);

cuadrado.position.set(1.5, 0.0, -7.0);
escena.add(cuadrado);

Notar que las coordenadas de los centros de las figuras hay que ponerlos de forma absoluta a la escena. Aquí ya no hay “puntero” a la posición actual del cursor. Eso lo gestiona Three.js por nosotros.

Y éso fue todo. Con ese sencillísimo código vamos a conseguir el mismo efecto que en el tutorial original, sin tener que devanarnos los sesos manejando punteros, búfferes, fragment shaders, vertex shaders, matrices de proyección, variables uniformes, variables varying… Todo aquello que convertía la programación gráfica con WebGL en una tortura china.

Para que tdo quede más claro, veámos ahora el código del ejempplo al completo.

Su HTML no tiene nada:

<!DOCTYPE html>
<html>
    <head>
        <title>Three.js tutorial - Leccion 01</title>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <script src="js/three.min.js"></script>
        <script>
            //A continuación...
        </script>
    </head>
    <body onload="webGLStart();">
        <div id="canvas"></div>
    </body>
</html>

Y todo el javascript de antes, compactado:

var escena;
var camara;
var render;

function iniciarEscena(){
    //Render
    render = new THREE.WebGLRenderer();

    render.setClearColorHex(0x000000, 1);

    var canvasWidth = 500;
    var canvasHeight = 500;
    render.setSize(canvasWidth, canvasHeight);

    document.getElementById("canvas").appendChild(render.domElement);

    //Escena
    escena = new THREE.Scene();

    //Camara
    camara = new THREE.PerspectiveCamera(45, canvasWidth / canvasHeight, 0.1, 100);
    camara.position.set(0, 0, 0);
    camara.lookAt(escena.position);
    escena.add(camara);

    //Material
    var material = new THREE.MeshBasicMaterial({
        color:0xFFFFFF,
        side:THREE.DoubleSide
    });

    //Triángulo
    var trianguloGeometria = new THREE.Geometry();
    trianguloGeometria.vertices.push(new THREE.Vector3( 0.0,  1.0, 0.0));
    trianguloGeometria.vertices.push(new THREE.Vector3(-1.0, -1.0, 0.0));
    trianguloGeometria.vertices.push(new THREE.Vector3( 1.0, -1.0, 0.0));
    trianguloGeometria.faces.push(new THREE.Face3(0, 1, 2));

    var triangulo = new THREE.Mesh(trianguloGeometria, material);
    triangulo.position.set(-1.5, 0.0, -7.0);
    escena.add(triangulo);

    //Cuadrado
    var cuadradoGeometria = new THREE.Geometry();
    cuadradoGeometria.vertices.push(new THREE.Vector3(-1.0,  1.0, 0.0));
    cuadradoGeometria.vertices.push(new THREE.Vector3( 1.0,  1.0, 0.0));
    cuadradoGeometria.vertices.push(new THREE.Vector3( 1.0, -1.0, 0.0));
    cuadradoGeometria.vertices.push(new THREE.Vector3(-1.0, -1.0, 0.0));
    cuadradoGeometria.faces.push(new THREE.Face4(0, 1, 2, 3));

    var cuadrado = new THREE.Mesh(cuadradoGeometria, material);
    cuadrado.position.set(1.5, 0.0, -7.0);
    escena.add(cuadrado);
}
function renderEscena(){
    render.render(escena, camara);
}

function webGLStart() {
    iniciarEscena();
    renderEscena();
}

¡Y nada más, la magia la hace Three.js por nosotros! Y como siempre, copya este código, y córrelo en tu PC, juega con él. Modifica los vértices de las figuras, y mira lo que pasa. Haz que la cámara haga un lookAt al cuadrado.position, en vez del al scene.position (asegurate que el objeto cuadrado existe cuando lo hagas o te dará error). Experimenta con el código, es la mejor forma de aprender.

Etiquetas: , , , ,

Comentarios (10)

  • Hola, grandes los tutoriales que estas colgando… me guardo este blog en favoritos para leerlo todo mas tranquilo , por cierto en la pagina de three el api no esta completamente documentada por lo que veo….
    Saludos.

  • Gracias, esta bueno el tuto 😀

  • Ya cambiaron unas cosas del threejs y afecto al código :(, esto fue lo que cambio:

    render.setClearColor(0x000000, 1); por render.setClearColorHex(0x000000, 1);

    cuadradoGeometria.faces.push(new THREE.Face3(0, 1, 2));
    cuadradoGeometria.faces.push(new THREE.Face3(2, 3, 0));
    por
    cuadradoGeometria.faces.push(new THREE.Face4(0, 1, 2, 3));

  • buenas quisiera saber como utilizar las primitivas triangle_fan y triangle_strip de webgl en three

  • hola, quisiera saber si con esta librería se puede formar edificios en 3D para la web

  • Estan buenisimas tus lecciones, llevo pocos días en esto de los gráficos por computadora. Solo tengo un problema: cuando quiero generar el cuadrado solamente m sale un triángulo. Cuando abro tu ejemplo todo sale perfecto, pero cuando hago mi código usando el archivo three.js que acabo de descargar me sale ese problema.

  • Muchas gracias por los tutoriales, me han sido muy útiles y estoy descubriendo un nuevo mundo 🙂

    Como dicen antes, el método Face4 hay que sustituirlo por dos Face3. En la documentación de three.js no aparece y me ha costado un rato averiguar porque no me funcionaba.

    Recomiendo al que le interese este curso gratuito de gráficos 3d en el que utilizan la tecnologia webGL y el framework three.js. Es muy sencillo y detallado con videotutoriales.

    https://www.udacity.com/course/cs291

    Un saludo

  • ¡Hola!
    Me gustaría saber por que, a pesar de que copio y pego el código de la página en mi framework y cambio el path del three.js script al directorio local, no me sale nada de nada. Muchas gracias.

  • Esto es mucho mas fácil que los otros cursos con Javascript puro.

¿Tienes algo que decir?

Gestionado con Wordpress y Stripes Theme Entradas (RSS) | Comentarios (RSS)