El API de WebGL JUAN CARLOS CONDE RAMÍREZ COMPUTER ANIMATION
Evolución de los gráficos, I Los conceptos básicos de gráficos interactivos no han cambiando mucho desde hace varios años. Por otro lado, las implementaciones evolucionan continuamente, en especial por la reciente proliferación de dispositivos y S. O. La piedra angular de todos estos cambios siempre ha sido OpenGL. Creado a finales de los 80 s, OpenGL ha sido por mucho tiempo el API estándar para la industria. Ha superado amenazas competitivas por parte de DirectX de Microsoft, emergiendo como el estándar indiscutible para la programación de gráficos 3D. Las características de las distintas plataformas como: computadoras de escritorio, televisiones, teléfonos inteligentes y tabletas, son tan divergentes que se han tenido que desarrollar diferentes versiones de OpenGL. FCC-BUAP 2
Evolución de los gráficos, II OpenGL ES (for Embedded Systems) es la versión de OpenGL desarrollada para ejecutarse sobre pequeños dispositivos (dispositivos móviles). Quizás de forma fortuita, ya que no se planeó al momento de su desarrollo, pero OpenGL ES se ha vuelto el núcleo ideal para WebGL. El API de WebGL es pequeño y ligero, lo que significa que no sólo es fácil de implementar en un navegador, sino que también es más seguro que se implemente dentro de los navegadores actuales. Así una aplicación WebGL escrita para determinado navegador funcionará de forma similar en otro. FCC-BUAP 3
Evolución de los gráficos, III Para el desarrollador Web promedio, WebGL representa una curva de aprendizaje llena de conceptos bastante especiales, por no decir extraños. Otra vez la buena noticia es que existen varias librerías de código abierto que hacen que el desarrollo con WebGL sea más accesible e incluso divertido. FCC-BUAP 4
Pipeline gráfico de WebGL FCC-BUAP 5
Anatomía de una aplicación WebGL, I Al final del día, WebGL sólo es una librería de dibujo una biblioteca de dibujo con esteroides, considerando la calidad y el potencial de los gráficos que pueden obtenerse con WebGL quien aprovecha al máximo el hardware gráfico de la mayoría de los dispositivos actuales. Pero la realidad es que sólo se trata de otro tipo de lienzo (canvas), similar al canvas 2D soportado de forma nativa en todo navegador con HTML5. De hecho WebGL utiliza el elemento <canvas> de HTML5 para mostrar gráficos 3D dentro de una página Web. FCC-BUAP 6
Anatomía de una aplicación WebGL, II Con el fin de renderizar objetos 3D con WebGL, una aplicación debe implementar los siguientes 8 pasos básicos: 1. Crear un elemento canvas. 2. Establecer un contexto de dibujo para el elemento canvas. 3. Inicializar el viewport o puerto de visión. 4. Inicializar una o más matrices para definir transformaciones desde el buffer de vértices (vertex buffer) hasta el espacio en pantalla. 5. Crear uno o más buffers que contengan los datos a ser renderizados (normalmente vértices). 6. Crear uno o más shaders para implementar el algoritmo de dibujado. 7. Inicializar los shaders con parámetros. 8. Dibujar. FCC-BUAP 7
canvas y contexto de dibujo, I Todo el renderizado de WebGL toma lugar dentro de un contexto determinado; un objeto DOM de JavaScript proporciona el API completa de WebGL. Esta estructura refleja el contexto de dibujo 2D provisto en la etiqueta <canvas> de HTML5. Por lo tanto, lo primero que se debe hacer es: Crea el elemento <canvas> en algún lugar de la página. Obtén el objeto DOM asociado con el elemento <canvas> mediante la instrucción: document.getelementbyid(..) Obtener un contexto WebGL para el objeto DOM del elemento <canvas> FCC-BUAP 8
canvas y contexto de dibujo, II Ejemplo 1-1. Obtención de un contexto WebGL desde un canvas. function initwebgl(canvas) { var gl; try { gl = canvas.getcontext("experimental-webgl"); } catch (e) { var msg = "Error creating WebGL Context!: " + e.tostring(); alert(msg); throw Error(msg); } return gl; } FCC-BUAP 9
canvas y contexto de dibujo, III Nótese que el bloque try/catch del ejemplo anterior es útil para los casos en donde el navegador no soporta WebGL, ya sea por el tipo o por la versión del navegador. Incluso puede darse el caso de navegadores que soporten WebGL ejecutándose sobre hardware obsoleto que no sea capas de brindar un contexto válido de renderizado para WebGL. Así que un código como el anterior, que detecta dicho soporte, será de gran utilidad para optar por la implementación de un renderizado de respaldo basado en un canvas 2D o al menos para poder proporcionar una salida elegante. FCC-BUAP 10
El viewport, I Una vez que se ha obtenido un contexto válido de dibujo para WebGL, a partir del canvas, es necesario especificar los límites rectangulares en donde se va a dibujar. A esto se le conoce como viewport en WebGL. La configuración del viewport en WebGL es simple; sólo se debe llamar al método viewport() del contexto. Ejemplo 1-2. Configuración delviewport dewebgl. function initviewport(gl, canvas) { gl.viewport(0, 0, canvas.width, canvas.height); } FCC-BUAP 11
El viewport, II Nótese que el objeto gl utilizado aquí es el mismo que se creo anteriormente en initwebgl(). En este caso, se ha inicializado el viewport de WebGL para ocupar el área completa de visualización del canvas. FCC-BUAP 12
Buffers y Arrays, I Ahora si ya está todo listo para comenzar a dibujar con WebGL. El dibujo con WebGL se realiza con primitivas que son tipos de objetos para dibujar tales como conjuntos de: puntos, líneas, triángulos (arreglos de triángulos) o tiras de triángulos (descritos de forma más corta). Tales primitivas utilizan arreglos de datos, llamados buffers para definir las posiciones de los vértices que serán dibujados. FCC-BUAP 13
Buffers y Arrays, II En el Ejemplo 1-3 se muestra cómo crear un buffer de datos para los vértices de un cuadrado de una unidad largo (1x1). Los resultados son retornados en un objeto de JavaScript que contiene los datos del buffer de vértices, el tamaño de la estructura de vértices (en este caso, tres números de coma flotante para almacenar x, y, y z), el número de vértices y el tipo de primitiva que será usada para dibujar el cuadrado, para este ejemplo se usa una tira de triángulos (triangle strip). Un triangle strip es una primitiva que define una secuencia de triángulos usando los primeros tres vértices para crear el primer triángulo, y cada vértice subsecuente junto con los dos anteriores para crear los siguientes. FCC-BUAP 14
Buffers y Arrays, III Ejemplo 1-3. Creación del buffer de datos para los vértices de un cuadrado de 1x1 unidades. function createsquare(gl) { var vertexbuffer; vertexbuffer = gl.createbuffer(); gl.bindbuffer(gl.array_buffer, vertexbuffer); var verts = [ 0.5, 0.5, 0.0, -0.5, 0.5, 0.0, 0.5, -0.5, 0.0, -0.5, -0.5, 0.0 ]; gl.bufferdata(gl.array_buffer, new Float32Array(verts), gl.static_draw); var square = {buffer:vertexbuffer, vertsize:3, nverts:4, primtype:gl.triangle_strip}; return square; } FCC-BUAP 15
Buffers y Arrays, IV Nótese que el tipo Float32Array es un nuevo tipo de dato introducido a los navegadores Web para uso de WebGL. Float32Array es una clase de BufferArray, también conocido como arreglo tipado (typed array). Éste es un tipo de tipo de JavaScript que almacena datos binarios compactos. Los arreglos tipados pueden ser accedidos desde JavaScript utilizando la misma sintaxis de los arreglos ordinarios, pero son más rápidos y consumen menos memoria. Son ideales para usarse con datos binarios cuando el rendimiento es crítico. FCC-BUAP 16
Buffers y Arrays, V Los arreglos tipados son de uso general, pero su introducción dentro de los navegadores Web fue gracias a los esfuerzos iniciales de WebGL. La última especificación puede ser consultada en el sitio Web del grupo Khronos en: http://www.ecma-international.org/ecma-262/6.0/#sec-typedarray-objects FCC-BUAP 17
Matrices, I Antes de que se pueda dibujar, por ejemplo un cuadrado, se deben crear un par de matrices. Primero, se necesita una matriz para definir dónde será posicionado el cuadrado dentro del sistema de coordenadas 3D, relativo a la cámara activa. Esta es conocida como matriz ModelView, debido a que combina transformaciones del modelo (malla 3D) y de la cámara. En el Ejemplo 1-4, se está transformando el cuadrado a través de una traslación a lo largo del eje negativo de Z (i.e. alejándolo de la cámara -3.333 unidades). FCC-BUAP 18
Matrices, II La segunda matriz que se necesita es la matriz de proyección (projection matrix), la cual será requerida por el shader para convertir el espacio de coordenadas 3D del modelo (espacio de la cámara) en coordenadas de dibujo 2D (espacio del viewport). Esta matriz es bastante desagradable; la mayor parte de los programadores no codifica las matrices de proyección a mano, por lo general usa librerías. Existe una gran librería de código abierto llamada glmatrix que realiza matemáticas matriciales en JavaScript (https://github.com/toji/gl-matrix, por Brandon Jones). FCC-BUAP 19
Matrices, III Ejemplo 1-4. Configuración de las matrices ModelView y de Proyección. function initmatrices() { // Matriz de transformación para el cuadrado traslación negativa en Z // para la cámara modelviewmatrix = new Float32Array( [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 3.333, 1]); // Matriz de proyección (para un campo de visión de 45 grados) projectionmatrix = new Float32Array( [2.41421, 0, 0, 0, 0, 2.41421, 0, 0, 0, 0, 1.002002, 1, 0, 0, 0.2002002, 0]); } FCC-BUAP 20
Los shaders, I Hay una pieza más de la configuración que considerar: el shader. Como se mencionó antes, los shaders son pequeños programas escritos en un lenguaje de alto nivel tipo C, que definen cómo serán mostrados los pixeles de los objetos 3D dibujados en pantalla. WebGL requiere que el desarrollador proporcione un shader por cada objeto a dibujar. El mismo shader puede ser usado para múltiples objetos, por lo que en la práctica es suficiente proporcionar un solo shader para toda la aplicación (reutilizándolo con diferentes parámetros). FCC-BUAP 21
Los shaders, II Un shader se compone típicamente de dos partes: 1. El vertex shader 2. El fragment shader (también conocido como pixel shader) El vertex shader es responsable de transformar coordenadas o vértices del objeto (3D) en coordenadas de pantalla (2D). El fragment shader es responsable de generar el color final de salida para cada pixel de los vértices transformados, basado en parámetros de entrada tales como: color, textura, iluminación y material. FCC-BUAP 22
Los shaders, III Shaders: Relación entre vértices y fragmentos FCC-BUAP 23
Los shaders, IV Para nuestro primer ejemplo, el vertex shader combina los valores de la modelviewmatrix y projectionmatrix para crear un vértice transformado para cada entrada, y el fragment shader simplemente genera un color blanco (en código duro). En WebGL, la configuración de shaders requiere una secuencia de pasos, incluyendo la compilación individual de las piezas, y su vinculación posterior. Por brevedad, a continuación se muestra sólo el código GLSL ES para nuestros dos shaders de ejemplo, pero el código completo se puede consultar en documento HTML completo. FCC-BUAP 24
Los shaders, V Ejemplo 1-5. Los vertex y fragment shaders var vertexshadersource = " attribute vec3 vertexpos;\n" + " uniform mat4 modelviewmatrix;\n" + " uniform mat4 projectionmatrix;\n" + " void main(void) {\n" + " // Return the transformed and projected vertex value\n" + " gl_position = projectionmatrix * modelviewmatrix * \n" + vec4(vertexpos, 1.0);\n" + " }\n"; var fragmentshadersource = " void main(void) {\n" + " // Return the pixel color: always output white\n" + " gl_fragcolor = vec4(1.0, 1.0, 1.0, 1.0);\n" + "}\n"; FCC-BUAP 25
Dibujo de primitivas, I Por fin está todo listo para dibujar un cuadrado: el contexto ha sido creado, elviewport ha sido configurado y el buffer de vértices, matrices y shaders han sido creados e inicializados. Ahora es necesario definir una función, draw(), la cual tomará el contexto de WebGL y el cuadrado previamente definido. FCC-BUAP 26
Dibujo de primitivas, II Este procedimiento involucra los siguientes pasos: 1. La función limpia el canvas con un color de fondo blanco. 2. Después se enlaza ( binds ) el buffer de vértices con el que se dibujará el cuadrado. 3. Se coloca ( uses ) el shader que se usará. 4. Posteriormente se conecta el buffer de vértices y las matrices como entradas del shader. 5. Finalmente se invoca al método drawarrays() de WebGL para dibujar el cuadrado. El método drawarrays() recibe como argumentos qué tipo de primitiva y cuántos vértices tiene la primitiva; WebGL conoce el resto de la información puesto que ya se han establecido los otros elementos como parte de su estado. FCC-BUAP 27
Dibujo de primitivas, III Ejemplo 1-6. El código dedibujo. function draw(gl, obj) { // se limpia el fondo (con blanco) gl.clearcolor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.color_buffer_bit); // se configure el buffer de vertices que se dibujará gl.bindbuffer(gl.array_buffer, obj.buffer); // se configure el shader que se usará gl.useprogram(shaderprogram); // se vinculan los parámetros del shader: posición de vertices y matrices (projection/model) gl.vertexattribpointer(shadervertexpositionattribute, obj.vertsize, gl.float, false, 0, 0); gl.uniformmatrix4fv(shaderprojectionmatrixuniform, false, projectionmatrix); gl.uniformmatrix4fv(shadermodelviewmatrixuniform, false, modelviewmatrix); // se dibuja el objeto gl.drawarrays(obj.primtype, 0, obj.nverts); } FCC-BUAP 28
Dibujo de primitivas, IV FCC-BUAP 29
Consideraciones... Hasta aquí ha terminado el tour a nuestra primera aplicación básica con WebGL. Seguramente estés pensando Uff! Cuanto trabajo para dibujar sólo un cuadrado de color blanco, Cielos, ni siquiera es un objeto 3D!. Si bien la programación con WebGL a este nivel es ardua, la API es ligera y simple a costa de tener que hacer mucha codificación del lado de la aplicación. Además, obviamente no se desea utilizar WebGL sólo para dibujar objetos 2D. Bueno hay buenas noticias, otros programadores ya han realizado el trabajo duro por ti. Por tanto, más adelante se crearán aplicaciones con WebGL usando la librería Three.js FCC-BUAP 30
Un vistazo a Threejs.org FCC-BUAP 31