WebGL Tutorial 9 – Mejorando la estructura del código

Escrito por el 11 noviembre, 2011

Bienvenido al noveno tutorial de la serie Aprende webGL. Es una traducción no literal de su respectivo tutorial en Learning webGL, que a su vez está basada en el capítulo 9 del tutorial sobre openGL de NeHe. En él mostraremos una forma de organizar el código para crear una escena 3D con un montón de objetos animados independientemente. También veremos algo sobre cómo cambiar el color de la texturas, y lo que sucede cuando mezclamos (blending) texturas juntas.

Si tu navegador soporta webGL, este video muestra lo que deberías ver:

Abre este enlace para ver el ejemplo en vivo de webGL. Y aquí tienes la imagen GIF para descargarla y probarlo en local. Para poder verlo en acción, tendrás que usar un navegador que soporte webGL, como Mozilla Firefox o Google Chrome.

Puedes usar el checkbox para activar o no el efecto de brillo, que veremos cómo hacer más adelante. También puedes usar los cursores para hacer girar la animación alrededor de su eje X, y se puede ampliar y reducir haciendo uso de las teclas AvPag y RePag.

Más información acerca de cómo funciona todo, abajo…

Pero antes, la advertencia habitual: Estas lecciones están pensadas para personas con unos razonables conocimientos de programación previa, pero sin experiencia en gráficos 3D; el objetivo que buscamos es que podáis poner en marcha en poco tiempo vuestras propias páginas web con 3D, con una buena comprensión de lo que está ocurriendo en el código en cada momento. Si no has leído los ocho primeros tutoriales, deberías hacerlo, pues aquí pasaremos a comentar los cambios que tenemos que hacer al código de la última lección.

La mejor forma de mostrar éstas diferencias es empezar por mostrar la función que inicia toda la aplicación, webGLStart:

He comentado el código idéntico, para que se resalte más la nueva función initWorldObjects. Esta función crea objetos javascript para representar la escena, algo que veremos en breve, pero antes me gustaría subrayar algo. En todas las lecciones anteriores, en webGLStart solíamos iniciar el buffer de profundidad:

Pero como habrás visto, en este ejemplo ha sido eliminado. Probablemente recuerdes del último tutorial que la técnica blending y el buffer de profundidad no se llevan bien juntos, y como vamos a volver a aplicar blending, y sabiendo que por defecto, el uso del buffer de profundidad está desactivado, simplemente borramos esa línea.

El siguiente gran cambio se produce en la función animate. Antes la usábamos para actualizar algunas variables globales que representaban algunos cambios que se iban a hacer en nuestra escena, por ejemplo, el ángulo con el que rotar el cubo antes de dibujarlo. Lo que vamos a hacer ahora es bien sencillo: en lugar de actualizar las variables directamente, lo que vamos a hacer es usar un sencillo bucle que recorra todos los objetos de la escena (que estarán dentro de una lista llamada stars) y que ejecute sobre cada uno de ellos su propia función de animado, siguiendo la filosofía de la programación orientada a objetos:

Para seguir con esta filosofía, hay que delegar el código que pintará un objeto en pantalla, a una función dentro del propio objeto. Por lo tanto, drawScenes ahora se encarga de preparar de forma general el canvas, y llamará a la función de pintado de cada objeto de la escena con un bucle similar al anterior. Por partes:

El comienzo, es exactamente el mismo que estamos usando desde el primer tutorial. Iniciamos la vista del canvas, lo limpiamos, y establecemos una perspectiva personalizada.

A continuación activamos el blending. Usaremos el mismo tipo que en la lección anterior. Como recordarás, esta técnica permitirá a los objetos brillar a través de otros. Así podremos hacer que las partes negras de la imagen de la textura se dibujen como si fueran totalmente transparentes. Traducido a nuestro ejemplo, quiere decir que cuando estemos dibujando las estrellas que componen nuestra escena, los bits de negro serán transparentes. Para que lo veas más claro, la textura que estamos dibujando es ésta:

En el siguiente trozo de código:

Aquí sólo iniciamos la matriz de modelo-vista con la matriz de identidad, nos vomemos al centro dependiendo de la Z actual (zoom, que puede variar si usamos las teclas, como ya vimos) y la posibilidad de rotar toda la escena en el eje X (manejada también por teclado, en la variable tilt). Ahora ya estamos prácticamente listos para empezar a dibujar en la escena, sólo nos queda comprobar si el brillo está seleccionado:

Y luego, tal y como hicimos en la función animate, iteramos sobre la lista de estrellas para decirle a cada una que se dibuje a sí misma, pasándole la inclinación actual de la escena y el booleano que indica si usar o no el brillo. También le pasamos un valor de giro, spin, para que las estrellas giren sobre sus propios centros al mismo tiempo que orbitan alrededor del centro de la escena.

Y con esto, terminaos con drawScene. Hemos visto que las estrellas se encargarán ellas mismas de animarse y dibujarse. Veámos el código donde se crean, la función initWorldObjects:

Como podemos ver, sólo es un bucle que introducirá 50 estrellas en una lista (puedes experimentar modificando el código añadiendo o quitando estrellas, si lo deseas). Cada estrella necesita dos parámetros para crearse correctamente. El primero indica la distancia que habrá entre el centro de la escena y el centro de esa estrella, y el segundo es la velocidad con la que orbitará alrededor de ese centro.

A continuación explicaré el código con el que representamos una estrella, usando objetos javascript. Necesitaré explicar un poco de teoría previa; si ya conoces cómo crear y usar objetos en javascript, puedes saltarte los tres o cuatro párrafos siguientes.

El modelo de objetos de javascript es muy diferente al de otros lenguajes de programación. Permite crear objetos de varias formas distintias, muy diferentes entre sí. La forma que he elegido es la que me parece más fácil de entender, ya que cada objeto se crea como un diccionario (o matriz asociativa) sobre el que podemos trabajar. Los atributos del objeto son simple entradas en su diccionario que se asignan a valores específicos, y sus métodos son también entradas en su diccionario, pero asignadas a funciones (en javascript, se puede). Y por último, aprovecharemos que en javascript se puede acceder a un diccionario de dos formas:

Asi que utilizaremos esta última anotación para obtener una sintaxis de objetos muy parecida a la que se utiliza en otros lenguajes.
A continuación, en javascript, cuando estamos dentro de una función, existe una variable implícita llamada this, que apunta al “dueño” sobre el que se ejecuta esa función. En las funciones globales, es decir, en todas las funciones que hemos programado hasta ahora, ese dueño es el objeto window, algo que nos era irrelevante, pero si llamamos a la función con la palabra reservada new delante, y se la asignamos a alguna variable, el this apuntará a esa variable. Ésto es muy útil porque permitirá que las funciones definidas en nuestro diccionario (que actúa como si fuera un objeto), puedan utilizar el resto de funciones o atributos contenidos en ese mismo diccionario, lo que en la práctica quiere decir que podemos manejar a ese diccionario como si fuera un objeto. Además, el new también nos servirá para llamar a la función constructora del objeto.

Finalmente, para utilizar otra de las características de javascript, utilizaremos prototype. Los métodos podrían haberse definido dentro de la función constructora, dándole a la sintaxis un aspecto similar a Java, pero finalmente para enseñar todo lo que nos ofrece javascript, usaremos la opción de crear prototipos, con lo que tendremos un código más parecido a C++. Prototype sirve para extender los objetos en javascript, es decir, definir nuevos atributos o variables, y que afecten a todas las instancias de ese objeto que existan.

Puedes ver más sobre cómo crear objetos en Javascript en el tutorial de Sergio Pereira; y en español, en la página de Cristalab.

De nuevo, la teoría es un poco engorrosa, pero te aseguro que en el código, se verá todo más claro.

Empecemos viendo la función constructora del objeto estrella, Star:

En la función constructora se inicializan todas las variables que tiene el objeto estrella, éstas son el ángulo de giro actual de la estrella, que comienza en cero, y los parámetros que le pasábamos en initWorldObjects. Además, estamos llamando a un método del objeto Star, que se encarga de iniciar el color que tendrá esa estrella, y que veremos después.

A continuación, y usando la palabra reservada prototype, crearemos los métodos de nuestro objeto Star. Todos los objetos que creemos con un new Star, tendrán también definidos estos métodos. Veamos primero el draw por partes, que se encargará de dibujar la estrella en la escena:

Este método recoge los parámetros que le pasamos en la función drawScene. Para empezar, mete en la pila la matriz de modelo-vista actual, para poder volver a un estado anterior antes de que otras funciones que dibujen cosas en la escena sufran horribles efectos secundarios, como vimos en el primer tutorial.

A continuación, nos situamos sobre la posición donde se debe dibujar la estrella rotando toda la escena cierto ángulo y moviéndonos cierta distancia del centro.

Estas líneas sirven para que al cambiar el ángulo con el que vemos la escena, las texturas se sigan viendo. Una textura pintada sobre un cubo no tiene anchura.Ésto quiere decir que  sólo pueden ser vistas de frente o parcialmente inclinadas, pero si las viésemos de perfil, sólo veríamos una línea. Este código se encarga de hacer que la textura siempre esté mirando de frente a la cámara. por ello, hay que deshacer todo los giros que hemos hecho, incluído aquél que hacíamos en drawScene, y en un exacto orden inverso, pero ojo, SIN MOVERNOS de la posición donde estamos.

Las siguientes líneas son para dibujar la estrella:

Ignoremos de momento la primera parte, donde tenemos el código que hay que ejecutar si el brillo (twinkle) está activo. Para dibujar la estrella primero rotamos sobre el eje Z la cantidad indicada, es decir, para girar sobre su propio centro. A continuación, le pasamos al shader un color con el dibujar la textura en una variable uniforme, y finalmente llamamos a una función global llamada drawStar que veremos en un momento.

¿Y qué hay del brillo? Bueno, la estrella tiene dos colores asociados con ella. Su color normal, que es el que acabamos de ver, y su color de brillo. Éste lo simularemos dibujando una estrella (que no gira), con un color diferente. Luego dibujamos la otra estrella justo encima, y las mezclamos usando el blending. Además, haciendo que los rayos de la primera estrella se queden fijos, mientras los de la segunda estrella van girando, se consigue un bonito efecto.

Y para acabar, restauramos el estado anterior de la matriz de modelo-vista que hay en nuestra pila.

El siguiente método que veremos será animate, encargado de animar la estrella:

Al igual que en lecciones anteriores, en lugar de actualizar la escena con un valor constante, cuya velocidad real de animación dependerá de la velocidad de ejecución de la máquina donde corra este código, usaremos la técnica del ritmo constante (tiempo delta), con lo que las máquinas rápidas conseguirán una animación suave, y las más lentas, si bien tendrán una animación más a trompicones, al menos la verán moverse a la misma velocidad. Ahora, creo que la velocidad angular y la velocidad con la que orbitan las estrellas alrededor del centro de la escena fueron cuidadosamente calculadas por NeHe para que produjeran una bonita animación a 60 frames por segundo, valor que ponemos en una variable global fuera de la función para que no tenga que calcularse en cada llamada.

Por lo tanto, con este valor ajustaremos el ángulo de la estrella, es decir, el valor para su nueva posición en la órbita alrededor del centro de la escena.

…y también hay que disminuir ligeramente la distancia que tiene del centro de la escena, para dar el efecto en espiral. Pero si una estrella alcanza el centro, debe volver a colocarse en el extremo más alejado, volviéndole a cambiar el color.

El último método de nuestro prototipo de estrella es el que vimos en el constructor, que se encargará de elegir un color al azar para el color normal de la estrella y para el color de su brillo:

Y con ésto hemos terminado el prototipo de la estrella. No era tan difícil, ¿verdad?. Sólo queda mostrar una aburrida función que pinta la estrella en la escena, pues el prototipo sólo prepara los bufferes con las características únicas que distingue cada estrella. El código que las pinta, al ser el mismo para todas, lo hemos puesto en una función global, drawStar, que simplemente pinta un cuadrado en el que aplicará la textura de arriba.

No detallaré la función initBuffers, pues los cambios son triviales. Simplemente establece las posiciones de los vértices que forman el cuadrado, y la posición de la textura que debe aplicarse a ese cuadrado, como ya hemos visto en su tutorial correspondiente; ni tampoco explicaré cómo manejar los eventos del teclado con los cursores y las teclas de cambio de página. Y en el initTexture y handleLoadedTexture lo único que hacemos es cambiarel nombre de la textura que utilizamos. No merece la pena perder más tiempo con ellos.

Lo que vamos a hacer es pasar a los shaders, donde esta el último cambio que merece la pena ver. Todos los detalles sobre la iluminación se han eliminado en el vertex shader, con lo que el aspecto que tiene es muy igual al del tutorial 5. El fragment shader ha sufrido algún cambio más interesante:

…Pero no mucho más interesante. Todo lo que hacemos es recoger el color mediante una variable uniforme, que  nos fue suministrada en el método del prototipo Star que dibuja la estrella, y su uso para teñir la textura, que como podemos ver, es una imagen en blanco y negro. Con ésto conseguimos que las estrellas tengan un color más apropiado a nuestros intereses.

¡Y ésto es todo! Ahora ya conoces todo lo que hay que aprender en esta lección: Cómo crear objetos con javascript para facilitar enormemente la gestión de los objetos, sus atributos y sus movimientos por la escena cuando los tenemos en grandes cantidades, y un poco sobre cómo colorear y mezlar texturas.

Agradecimientos: Naturalmente agradezco a Giles Thomas, creador de learning webGL, ya que esta serie de tutoriales son traduciones casi literales de sus respectivas lecciones en inglés, que son de lo mejorcito que podemos encontrar para introducirnos en esta muy novedosa tecnología web. Él, a su vez, agradece la ayuda de terceras personas, que por cortesía, voy a reproducir a continuación.

Como siempre, estoy profundamente agradecido a NeHe por sus tutoriales de openGL que usé como base para el código de esta lección.

Etiquetas: , ,

Comentarios (2)

  • cuando haces el bucle sobre los stars con el for in tambien encuentra los métodos o propiedades del array stars por lo tanto no le puede aplicar el metodo draw y cuelga!, alguna sugerencia ?

  • ¿En qué navegador pasa eso? En Chrome y Firefox funciona.
    Puedes sustituírlo por la forma más usual:

    var n=stars.length;
    for (var i=0;i

¿Tienes algo que decir?

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