Diseño y desarrollo de un juego en WebGL - UPCommons

13 jun. 2012 - Un buen ejemplo es que la geometrıa en. OpenGL se puede usar en Modo Immediato, Display Lists o Vertex Arrays. Sin embargo en el caso ...
5MB Größe 18 Downloads 52 vistas
Informaci´ on del Proyecto T´ıtulo: Dise˜ no y desarrollo de un juego en WebGL Autor: Jaime Crespo Amigo Fecha: 13 de junio de 2012 Director: Lluis Solano Albajes Departamento del director: Lenguajes y Sistemas Inform´ aticos Presidente: Luis P´erez Vidal Vocal: Francisco Javier Heredia Cervera Titulaci´ on: Ingenier´ıa Superior de Inform´ atica Centro: Facultad de Inform´ atica de Barcelona Universidad: Universidad Polit´ecnica de Catalunya

´ tica de Barcelona Facultad de Informa

Proyecto Final de Carrera ´ tica Ingiener´ıa Superior de Informa

Dise˜ no y desarrollo de un juego en WebGL

Autor:

Director:

Jaime Crespo Amigo

Lluis Solano Albajes

Un Proyecto realizado seg´ un los requerimientos de la Normativa Plan 2003 Universidad Polit´ecnica de Catalunya

13 de junio de 2012

Declaraci´ on de Autor´ıa Yo, Jaime Crespo Amigo, declaro que el proyecto titulado ’Dise˜ no y desarrollo de un juego en WebGL’ y el trabajo presentado son de mi autor´ıa. Yo confirmo que:



Este trabajo ha sido realizado principalmente con el objetivo de presentarlo como Proyecto Final de Carrera.



Donde se ha consultado la publicaci´on de otros est´a claramente especificado.



Donde he citado el trabajo de otros, la fuente del c´odigo est´a presente. Con excepci´ on de esas citaciones el Proyecto es enteramente mi trabajo.



Donde el proyecto esta basado en trabajo compartido, est´a claramente especificado la aportaci´ on de cada uno.

Firmado:

Fecha: 13 de junio de 2012

i

“Como en cualquier lenguaje de programaci´ on, saber lo que haces marca la diferencia.”

Douglas Crockford

Agradecimientos Debo agredecer este proyecto a todas aquellas personas que sin ´animo de lucro escriben en foros y blogs explicando, ayudando y enriqueciendo las tecnolog´ıas en las que se basa este proyecto, tecnolog´ıas libres. A todos ellos, gracias.

iii

´Indice general ´ Indice General

IV

Lista de Im´ agenes

VII

Lista de C´ odigo Fuente

IX

Lista de Tablas

X

Abreviaciones y Anglicismos

1. Introducci´ on 1.1. Motivaci´ on y Contexto . . . . 1.2. Objetivos . . . . . . . . . . . 1.2.1. Objetivo Principal . . 1.2.2. Objetivos Secundarios 1.3. Proyecto Compartido . . . . .

XI

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

2. WebGL 2.1. ¿Qu´e es WebGL? . . . . . . . . . . . . . . . . . . . . . 2.1.1. OpengGL ES 2.0 . . . . . . . . . . . . . . . . . 2.1.2. Pipeline de renderizado de WebGL . . . . . . . 2.1.3. HTML5 Elemento Canvas . . . . . . . . . . . . 2.2. Navegadores . . . . . . . . . . . . . . . . . . . . . . . . 2.2.1. Historia . . . . . . . . . . . . . . . . . . . . . . 2.2.2. Funcionamiento Interno de WebGL . . . . . . . 2.2.3. Soporte Mayo 2012 . . . . . . . . . . . . . . . . 2.2.4. Internet Explorer . . . . . . . . . . . . . . . . . 2.2.5. Seguridad . . . . . . . . . . . . . . . . . . . . . 2.2.6. WebGL en m´ oviles y Tablets . . . . . . . . . . 2.3. State of the art Web 3D . . . . . . . . . . . . . . . . . 2.3.1. Historia 3D en la web . . . . . . . . . . . . . . 2.3.2. 3D surfing, Hype or real? . . . . . . . . . . . . 2.3.3. Competidores . . . . . . . . . . . . . . . . . . . 2.3.3.1. ¿En qu´e podemos experimentar 3D en 2.3.3.2. ¿En qu´e merece la pena arriesgar? . .

iv

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . la Web? . . . . .

. . . . .

. . . . . . . . . . . . . . . . .

. . . . .

. . . . . . . . . . . . . . . . .

. . . . .

. . . . . . . . . . . . . . . . .

. . . . .

. . . . . . . . . . . . . . . . .

. . . . .

. . . . . . . . . . . . . . . . .

. . . . .

1 1 3 3 3 7

. . . . . . . . . . . . . . . . .

8 8 9 10 16 18 18 18 22 25 26 28 29 29 31 32 32 33

´ Indice General 3. El Juego 3.1. Visi´ on Global . . . . . . . . . . . . . . . . . . 3.2. Esquema General . . . . . . . . . . . . . . . . 3.3. Capturas de Pantalla . . . . . . . . . . . . . . 3.4. An´ alisis . . . . . . . . . . . . . . . . . . . . . 3.4.1. Requisitos Funcionales . . . . . . . . . 3.4.2. Requisitos No Funcionales . . . . . . . 3.4.3. Diagrama Entidad - Relaci´on . . . . . 3.5. Especificaci´ on . . . . . . . . . . . . . . . . . . 3.5.1. Metodolog´ıa de Desarrollo . . . . . . . 3.5.2. Historias . . . . . . . . . . . . . . . . . 3.6. Arquitectura . . . . . . . . . . . . . . . . . . 3.6.1. Control de Versiones . . . . . . . . . . 3.6.2. Dise˜ no . . . . . . . . . . . . . . . . . . 3.6.3. Patrones de Dise˜ no . . . . . . . . . . . 3.6.4. Componentes . . . . . . . . . . . . . . 3.6.4.1. Render . . . . . . . . . . . . 3.6.4.2. Jugador Principal . . . . . . 3.6.4.3. C´ amara . . . . . . . . . . . . 3.6.4.4. Animaciones y Modelos MD5 3.6.4.5. Audio . . . . . . . . . . . . . 3.6.4.6. F´ısica . . . . . . . . . . . . . 3.6.4.7. Shaders Factory . . . . . . . 3.6.4.8. Iluminaci´on . . . . . . . . . . 3.6.5. Sistema Global . . . . . . . . . . . . . 3.6.6. Diagramas de Secuencia de un Frame 3.6.7. Ficheros de Configuraci´on . . . . . . . 3.7. Implementaci´ on . . . . . . . . . . . . . . . . . 3.7.1. Javascript . . . . . . . . . . . . . . . . 3.7.1.1. Partes Malas . . . . . . . . . 3.7.1.2. Partes Buenas . . . . . . . . 3.7.2. Render Loop . . . . . . . . . . . . . . 3.7.3. Instanciaci´ on . . . . . . . . . . . . . . 3.7.4. Componentes . . . . . . . . . . . . . . 3.7.4.1. Patr´ on M´odulo . . . . . . . . 3.7.4.2. Camera . . . . . . . . . . . . 3.7.4.3. Render . . . . . . . . . . . . 3.7.4.4. Modelos . . . . . . . . . . . . 3.7.4.5. Iluminaci´on . . . . . . . . . . 3.7.4.6. Factor´ıa de Shaders . . . . . 3.7.4.7. Shaders . . . . . . . . . . . . 3.7.4.8. Jugador Principal . . . . . . 3.7.4.9. F´ısica . . . . . . . . . . . . . 3.7.4.10. Audio . . . . . . . . . . . . .

v

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

35 35 36 37 39 39 40 41 42 42 44 47 47 48 48 49 49 52 52 53 54 54 56 57 58 60 61 62 62 62 63 67 68 70 70 72 74 76 77 82 84 89 92 94

4. T´ ecnicas, Optimizaciones y Rendimiento 96 4.1. Javascript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

´ Indice General

vi

4.1.1. Arrays . . . . . . . . . . . . . . . . . . 4.1.2. Objetos . . . . . . . . . . . . . . . . . 4.1.3. Optimizaciones internas de Javascript 4.1.4. Garbage Collector . . . . . . . . . . . 4.1.5. Render Loop Memory . . . . . . . . . 4.2. Reglas Generales . . . . . . . . . . . . . . . . 4.2.1. Llamadas a WebGL . . . . . . . . . . 4.2.2. Buffering . . . . . . . . . . . . . . . . 4.3. CPU vs GPU . . . . . . . . . . . . . . . . . . 4.4. Rendimiento Pipeline . . . . . . . . . . . . . . 4.5. Web Workers . . . . . . . . . . . . . . . . . . 4.6. Optimizar Pintado . . . . . . . . . . . . . . . 4.7. Optimizar Geometr´ıa . . . . . . . . . . . . . . 4.8. Optimizar Shaders . . . . . . . . . . . . . . . 4.9. Redimensionar Canvas CSS . . . . . . . . . . 4.10. Flujo de Datos . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

96 97 99 99 101 103 103 104 106 107 108 110 112 114 116 117

5. Planificaci´ on y Costes 119 5.1. Planificaci´ on y Costes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 6. Conclusiones 6.1. Evaluaci´ on de Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2. Dificultades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3. Conclusiones personales . . . . . . . . . . . . . . . . . . . . . . . . . . .

125 . 125 . 129 . 131

A. Modelos Wavefront 132 A.1. Introducci´ on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 A.2. Especificaci´ on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 A.3. Importaci´ on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 B. Modelos MD5 B.1. Introducci´ on . . B.2. Especificaci´ on . B.3. Importaci´ on . . B.4. Skinning GPU

Bibliograf´ıa

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

136 . 136 . 138 . 144 . 144

151

Lista de Im´ agenes 1.1. HTML5 Multi Plataforma . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Esquema Trabajo Compartido . . . . . . . . . . . . . . . . . . . . . . . . . 2.1. WebGL Logo . . . . . . . . . . . . . . . . 2.2. OpenGL ES Logo . . . . . . . . . . . . . . 2.3. OpenGL ES Pipeline . . . . . . . . . . . . 2.4. Vertex Shader . . . . . . . . . . . . . . . . 2.5. Rasterization . . . . . . . . . . . . . . . . 2.6. Fragment Shader . . . . . . . . . . . . . . 2.7. Per Fragment Operations . . . . . . . . . 2.8. WebGL Rendering Pipeline . . . . . . . . 2.9. Chromium Logo . . . . . . . . . . . . . . 2.10. Chromium Rendering Process . . . . . . . 2.11. Chromium GPU Process . . . . . . . . . . 2.12. Soporte WebGL Mayo 2012 . . . . . . . . 2.13. Webgl por Sistema Operativo . . . . . . . 2.14. Webgl por Navegador . . . . . . . . . . . 2.15. Tendencia navegadores Mayo 2012 . . . . 2.16. Tendencia Sistemas Operativos Mayo 2012 2.17. Mecanismo de un ataque DoS en WebGL 2.18. Historia 3D est´ andares abiertos . . . . . . 2.19. Web3D logos . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

8 9 10 11 12 13 14 15 18 19 20 22 22 23 24 24 27 29 33

3.1. Simulaci´ on de un borrador del jugador principal del juego DOOM3 3.2. Esquema General del Juego . . . . . . . . . . . . . . . . . . . . . . 3.3. Captura de Pantalla del comienzo del juego . . . . . . . . . . . . . 3.4. Captura de Pantalla del juego en funcionamiento . . . . . . . . . . 3.5. Captura de Pantalla del juego mostrando las Bounding Boxes . . . 3.6. Captura de Pantalla del final del juego . . . . . . . . . . . . . . . . 3.7. Diagrama Entidad-Relaci´on . . . . . . . . . . . . . . . . . . . . . . 3.8. Modelo Iterativo Incremental . . . . . . . . . . . . . . . . . . . . . 3.9. Control de Versiones . . . . . . . . . . . . . . . . . . . . . . . . . . 3.10. Modelo de Dise˜ no . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.11. Dise˜ no Coponente Render UML . . . . . . . . . . . . . . . . . . . . 3.12. Diagrama Secuencia Render Loop . . . . . . . . . . . . . . . . . . . 3.13. Dise˜ no Componente Jugador Principal UML . . . . . . . . . . . . 3.14. Dise˜ no Componente C´amara UML. . . . . . . . . . . . . . . . . . . 3.15. Dise˜ no Componente Modelos MD5 UML. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

35 36 37 37 38 38 41 43 47 48 50 51 52 52 53

vii

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

2 7

Lista de Im´ agenes

viii

3.16. Dise˜ no Componente Audio UML . . . . . . 3.17. Dise˜ no Componente F´ısicas UML . . . . . . 3.18. Dise˜ no Componente Shaders Factory UML 3.19. Dise˜ no Componente Luces UML . . . . . . 3.20. Dise˜ no Sistema UML . . . . . . . . . . . . . 3.21. Diagrama de Secuencia de un frame . . . . 3.22. Secuencia de los Sistemas de coordenadas . 3.23. C´ amara en tercera Persona . . . . . . . . . 3.24. Tipos de Modelos usados . . . . . . . . . . 3.25. Modelo Phons de Iluminaci´on . . . . . . . . 3.26. Intensidad Luz Difusa . . . . . . . . . . . . 3.27. Intensidad Luz Especular . . . . . . . . . . 3.28. F´ısicas en un Web Worker . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

54 55 56 57 58 60 72 73 76 77 78 78 93

4.1. Etapa de un frame en WebGL . . . 4.2. Buffering Input Usuario . . . . . . 4.3. Rendimiento Pipeline . . . . . . . . 4.4. Esquema Web Worker . . . . . . . 4.5. Esquema Web Worker con la f´ısica 4.6. Pintado L´ ogico en Canvas . . . . . 4.7. Pintado L´ ogico en WebGL . . . . . 4.8. Orden de Pintado en WebGL . . . 4.9. Tri´ angulos sin ´ındices . . . . . . . 4.10. Tri´ angulos con ´ındices . . . . . . . 4.11. Estructura de datos . . . . . . . . 4.12. Pir´ amide de n´ umero de llamadas . 4.13. Redimensionar Canvas con CSS . . 4.14. Render Sync Flush . . . . . . . . . 4.15. Render gl Flush . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

104 104 107 108 109 110 110 111 112 112 113 114 116 117 118

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

5.1. Diagrama Gantt de la Planificaci´on . . . . . . . . . . . . . . . . . . . . . . 120 5.2. Diagrama de Rendimiento Personal . . . . . . . . . . . . . . . . . . . . . . 121 A.1. Modelos Wavefront . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 B.1. Modelos MD5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 B.2. Representaci´ on de Skeleton y Skinning . . . . . . . . . . . . . . . . . . . . 139

Lista de C´ odigo Fuente 2.1. Crear un Canvas . . . . . . . . . . . . . . . 2.2. Preparar el contexto WebGL . . . . . . . . 2.3. Inicializar WebGL . . . . . . . . . . . . . . 3.1. Ejemplo Data Hiding Javascript . . . . . . . 3.2. Clases en Javascript . . . . . . . . . . . . . 3.3. Closures en Javascript . . . . . . . . . . . . 3.4. Game Loop en HTML5 . . . . . . . . . . . 3.5. Pintado sin instanciaci´on . . . . . . . . . . 3.6. Pintado con instanciaci´on . . . . . . . . . . 3.7. Patr´ on M´ odulo en Javascript . . . . . . . . 3.8. Matrices de C´ amara . . . . . . . . . . . . . 3.9. Ejemplo de pintado de un modelo completo 3.10. Entidad Luz . . . . . . . . . . . . . . . . . . 3.11. Entidad Material . . . . . . . . . . . . . . . 3.12. Entidad Material . . . . . . . . . . . . . . . 3.13. Carga y representaci´ on de los Programas de 3.14. Vertex Shader . . . . . . . . . . . . . . . . . 3.15. Fragment Shader . . . . . . . . . . . . . . . 3.16. Fire Handler . . . . . . . . . . . . . . . . . 3.17. Movement Handler . . . . . . . . . . . . . . 3.18. Implementaci´ on Componente Audio. . . . . 4.1. Javascript Arrays Mala Idea . . . . . . . . . 4.2. Javascript Arrays Buena Idea . . . . . . . . 4.3. Javascript Objects Mala Idea . . . . . . . . 4.4. Javascript Objects Buena Idea . . . . . . . 4.5. Javascript Garbage Collector Mala Idea . . 4.6. Javascript Garbage Collector Buena Idea . 4.7. Memoria Render Loop Mala Idea . . . . . . 4.8. Memoria Render Loop Buena Idea . . . . . 4.9. Buffering Javascript . . . . . . . . . . . . . 4.10. Lectura del Buffer Javascript . . . . . . . . 4.11. Dependencia de Datos GLSL . . . . . . . . A.1. Especificaci´ on Modelos Wavefront . . . . . . B.1. Skinning por CPU . . . . . . . . . . . . . . B.2. Skinning por GPU . . . . . . . . . . . . . .

ix

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Shading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

16 16 17 64 65 65 67 68 69 70 73 74 79 79 80 82 84 86 89 90 94 96 97 97 98 100 100 101 102 105 105 115 133 145 146

´Indice de cuadros 2.1. Tabla tecnolog´ıas Web 3D - 1 . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.2. Tabla tecnolog´ıas Web 3D - 2 . . . . . . . . . . . . . . . . . . . . . . . . . 32 5.1. 5.2. 5.3. 5.4. 5.5.

Tabla Tabla Tabla Tabla Tabla

de de de de de

tareas y horas . . . . . . dedicaci´ on por perfil . . . costes de trabajadores . . costes de recursos . . . . costes totales del proyecto

x

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

122 123 124 124 124

Abreviaciones y Anglicismos HTML

HyperText Markup Language

WebGL

Web Graphics Library

OpenGL ES

Open Graphics Library Embedded Systems

GPU

Graphics Processing Unit

CPU

Ccentral Processing Unit

ANGLE

Almost Native Graphics Layer Engine

IPC

Inter Pprocess Communication

API

Application Programming Interface

JS

JavaSscript

FPS

Frames Per Second

*Nota: Se ha abusado de muchos anglicismos en palabras t´ecnicas provinientes del ingl´es. Si los traduj´eramos perder´ıan rigor o precisi´on ya que su origen no es el castellano, lengua de este proyecto.

xi

Cap´ıtulo 1

Introducci´ on 1.1.

Motivaci´ on y Contexto

A d´ıa de hoy, Mayo 2012, los juegos en la Web est´an empezando a crecer de forma muy interesante. Esto se debe principalmente a tres factores:



El primer factor de todos es que la tecnolog´ıa de hoy en d´ıa est´a preparada para albergar juegos en la Web. Hay alto ancho de banda y el Hardware de las plataformas Web es cada vez mejor. Un claro ejemplo son las t´ıpicas aplicaciones de escritorio (ofim´ atica, programas multimedia, gestores de correo, juegos, etc ) que est´ an siendo absorbidas por el navegador como aplicaciones web, Web Apps.



Hasta hace unos a˜ nos los juegos eran territorio de aplicaciones de PC y de consolas. Gracias al primer punto y a los dispositivos m´ oviles la web se ha introducido en todas las plataformas existentes. Ahora est´an tanto en televisiones, SmartPhones, PC’s, Tablets, etc.



Mucho de los navegadores hacen que esto sea posible gracias a los est´ andares de la Web. Estos est´ andares hacen que un mismo programa puede ser ejecutado en varias plataformas, bajo navegadores diferentes con id´entico resultado. Estos est´ andares son tales como HTML5

1

y ECMA SCRIPT2

Por lo tanto, los navegores est´an en todos los lados y preparados. ¿Pero qu´e le falta para competir? Lo que siempre hab´ıa da˜ nado mucho a la Web es el rendimiento. Bajo rendimiento e inexistencia de aceleraci´on 3D. Pero ya no es as´ı, gracias a WebGL y HTML5 esto ya es posible. 1 2

Es la quinta revisi´ on del lenguage b´ asico de la World Wide Web, HTML. Especificaci´ on de un lenguaje de programaci´ on en el que se basa Javascript.

1

Cap´ıtulo 1. Introducci´ on

2

Imagen 1.1: HTML5 multi-plataforma: “write once, run anywhere”

Hasta hace poco tiempo los navegadores han tenido juegos pero ya sabemos todos de qu´e tipo. Juegos casual, de poco detalle gr´afico e incomporable con el resto de juegos del mercado. No quiere decir que no triunfar´an sino que a nivel t´ecnico est´an muy por debajo. La principal motivaci´ on, como amante del desarrollo de videojuegos, es conocer y explorar esta nueva plataforma. La programaci´on de videojuegos no es solo muy entretenida y gratificante sino que adem´as es dif´ıcil porque siempre se quiere llevar la aplicaci´ on al m´ aximo de recursos para proporcionar la m´axima calidad de contenido. El gran reto de este proyecto, es intentar llevar esta expresi´on al m´aximo para ofrecer un juego digno en aceleraci´ on 3D en la Web que abra las puertas a otros proyectos y ofrezca soluciones a problemas tencol´ ogicos en este ´ambito.

Cap´ıtulo 1. Introducci´ on

1.2. 1.2.1.

3

Objetivos Objetivo Principal

El objetivo principal de este proyecto es dar una soluci´on tecnol´ogica al desarrollo de un juego completo, en todos sus ´ambitos (dise˜ no, implementaci´on y experiencia del usuario dentro de la Web) y utilizando WebGL como tecnolog´ıa Web 3D. Estamos hablando de un juego que contenga f´ısicas, iluminaci´on din´amica, 3D, modelos, Skinning, Audio y un renderizado digno de los juegos de escritorio. En otras palabras, sabemos como se hace un juego en PC, en los m´oviles, en las consolas, etc. Pero con estas caracter´ısticas ¿En la Web?. No se pretende conseguir un juego cerrado que se publique sino ofrecer unos patrones y t´ecnicas para desarrollarlo en estas nuevas tecnolog´ıas: HTML5 y WebGL.

1.2.2.

Objetivos Secundarios

El objetivo principal es muy ambicioso y extenso. Si analizamos todo el trabajo que hay que realizar es necesario especificar hasta donde queremos llegar subdividiendo el objetivo principal en otros secundarios m´as concretos: Crear un juego innovador, accesible a la mayor´ıa de plataformas sin instalador, sin plugins, s´ olo con un navegador apto y una tarjeta gr´ afica. Casi la totalidad de todos los juegos en los navegadores requieren de un plugin para ser ejecutados, ya sean: Flash, Unity o Java. Gracias a WebGL podemos acceder a la GPU sin una pasarela privativa. Esto permite poder renderizar casi cualquier elemento 3D en la Web. WebGL surgi´ o hace justamente un a˜ no y se han visto grandes propuestas pero no juegos que combinen todos los elementos de un videojuego t´ıpico. Usar tecnolog´ıas libres durante todo el proyecto. Las plataformas actuales est´an intentando cerrarse a un tecnolog´ıa para enfocar el mercado a su antojo. Nuestro objetivo es usar en todo momento tecnolog´ıas libres para demostrar el potencial de ellas y no depender de los intereses de un software privativo. En este caso son: HTML5, Javascript DOM API, Javascript y WebGL como tecnolog´ıas de desarrollo. Usar HTML5 como base nos permite so˜ nar con distribuir en todas las plataformas que lo apoyan.

Cap´ıtulo 1. Introducci´ on

4

Llegar a crear una escena real´ıstica en tiempo real Para conseguir una escena real´ıstica entran muchos factores. Iluminaci´on din´amica mediante Shaders. Desarrollar una f´ısica para que los elementos en la escena se comporten como en la realidad mediante fuerzas, empezando por la gravedad. Modelos est´aticos y din´ amicos texturizados y materializados para dar detalle a la escena. La Inteligencia Artificial nos ayudar´ a a modelar comportamientos en los elementos controlados por el sistema. Y las t´ecnicas de procesado nos servir´an para crear efectos y detalles dentro del renderizado entre muchas otras. Crear una sensaci´ on de inmersi´ on digna de aplicaciones de escritorio. Mediante un Gameplay acurado, un alto detalle gr´afico, una escena real´ısitica y un viewport grande es posible desarrollar un juego altamente interactivo entre el jugador y el juego. Conseguir una tasa de renderizado de 60FPS en el navegador. La tasa perfecta de renderizado son 60 frames por segundo justo la frecuencia de actualizacion de la mayor´ıa de las pantallas actuales. Esto quiere decir que cada frame se tiene que ejecutar en 17 milisegundos. Y en cada frame actualizar toda la escena, calcular posiciones mediante la f´ısica, leer inputs del usuario, calcular colisiones, preparar el renderizado y enviar todo a GPU. Todo este sistema en el navegador parece imposible pero no lo es si se usan las t´ecnicas correctas. Conocer WebGL en todo su potencial. WebGL es una API DOM de acceso a la GPU basada en OPENGL ES 2.0. Es una api de muy bajo nivel y simple. La idea es que si WebGL es una API Html5 tiene que estar dise˜ nada para ser accedida desde cualquier dispositivo: PC, smartphones,tablets...Los dispositivos m´ oviles tienen un hardware muy limitado por eso WebGL simplifica el acceso y limita los recursos much´ısimo. Hace falta conocer al detalle todo los elementos del Pipeline para aprovechar al m´ aximo su potencial. WebGL funciona mediante un Pipeline programable requiriendo de Shaders (Vertex y Fragment Shaders). Por lo tanto no basta con conocer la API de WebGL sino profundizar en el conocimiento de los shaders para ejecutar la funcionalidad de la aplicaci´on a la perfecci´on y explotar el prop´ostio de la GPU , la paralelizaci´ on de c´ alculos, que nos ofrece WebGL.

Cap´ıtulo 1. Introducci´ on

5

Analizar WebGL como futura posibilidad 3D y multi-plataforma. El proyecto mostrar´ a como est´a la Web 3D cada d´ıa. La idea es describir de qu´e es capaz esta tecnolog´ıa y de qu´e son capaces las plataformas para analizar su futuro como herramienta principal del 3D en la web. Estudiar estrategias y patrones para adaptarse a la web. Javascript no es el entorno perfecto para desarrollar un videojuego porque el lenguaje en s´ı no se cre´ o con ese fin. Por ello vamos a intentar adaptar los patrones de dise˜ no t´ıpicos de lenguajes nativos en la Web. Javascript es muy lento comparado con C++ por lo que habr´ a que modificar el procesamiento para no vernos penalizados por este factor. Esto requiere que mucha funcionalidad t´ıpica de CPU tendr´a que ser realizada en GPU para aprovechar al m´ aximo los recursos que nos brinda WebGL. La Web en general exige ciertos est´ andares y requisitos que en un juego normal no existen. Como por ejemplo la carga de la aplicaci´ on. Es importante minimizar el impacto de las transferencias usando elementos comprimidos (texturas, modelos) y catching. Explotar las posibilidades de HTML5 Gracias a los nuevos elementos de HTML es posible mostrar contenido multimedia en la Web sin requerir de plugins:



< audio > : M´ odulo de audio del juego.



< video > : Texturas din´ amicas, cinem´aticas, etc.



< canvas > : Wrapper 3D y elementos GUI.

El objetivo es usarlos debidamente para que el juego contenga elementos multimedia que lo enriquezcan. Dise˜ no de alto nivel y escalable para que en el futuro poder a˜ nadir m´ as elementos. La idea es implementar el juego de una forma escalable y bien dise˜ nada para reusar el “motor” en cualquier futuro desarrollo o para cambiar cualquier funcionalidad de forma adecuada.

Cap´ıtulo 1. Introducci´ on

6

Asentar los conocimientos de la carrera universitaria de inform´ atica. Este proyecto nos brinda la oportunidad de entrar en todas las ramas de la carrera desde programaci´ on avanzada, ingenier´ıa del Software, visualizaci´on avanzada, Inteligencia Artifical, algoritmia, estructuras de datos, programaci´on consciente de la arquitectura, f´ısica,... Gracias a la ambici´ on del proyecto se va a usar los multiples concocimientos asimilados en la carrera y plasmarlos en este proyecto.

Cap´ıtulo 1. Introducci´ on

1.3.

7

Proyecto Compartido

Este proyecto ha sido desarrollado entre dos personas, M´ıquel de Arcayne y yo mismo, Jaime Crespo. El objetivo de hacerlo entre dos personas ha sido simple, unificar los conocimientos de ambos para llegar a profundizar m´as en la materia. Siendo conscientes de que el proyecto tiene un ´ ambito personal hemos diferenciado el trabajo en partes. Este proyecto es un an´ alisis profundo de todas las etapas del Software. Por lo tanto, la parte de especificaci´ on ha sido completamente com´ un ya que ambos nos dedicamos a decidir c´ omo quer´ıamos que fuera el juego. De ah´ı en adelante nos hemos separado esos requisitos para que cada uno trabajara en cosas diferentes y que cada uno pudiera sacar sus propias conclusiones del trabajo realizado. Las partes realizadas por cada uno est´an claramente definidas en la planificaci´ on del proyecto, figuras: 5.1 y 5.2. Y todos los componentes de dise˜ no e implementaci´on en esta memoria son de mi propia autor´ıa que en uni´on con el trabajo de mi compa˜ nero resulta en el juego final.

Imagen 1.2: Esquema Trabajo Compartido.

Cap´ıtulo 2

WebGL 2.1.

¿Qu´ e es WebGL?

WebGL es una DOM API

1

para crear gr´aficos en el navegador. Al ser una API

ofrece una serie de llamadas a una librer´ıa especializada en renderizado 3D. Esta librer´ıa est´a basada en OpenGL ES 2.0. WebGL trabaja bajo el elemento Canvas de HTML5 por lo tanto es accesible desde cualquier lenguaje compatible con DOM, Javascript b´asicamente. En una r´ apida conclusi´ on, para conocer al lenguaje de acceso a WebgGL habr´a que referirse a Javascript y para explorar la API de WebGL habra que referirse a OpenGL ES 2.0.

Imagen 2.1

1 Una DOM API es una interfaz de programaci´ on para aplicaciones relacionadas con el Document Object Model para acceder y actualizar contenido, estructura y estilo.

8

Cap´ıtulo 2. WebGL

2.1.1.

9

OpengGL ES 2.0

En todo lo relacionado al funcionamiento interno de WebGL tenemos que referenciarnos a OpenGL ES 2.0. La Open Graphics Library (OpenGL) es una librer´ıa usada para visualizar datos 2D y 3D , posee m´ ultiples prop´ositos que soporta aplicaciones para la creaci´ on de contenido digital tales como dise˜ no mec´anico y arquitect´onico, prototipos virtuales, simulaciones, videojuegos, etc,,,

Imagen 2.2

En el mundo de los ordenadores de sobremesa hay 2 APIs principales: DirectX y OpenGL. DirectX es la principal herramienta 3D para los sistemas que usan Microsoft Windows como sistema operativo y es la normalmente usada por la mayor´ıa de juegos de esta plataforma. OpenGL es multi-plataforma usada mayoritariamente en sistemas Linux, Max OS X y Microsoft Windows. OpenGL ES es una versi´ on simplificada de la existente librer´ıa de OpenGL para sistemas m´ as peque˜ nos que contienen muchas m´as restricciones que la mayor´ıa de plataformas. La version ES (Embedded Systems) est´a dirigida a dispositivos con limitada capacidad de proceso y memoria, poca banda ancha de memoria, sensibilidad al gasto de energ´ıa y falta de floating-point Hardware. Ha sido dise˜ nada seg´ un estos criterios:



OpenGL es una librer´ıa muy grande y compleja y se quer´ıa adaptar para dispositivos restringidos de hardware. Para conseguirlo se ha tenido que suprimir toda la redundancia existente en la librer´ıa. Un buen ejemplo es que la geometr´ıa en OpenGL se puede usar en Modo Immediato, Display Lists o Vertex Arrays. Sin embargo en el caso de OpenGL ES s´olo se pueden usar Vertex Arrays.



Eliminar la redundancia es importante pero mantener la compatibilidad con OpenGL tambi´en. En la medida de lo posible se ha intentando que la funcionalidad de OpenGL ES sea un subconjunto de las funcionalidad de OpenGL para la compatabilidad de las aplicaciones.

Cap´ıtulo 2. WebGL 

10

Se han a˜ nadido algunas caracter´ısticas nuevas para controlar el consumo de energ´ıa. Por ejemplo la precisi´ on de los datos en los Shaders. La precisi´on de los datos puede incrementar o disminuir el rendimiento de los Shaders y por lo tanto el consumo energ´etico.



Se han preocupado de ofrecer un m´ınimo de carater´ısticas para la calidad de las im´ agenes. Muchos dispositivos m´oviles tienen pantallas peque˜ nas por lo que requieren que la calidad de los p´ıxeles dibujados sea la mejor posible.

La especificaci´ on de OpenGL ES 2.0 implementa un pipeline gr´afico programable y deriva de la especificaci´ on 2.0 de OpenGL. La derivaci´on especifica que subconjunto de la funcionalidad de la liber´ıa principal corresponde a la ES.

2.1.2.

Pipeline de renderizado de WebGL

´ El pipeline de WebGL es exactamente el mismo que OpenGL ES 2.0. Este implementa un pipeline gr´ afico programable mediante Shading y consiste en dos especificaciones:



OpenGL ES 2.0 API specification



OpenGL ES Shading Language Specification (OpenGL ES SL).

Imagen 2.3: OpenGL ES 2.0 Pipeline Programable

Cap´ıtulo 2. WebGL

11

Vertex Shader El Vertex Shader implementa m´etodos que operan con los v´ertices del Pipeline. Los Inputs del Vertex Shader son:



Attributes - Datos por v´ertice en los Vertex Arrays.



Uniforms - Datos constantes en el Vertex Shader.



Samplers - Tipo espec´ıfico que representa qu´e textura usar en el caso que se use.



Shader Program - C´ odigo fuente o ejecutable que describe las operaciones que ser´ an ejecutadas en cada v´ertice.



Varyings - Datos de salida que pueden ser usados en etapas posteriores del pipeline.

Imagen 2.4: Representac´ıon Gr´afica del Vertex Shader

El objetivo del Vertex Shader es depositar el valor de la posici´on transformada en la variable predefinida gl Position.

Cap´ıtulo 2. WebGL

12

Primitive Assembly Despu´es del procesamiento del Vertex Shader, la siguiente etapa del pipeline es Primitive Assembly. Una primitiva es un objeto geom´etrico que puede ser representado gr´aficamente por OpenGL ES. As´ı que despu´es del Vertex Shader los v´ertices son ensamblados en formas geom´etricas individuales tales como tri´angulos, l´ıneas o puntos-Sprite con sus respectivas posiciones. En esta etapa tambi´en se guarda el estado de si la forma geom´etrica est´ a dentro o no del frustum de visi´on de la pantalla. Si la primitiva esta enteramente fuera del frustum ´este es descartado y no se tratar´a en etapas posteriores. Si la primitiva est´ a parcialmente fuera del frustum tendr´a que ser recortada para ajustarse al frustum. A este proceso de elecci´on de que est´a dentro y no se le llama clipping. Si se activa la opci´ on de culling se ejecutar´a un procesamiento de caras que descartar´a aquellas que no est´en orientadas con la normal hacia el punto de visi´on. Despu´e del clipping y del culling la primitiva est´ a preparada para la siguiente etapa del pipeline, Rasterization. Rasterization Es etapa es el proceso que convierte la primitiva (l´ınea, tri´angulo o point-sprite) en un conjunto 2D de fragmentos que ser´an tratados posteriormente por el Fragment Shader.

Imagen 2.5: Representac´ıon Gr´afica de la Rasterization

Cap´ıtulo 2. WebGL

13

Fragment Shader El Fragment Shader opera sobre los fragmentos generados en la etapa anterior y trabaja con los siguientes inputs:



Varyings - Datos de salida del Vertex Shader que ahora son los de entrada de este Shader.



Uniforms - Datos constantes en el Vertex Shader.



Samplers - Tipo espec´ıfico que representa qu´e textura usar en caso de que se use.



Shader Program - C´ odigo fuente o ejecutable que describe las operaciones que ser´ an ejecutadas en cada fragmento.

Imagen 2.6: Representac´ıon Gr´afica del Fragment Shader

Los principales objetivos del Fragment Shader son descartar el fragmento o generar un color espec´ıfico para ese fragmento en la variable predefinida gl FragColor.

Cap´ıtulo 2. WebGL

14

Per-Fragment Operations Una vez tenemos el Fragmento definido con su color y su posici´on en pantalla (p´ıxel) hay un conjunto de operaciones que a´ un se tienen que ejecutar antes de enviar el Frame Buffer

1

al dispositivo de salida. El conjunto de operaciones, por fragmento, que se

realizan son los siguientes:

Imagen 2.7: Per Fragment Operations



Pixel Ownership Test - Es un test que determina si el p´ıxel actual es propiedad de OpenGL ES. Este test permite que el sistema de ventanas descarte o no el p´ıxel. Por ejemplo, podemos tener una pantalla del Sistema Operativo que tape parte del contexto de OpenGL ES y por lo tanto no se tenga que pintar, ya que no es visible.



Scissor Test - En OpenGL ES se puede definir ´areas donde no hay que pintar, las llamadas Scissors Regions. Este test se encarga de determinar si el fragmento est´ a dentro o fuera para descartarlo.



Stencil y Depth Test - Seg´ un el valor de los buffers de profundidad (z test) y stencil se determina si el fragmento es rechazado o no.



Blending - Esta operaci´ on combina el nuevo p´ıxel generado con el valor ya guardado en el Frame Buffer en la misma posici´on. Hay casos en que por ejemplo una transparencia se puede realizar usando esta t´ecnica y combinar el color del elemento transparente con el fondo.



Dithering - En sistemas que poseen poca resoluci´on de colores esta t´ecinca puede mejorar diche resoluci´ on tramando el color de la imagen espacialmente.

Al final de este proceso, si se han pasado todas las etapas, se escribe en el Frame Buffer el resultado correspondiente de dicho fragmento. Ese resultado es un color, una profundidad y un valor Stencil. 1

El Frame Buffer es un Array bidimensional de p´ıxeles que representa lo que se va a ver en pantalla.

Cap´ıtulo 2. WebGL

15 Por lo tanto WebGL nos ofrece la posibilidad de usar todo el potencial de OpenGL ES 2.0 desde el navegador mediante Javascript. Si nos fijamos en la actual especificaci´on de WebGL: Especificaci´ on WebGL Mayo 2012 Esta especificaci´on contiene todas esas llamadas que somos capaces de hacer desde el entorno DOM a la m´aquina de estados de OpenGL ES 2.0. Tal como se muestra en la Figura 2.8 el desarrollador tendr´a que especificar mediante los Arrays de v´ertices, un Vertex Shader y un Fragment shader lo que quiera mostrar en pantalla. Y el proceso interno de WebGL sigue exactamente el mismo procedimiento que OpenGL ES 2.0 con la salvedad de que las llamadas nativas a la unidad gr´afica no se har´an directamente desde el c´odigo Javascript sino que primero ser´an interpretadas por el propio int´erprete de Javascript de cada navegador y este seg´ un el estado de ese momento proceder´a a la ejecuci´on de estas mismas intentando aproximarse a una aplicaci´ on nativa. Esta peque˜ na diferencia, en c´omo internamente WebGL es procesado por los navegadores, crear´a un peque˜ no descenso del rendimiento que para peque˜ nas aplicaciones gr´aficas no supondr´a ning´ un problema, pero para aplicaciones que vayan al l´ımite, se tendr´a que ir con m´as cuidado para no verse penalizado. Mediante diferentes t´ecnicas y patrones, que se comentar´an m´ as adelante, es posible minimizar este impacto y poder llegar a generar escenas realmente complejas, uno de los objetivos de este proyecto.

Imagen 2.8: WebGL Rendering Pipeline

Cap´ıtulo 2. WebGL

2.1.3.

16

HTML5 Elemento Canvas

WebGL usa el elemento Canvas introducido en el est´andard HTML5 para renderizar. El elemento Canvas se introdujo como herramienta para poder pintar gr´aficos de forma Scriptable, mayoritariamente con Javascript. Aparte tambi´en ha servido para el alojamiento de WebGL. Por ejemplo para crear un elementos Canvas de 640x480 p´ıxeles es tan sencillo como se define a continuaci´ on:

1 2 3

< body onload = " start () " > < canvas id = " glcanvas " width = " 640 " height = " 480 " > Your browser doesn ’ t appear to support the HTML5 < code >& lt ; canvas & gt ; element .

4 5



´ digo Fuente 2.1: Crear un Canvas Co

Una vez tenemos un elemento Canvas dentro del entorno DOM hace falta recoger un contexto WebGL e inicializarlo. Cuando recibimos un contexto WebGL es necesario inicializarlo tal y como se especifica aqu´ı:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

function start () { var canvas = document . get ElementB yId ( " glcanvas " ) ; // Inicializar el Contexto WebGL initWebGL ( canvas ) ; // Solo continuar si Webgl est´ a activo if ( gl ) { // Setear a negro el clear color gl . clearColor (0.0 , 0.0 , 0.0 , 1.0) ; // Activar el test de profundidad gl . enable ( gl . DEPTH_TEST ) ; // Oscurecer partes lejanas gl . depthFunc ( gl . LEQUAL ) ; // Limpiar el buffer color y el depth buffer con clear color . gl . clear ( gl . C O L O R _ BU F F E R _ B I T | gl . D E P T H _ B U F F E R _ B I T ) ; } }

´ digo Fuente 2.2: Preparar el contexto WebGL Co

Cap´ıtulo 2. WebGL

17

A continuaci´ on se muestra el c´odigo necesario para crear un contexto WebGL:

1 2 3 4 5

function initWebGL ( canvas ) { // Inicializar la variable gl a null . gl = null ; try { // Coger el contexto webgl est´ a ndard . Si falla , intentarlo con el experimental .

6 7 8 9 10 11

gl = canvas . getContext ( " webgl " ) || canvas . getContext ( " experimental - webgl " ) ; } catch ( e ) {} // Si no se ha podido crear avisar al usuario . if (! gl ) { alert ( " No se ha podido inicializar WebGL . Posiblemente stu navegador no lo soporte " ) ;

12 13

} }

´ digo Fuente 2.3: Inicializar WebGL Co

Con estas tres tiras de c´ odigo ya tenemos un Canvas WebGL listo para empezar a recibir llamadas.

Cap´ıtulo 2. WebGL

2.2.

18

Navegadores

Los navegadores son los encargados de recoger la especificaci´on de WebGL e implementarla si quieren dar soporte al contexto WebGL. Para publicar que un navegador soporta WebGL es necesario que dicha implementaci´on pase unos test de conformidad: Tests de Conformidad para implementaciones de WebGL

2.2.1.

Historia

WebGL naci´ o como un experimento que comenz´o en Mozilla por Vladimir Vukicevic en 2006. En 2009 el consorcio Khronos Group

1

empez´o el WebGL Working Group

con la participaci´ on inicial de Apple, Google, Mozilla y Opera entre otros. La primera especificaci´ on 1.0 sali´ o en Marzo de 2011.

2.2.2.

Funcionamiento Interno de WebGL

Este apartado est´ a basado en la informaci´on extra´ıda sobre el desarrollo de Chromium 2 . El resto de navegadores que implementan WebGL no adoptan esta misma estructura sobretodo Firefox que no usa WebKit

3

como sistema de renderizado. A´ un habien-

do estas diferencias es importante entender c´omo un navegador tan importante como Google Chrome trabaja internamente para ofrecer WebGL. En aplicaciones intensas bajo WebGL, como el caso de este proyecto, el navegador va a tener un papel muy importante en el rendimiento de la aplicaci´on sobre todo en momentos de mucha carga gr´afica y procesado. Conocer el funcionamiento interno nos ayudar´a a adoptar medidas para adaptar nuestra aplicaci´ on al sistema local.

Imagen 2.9: Chromium Logo

1 El Khronos Group es una asociaci´ on sin a ´nimo de lucro centrada en crear est´ andares abiertos para la reproducci´ on de contenido multimedia. Llevan proyectos tales como OpenGL y OpenGL ES. 2 Chromium es un proyecto de navegador web de c´ odigo abierto, a partir del cual se basa el c´ odigo fuente de Google Chrome. Es de participaci´ on comunitaria bajo el a ´mbito de Google Code. 3 WebKit es una plataforma para aplicaciones como base para el navegador. Uno de los componentes m´ as famoso es el WebCore para dise˜ nar, renderizar y librer´ıa DOM para HTML y SVG.

Cap´ıtulo 2. WebGL

19

Proceso de Renderizado en Chromium Dentro de WebKit existen varios procesos encargados de controlar diferentes aspectos del navegador. Los m´ as importantes son los controladores de CSS, HTML y Javascript. Cuando cualquiera de estos tres controladores quiere renderizar algo en la p´agina enviar´ a los comandos al RPC Buffer

1

que se aloja en un proceso llamado Ren-

derer Process. El navegador se encargar´a de ir recogiendo esos comandos depositados en el Buffer y transfieri´endolos a un proceso propio del navegador llamado GPU Process. Es importante observar como esta estructura es compartida entre todas las ejecuciones del navegador que quieran renderizar algo. El GPU Process se encargar´a de comunicarse con la unidad de proceso gr´afico mediante los drivers del propio Hardware. Pero hay un par de piezas fundamentales en esta comunicaci´ on. Una de ellas es ANGLE

2

que en Windows permite a los usuarios que no

tengan los drivers adecuados de OpenGL ES se traduzcan a DirectX. Tambi´en existe otra traducci´ on llamada SwiftShader para m´aquinas que no tengan drivers de Hardware adecuados se har´ a Rasterization v´ıa Software.

Imagen 2.10: Chromium Rendering Process

1

Se le llama RPC porque las llamadas al motor gr´ afico son as´ıncronas y trabajan bajo el modelo cliente-servidor que m´ as adelante de explicar´ a con detalle y responde a las siglas Remote Procedure Call. 2 ANGLE : Almost Native Graphics Layer Engine. Es una capa de proceso que traduce comandos OpenGL ES 2.0 a DirectX 9

Cap´ıtulo 2. WebGL

20

RPC Buffer Este Buffer se encarga de almacenar todas las llamadas del entorno Web que tienen que ir a la unidad de proceso gr´ afico. Este Buffer tiene un tama˜ no fijo y todos las recursos (texturas, Buffers, comandos, etc) que van a ser subidos a la GPU se depositar´an ah´ı. Si se sobrepasa este Buffer el proceso de renderizado tendr´a que vaciarlo autom´aticamente para liberar recursos y atender a las siguientes peticiones. A este evento de limpieza del RPC Buffer se le llama Sync Flush. Es algo que se tiene que evitar porque para la ejecuci´ on de un Sync Flush hasta que se ejecuta todo lo que ya estaba depositado en ese RPC Buffer con el objetivo de poder limpiarlo. En conclusi´on, hay que evitar llenar el RPC Buffer y que el proceso de renderizado fluya a medida que se ejecuta y no estresar al navegador porque nos penalizar´a con ello. En los apartados de optimizaciones se hablar´ a de c´ omo evitar estos Sync Flush. GPU Process El GPU Process se ejecuta exclusivamente en un SandBox

1

para que el Rende-

rer Process no tenga acceso directo a las llamadas 3D suministradas por el Sistema Operativo. Este proceso trabaja en modo cliente-servidor con la aplicaci´on:

Imagen 2.11: Chromium GPU Process

1

Aislamiento de proceso, mediante el cual, se pueden ejecutar distintos programas con seguridad y de manera separada. A menudo se utiliza para ejecutar c´ odigo nuevo, o software de dudosa confiabilidad, con objeto de evitar la corrupci´ on de datos del sistema en donde estos se ejecutan.

Cap´ıtulo 2. WebGL 

21

Cliente - El Renderer Process es el cliente y en vez de ejecutar directamente las instrucciones en la unidad de proceso gr´afico, las serializa y las deposita en un Buffer de comandos en un trozo de memoria compartido entre ´el y el proceso servidor.



Servidor - El GPU Process trabaja aislado y recoge los datos serializados, los parsea y los ejecuta apropiadamente en la unidad de proceso gr´afico.

Como vemos en el gr´ afico, la Shared Memory es el RPC Buffer donde residen todos los recursos de nuestra aplicaci´on en cada llamada. Como casi todas las llamadas de OpenGL no tienen valores de retorno el cliente y el servidor pueden trabajar as´ıncronamente para mantener un buen rendimiento y no tener que validar ning´ un retorno. En algunos modos de debug o llamadas muy espec´ıficas es necesario controlar retornos para recoger ciertos valores del servidor, para estos casos se usa un mecanismo IPC 1 . Los beneficios de que el GPU Process se ejecute aislado son:



Seguridad - La l´ ogica de renderizado trabaja aislada no compartiendo recursos b´ asicos.



Robusteza - Si el GPU Process se interrumpe o deja de funcionar no interfiere en el resto de procesos y no para la ejecuci´on del navegador.



Uniformidad - Estandarizando OpenGL ES 2.0 como la API de renderizado permite manter un u ´nico c´ odigo para las diferentes distribuciones de Chromium.

Por u ´ltimo vemos como el contexto gr´afico accede directamente a las librerias nativas del Sistema Operativo GL, Direct3D , o Direct3D mediante ANGLE. Drivers Los Drivers de las tarjetas gr´aficas no han sido dise˜ nados con la seguridad en mente y es relativamente f´ acil que en Drivers un poco antiguos haya agujeros de seguridad importantes. Ahora que la GPU va a estar expuesta al mundo Web, los navegadores est´an controlando muy de cerca qu´e tarjetas gr´aficas son aptas para WebGL. En el siguiente link est´ a la lista de tarjetas aceptadas por navegador: http://www.khronos.org/webgl/wiki/BlacklistsAndWhitelists

1 IPC: Inter-process communication es un conjunto de m´etodos para intercambiar datos entre hilos o procesos de ejecuci´ on.

Cap´ıtulo 2. WebGL

2.2.3.

22

Soporte Mayo 2012

Tal y como se muestra en la Figura 2.12 los navegadores principales que soportan WebGL son Firefox, Chrome, Safari y Opera en sus u ´ltimas versiones. Hace falta destacar que Firefox lo soporta completamente pero est´a marcado como parcialmente soportado, ya que para aquellas tarjetas gr´aficas no permitidas no hay soluci´on posible y se niega el acceso. En caso de Chrome han usado Swift Shader para hacer renderizado v´ıa Software.

Imagen 2.12: Soporte de WebGL en los navegegadores Mayo 2012 Informaci´on extraida de CanIUse.com.

En las siguientes im´ agenes muestro porcentualmente qu´e usuarios soportan WebGL seg´ un el Sistema Operativo y a continuaci´on por Navegador en sistemas operativos. Hay que entender que los porcentajes son dependientes de la cantidad de usuarios que usa ese Sistema Operativo, por ejemplo, el 100 % de los usuarios de Windows son alrededor del 80 % del total por eso la influencia de Windows es grande en la habilitaci´on de WebGL. WebGL por Sistema Operativo SI

SI

54.7 % 45.3 % NO (a) Todos los Sistemas Operativos

SI SI

34.7 %

60.3 %

20.7 % 65.3 %

39.7 % NO (b) Windows

79.3 % NO

NO (c) Linux

(d) MacOS

Imagen 2.13: Porcentajes de aceptaci´on de WebGL por Sistema Operativo. Informaci´ on extra´ıda de webglstats.com.

Cap´ıtulo 2. WebGL

23

WebGL por Navegador

NO

NO

0.0 % SI

100.0 %

(a) Internet Explorer

99.0 %

1.0 % SI

(b) Safari

SI

38.7 % SI 90.2 % 61.3 %

9.8 % NO

NO (c) Firefox

(d) Chrome

Imagen 2.14: Porcentajes de aceptaci´on de WebGL por Navegador. Informaci´on extra´ıda de webglstats.com

.

Cap´ıtulo 2. WebGL Tendencia Cuota de uso del Navegador

Imagen 2.15: Tendencia cuota de uso de los navegadores. Informaci´on extraida de StatCounter.com.

Cuota de uso del Sistema Operativo

Imagen 2.16: Cuota de uso de los sistemas operativos. Informaci´on extraida de StatCounter.com.

24

Cap´ıtulo 2. WebGL

25

Viendo estos datos parece que el futuro de WebGL est´a asegurado. Si tenemos en cuenta que el Sistema Operativo que mejor lo soporta es el que m´as se usa (Windows) y lo mismo para el navegador (Chrome) que su uso est´a creciendo cada d´ıa m´as y es cuesti´ on de semanas para que se proclame el m´as usado, se puede decir con cierta seguridad que el soporte de WebGL s´ olo va a seguir creciendo. Hay que remarcar que Firefox tiene menos soporte que Chrome un 38 % vs el 90 % de Chrome ya que Chrome usa una alternativa para los ordenadores con gr´aficas no permitidas, SwiftShader. Safari soporta WebGL pero viene desactivado por defecto por lo tanto su porcentaje es casi nulo. Con el tiempo, todos los navegadores que tengan WebGL tendr´ an el 100 % de soporte, ya que las nuevas gr´aficas vendr´an con drivers m´as seguros y ajustados a las necesidades actuales. As´ı que el futuro de esta tecnolog´ıa est´a asegurado en cuanto a soporte. Sin embargo hace falta analizar qu´e otras tecncolog´ıas existentes hay en el mundo Web. El u ´nico punto negro es Internet Explorer.

2.2.4.

Internet Explorer

Microsoft est´ a en decadencia en cuanto a tecnolog´ıas Web y cuota de navegador pero a´ un es el navegador m´ as usado por lo tanto es un lastre que los desarrolladores web no puedan disfrutar de un soporte total. En un Blog de Microsoft se justifica porqu´e no se implementa WebGL en sus navegadores. En resumen dec´ıan: ((Creemos que WebGL probablemente se convertir´ a en una fuente continua de vulnerabilidades dif´ıciles de solucionar. En su forma actual, WebGL no es una tecnolog´ıa que Microsoft puede admitir desde una perspectiva de seguridad.)) En el siguiente apartado nos centraremos en la seguridad. Conociendo la filosof´ıa de Microsoft en cuanto a tecnolog´ıas abiertas podemos decir que WebGL compite directamente con la API DirectX de Microsoft, que es m´as popular en Windows que OpenGL, y no hay ning´ un inter´es por parte de Microsoft de popularizar esta tecnolog´ıa. El futuro de WebGL en Internet Explorer es bastante incierto. Microsoft lo rechaza por temas de seguridad pero se sospecha que tiene m´as inter´es en proporcionar su propio framework 3D basado en DirectX. Dicho esto, Google, Mozilla, Opera y Apple admiten WebGL y tienen el soporte de los dos grandes empresas de Hardware gr´afico: Nvidia y AMD. As´ı que WebGL tendr´a un gran apoyo pero no total y viendo la tendencia de uso, cada vez m´as.

Cap´ıtulo 2. WebGL

2.2.5.

26

Seguridad

WebGL es una tecnolog´ıa muy nueva y accede profundamente al Software y al Hardware del ordenador. Accede a los controladores de la tarjeta de v´ıdeo y ´estos no est´an implementados con la seguridad en mente. Se han detectado vulnerabilidades pero no tanto por parte de WebGL sino m´as por parte de los controladores que acceden a los recursos hardware, problema que tendr´a Microsoft si alg´ un d´ıa se dedica a hacer algo similar. As´ı fue de contundente la respuesta de Mozilla al art´ıculo de Microsoft. Les invitaban a ayudar y enriquecer la Web en lugar de rechazar y privar. WebGL no es un plugin, como Flash o ActiveX, sino un built-in

1

del navegador y no

proporciona el acceso nativo extremo que por ejemplo tiene ActiveX, no se puede escribir en disco, no se puede acceder a la memoria principal y no se puede ejectuar c´odigo CPU fuera del SandBox de Javascript. Sus problemas de seguridad est´an relacionados s´olo con el Hardware gr´ afico. ¿De qu´ e vulnerabilidades estamos hablando? En Mayo de 2011, dos meses despu´es de la primera versi´on de WebGL, la firma Context Information Security public´o dos vulnerabilidades presentes en Google Chrome y en Mozilla Firefox. Hubo un gran revuelo ya que la especificaci´on no ten´ıa ni 3 meses. La dos vulnerabilidades de las que estamos hablando son:



Robo de Im´ agenes Cross-Domain.



Denegaci´ on de servicio.

Robo de Im´ agenes Cross-Domain. Uno de los puntos fundamentales de la seguridad en la especificaci´on DOM son las fronteras de los dominios. Estas fronteras previenen acceder a contenido autenticado y confiado de un dominio a otro. WebGL permiti´o el uso de textura Cross-Domain. Es totalmente aceptable poder cargar una imagen externa, fuera de tu dominio, en tu DOM porque no tienes forma de acceder al contenido y descifrar que est´as mostrando. Pero WebGL mediante un Shader malicioso puede llegar a interpretar esa imagen, convertida en textura y actuar en consecuencia leyendo los p´ıxeles correspondientes.La respuesta de los navegadores fue muy r´ apida y contundente: negar texturas Cross-Domain y activar la pol´ıtica del mismo origen. Esta pol´ıtica es una medida de seguridad para Scripts en la parte de cliente y previene que un documento o Script cargado en un origen pueda cargarse o modificar propiedades de un documento desde un origen diferente. 1

Programa interno, parte original del navegador.

Cap´ıtulo 2. WebGL

27

Denegaci´ on de servicio. Un ataque de denegaci´ on de servicio, tambi´en llamado ataque DoS 1 , es un ataque a un sistema de computadoras o red que causa que un servicio o recurso sea inaccesible a los usuarios leg´ıtimos. Normalmente provoca la p´erdida de la conectividad de la red por el consumo del ancho de banda de la red de la v´ıctima o sobrecarga de los recursos computacionales del sistema de la v´ıctima.

Imagen 2.17: Mecanismo de un ataque DoS en WebGL

El procedimiento de este ataque es el siguiente:



1 - Un usuario visita un sitio donde reside un script WebGL malicioso.



2 - El componente WebGL sube cierta geometr´ıa y cierto c´odigo en forma de Shader a la tarjeta gr´ afica del usuario.



3 - El c´ odigo o la geometr´ıa se aprovecha de Bugs o Exploits

2

en los drivers de la

tarjeta gr´ afica. 

1

4 - El Hardware gr´ afico puede ser atacado causando que todo el sistema se paralice.

DoS: de las siglas en ingl´es Denial of Service Exploit: del ingl´es aprovechar, secuencia de comandos con el fin de causar un error o un fallo en alguna aplicaci´ on, a fin de causar un comportamiento no deseado 2

Cap´ıtulo 2. WebGL

28

La soluci´ on adoptada por los navegadores que sufr´ıan esta vulnerablidad ha sido restringir el acceso a todas aquellas tarjetas gr´aficas con drivers vulnerables (ver p´agina 21 para consular la lista). En ning´ un caso el Exploit puede ofrecer control de la m´aquina o robo de informaci´ on. A d´ıa de hoy, Mayo 2012, no hay ninguna entrada relacionada con WebGL en la National Vulnerability Database que referencie a las versiones actuales de producci´on de los navegadores.

2.2.6.

WebGL en m´ oviles y Tablets

S´ olo hay un navegador que soporte WebGL en SmartPhones y Tablets: OperaMobile. Los navegadores son una aplicaci´on en los m´oviles muy costosa, podr´ıamos decir que la m´as costosa. Todos los m´ oviles quieren ofrecer funcionalidades nuevas que funcionen bien. No tiene sentido que por ejemplo, Google soporte WebGL en el Android Browser si no es capaz de reproducir sus propios ejemplos de WebGL. WebGL tiene un coste grande, no s´ olo por Javascript, sino por el compositor Web. Un coste muy alto que, por ahora, no est´ an preparados para asumir en aplicaciones grandes. Existe el Hardware necesario, WebGL est´a basado en OpenGL ES 2.0 y es la misma librer´ıa que usan los m´ oviles de u ´ltima generaci´on para renderizar 3D tanto en Android como en iOS (iPhone e iPad). Pero tienen que mejorar el tratamiento de WebGL internamente. Tambi´en cabe decir que no le est´an poniendo mucho ´enfasis en solucionar esto porque se cree que hay intereses detr´ as. Tanto Google como Apple no quieren generar una nueva plataforma de videojuegos como WebGL en HTML5 si ya tienen sus propios mercados: Android Market y el App Store. Un claro ejemplo es una implementaci´on que hizo Sony Ericsson para Android 2.3 para el m´ovil Xperia pero no ha recibido ninguna respuesta por parte del equipo de Google. Otro claro ejemplo es que Apple est´a soportando WebGL pero s´ olo para su plataforma de publicidad iAds. En cambio para el resto de aplicaciones ¿Por qu´e no? Hay una cierta hipocres´ıa en las grandes empresas cuando hablan de tecnolog´ıas libres ya que para navegadores de escritorio est´an muy interesados en proporcionar WebGL para luchar contra otras tecnolog´ıas privativas como Adobe Flash o Unity. Pero cuando ellos son los que dominan el mercado, no les interesa promocionarlo. Por eso el u ´nico que ha generado algo interesante, s´olo para sumar m´as adeptos, es Opera Mobile. Cuando uno de los dos mercados se imponga el otro querr´a innovar y esperamos que se abra la lata de WebGL.

Cap´ıtulo 2. WebGL

2.3.

29

State of the art Web 3D

En este cap´ıtulo vamos a analizar el estado de la Web 3D y que aportaciones tecnol´ogicas ofrece WebGL respecto a sus competidores.

2.3.1.

Historia 3D en la web

En la rama de superior del gr´afico 2.18 est´an representadas las tecnolog´ıas abiertas y est´andares que han surgido entorno a la Web. En la rama inferior la tecnolog´ıa principal, OpenGL, a lo largo de la historia. Y c´omo ´estas han confluido en WebGL bajo el dominio de HTML5 y Javascript en la Web.

Imagen 2.18: Historia 3D est´andares abiertos.

Web3D es un t´ermino usado para describir contenido 3D incluido en una p´agina HTML visible desde un navegador Web. Tambi´en el t´ermino Web3D se usa para referirse a la experiencia de inmersi´ on en el espacio 3D. Para entender y poder analizar lo que nos proponemos es importante echar un vistazo atr´as y ver como ha ido evolucionando. La Web3D empez´ o en 1994 cuando en una conferencia de World Wide Web en donde se empez´ o a pensar sobre un lenguaje capaz de describir escenarios e hyperlinks 3D. Es as´ı como apareci´ o la primera versi´on de VRML (Virtual Reality Modeling Language ), un lenguaje de programaci´ on que promet´ıa dar a Internet navegaci´on en 3D. Despu´es de un gran inter´es y desarrollo, las compa˜ n´ıas involucradas, decidieron dar un paso atr´as en el proyecto. Los motivos fueron la poca participaci´on por parte de las compa˜ n´ıas de software y el factor tecnol´ ogico; ya que el entorno 3D requiere de caracter´ısticas hardware inviables para aquella fecha. Y el ancho de banda de los m´odems de la ´epoca no daba suficiente para conseguir una experiencia realmente buena. Se pens´o que el proyecto VRML fue un completo fracaso, pero no fue as´ı. El proyecto no lleg´o a morir sino se perdi´o el inter´es de ir m´ as all´ a y se dej´ o de lado. Hasta que en 2004, gracias a la posibilidad de acceder a

Cap´ıtulo 2. WebGL

30

un Hardware espec´ıfico en aceleraci´on de gr´aficos surgi´o X3D, el nuevo VRML. X3D es un est´ andar XML para representar objetos 3D. Pero Microsoft, el grande del momento, consider´ o que los clientes Web no estar´ıan dispuestos a asumir requirimientos extras para desarrollarlo por eso Internet Explorer nunca implement´o VRML or X3D. Si el mayor navegador del momento no inclu´ıa esa caracter´ıstica no hab´ıa ning´ un inter´es comercial. En paralelo, fuera de los est´andares Web, grupos privados desarrollaban sus propios productos. Intel, Sillicon Graphics, Apple y Sun hacia 1996 dicidieron hacer una versi´ on Java. As´ı que colaboraron en ello. El proyecto fue Java3D y fue acabado en 1997 y la primera release en 1998. Y desde 2003 el desarrollo ha sido intermitente hasta que en 2004 se sac´ o como proyecto de c´odigo abierto. Java 3D nunca ha brillado en el mundo Web. Aparte de necesitar un entorno Java en el navegador a modo de plugin su uso se ha visto muy limitado a peque˜ nos ejemplos por culpas de sus deficiencias. En cuanto a Adobe y Macromedia, antes de que la comprara Adobe, nunca hab´ıan trabajado con renderizado 3D real, sino han jugado con sus reproductores de Flash y Shockwave para dar perspectiva a escenas 2D, simulando 3D. A partir de Shockwave 5 se incluyo una API 3D en Shockwave pero sin nada de ´exito. Es ahora, en la actualidad, cuando han empezado a desarrollar una API 3D real de bajo nivel, Stage3D, nombre en clave, Molehill. En 2005, las compa˜ n´ıas de videojuegos, Sony entre las m´as importantes, estaban interesadas en usar alg´ un formato intermedio entre aplicaciones digitales. De ah´ı surgi´o COLLADA, COLLAborative Design Activity. Gracias a estas dos nuevas tecnolog´ıas, el efecto Web 2.0 y el avance tecnol´ogico han dado paso a nuevas tecnolog´ıas para conseguir un verdadero efecto 3D en el navegador, como Webgl, Unity, O3D o Molehill, entre las m´ as importantes. Actualmente la Web3D est´a un poco inmadura pero las posibilidad de mostrar contenido 3D en tiempo real es totalmente posible. S´olo hay que echar un vistazo a los u ´ltimos ejemplos creados por el grupo Mozilla o los famosos experimentos del Google Chrome, como Google Body.

Cap´ıtulo 2. WebGL

2.3.2.

31

3D surfing, Hype or real?

“3D surfing, Hype or real?” se refiere a si la experiencia 3D en la web es una realidad o es el ”boom“ de una tecnolog´ıa nueva que crea expectaci´on. Cabe decir que aunque Adobe no haya trabajado comercialmente con 3D en la Web de forma nativa, s´ı que ha proporcionado Shockave y Flash, una forma de reproducir contenido multimedia y gr´ afico en la Web. Durante 5 a˜ nos han acaparado casi todo el contenido multimedia en la Web y de forma exitosa. Gracias, en parte a ellos, hay cada vez m´ as necesidad de crear aplicaciones gr´aficas m´as complejas, entre ellas las de 3D. Las causas que anteriormente hicieron que el 3D no inundara la web est´an ahora cubiertas, principalmente las tecnol´ogicas. La m´as importante la aceleraci´on 3D que entraremos en detalle adelante. Si a esto le sumamos que los navegadores han pasado a ser la aplicaci´ on m´ as usada de un ordenador casual y que cada vez m´as, las aplicaciones de escritorio est´ an siendo embedidas por el navegador podemos creer que es el momento de la transici´ on 3D. Si echamos un vistazo atr´as, 2004, cuando el t´ermino Web 2.0 estaba de moda podemos apreciar como ha ido creciendo el n´ umero de aplicaciones Webs que antes eran propiamente de escritorio. Desde clientes de correo (Gmail), gestores de fotos (Flicker), Google Maps, Wikipedia, etc. Las lista es interminable, hasta ChromeOS est´a orientado a la navegaci´ on Web. Por lo tanto, es el momento del 3D en la Web. Entre las aplicaciones 3D, las m´ as jugosas, los videojuegos ya est´ an aqu´ı y este proyecto quiere corroborarlo. Que la Web3D est´e ya aqu´ı no quiere decir que todo Internet se convierta en un mundo virtual 3D, sino que las Webs comunes se podr´an enriquecer de un contenido mucho m´as realista y din´amico. El texto seguir´ a siendo texto, las im´agenes seguir´an siendo im´agenes y nada cambiar´ a radicalmente. Pero por ejemplo, nada impide que sin necesidad de instalaciones previas inicies una Web y est´es decorando tu propia casa en 3D con objetos con un detalle muy real y puntos de luz propios de renders ilustrativos.

Cap´ıtulo 2. WebGL

2.3.3.

32

Competidores

En toda carrera hay competidores y m´as hoy en d´ıa. La tecnolog´ıa que se lleve el pastel podr´ a tener el futuro asegurado hasta aqu´ı unos a˜ nos. Hay mucho inter´es, sobre todo por las compa˜ n´ıas de videojuegos en ver cual es la mejor opci´on de 3D en la Web. Es importante mencionar que muchas de las tecnolog´ıas Web que triunfaron en su momento no eran las de mejor calidad entre sus competidoras. Puede ser que una multinacional la use y se expanda su uso en cadena. Puede ser que su proceso de desarrollo sea mucho m´as r´ apido que otras. Puede ser que su coste sea menor. O puede ser que haya pluraridad de tecnolog´ıas para el mismo prop´osito. Es momento de experimentar y arriesgar. En este proyecto se ha decidido por WebGL.

2.3.3.1.

¿En qu´ e podemos experimentar 3D en la Web?

En esta tabla podemos observar las tecnolog´ıas m´as importantes que lidian con 3D actualmente. Se ha a˜ nadido DirectX y OpenGl, que aunque no est´en basadas en el navegador, son usadas por el resto a modo de Low-level API.

DirectX

OpenGL

Java3D

FlashMolehill

Para Navegador

No

No

S´ı

S´ı

Aceleraci´ on HW

S´ı

S´ı

S´ı

S´ı

-

-

S´ı

S´ı

Excelente

Excelente

Buena

Regular

Plugin Calidad Gr´ afica Adopci´ on 3D Estabilidad

Alta

Alta

Baja

Baja

Estable

Estable

Estable

Estable

Tabla 2.1: Tecnolog´ıas Web 3D - 1

Silverlight

Unity

Para Navegador

S´ı

Aceleraci´ on HW

No

Plugin Calidad Gr´ afica Adopci´ on 3D Estabilidad

Canvas

WebGL

S´ı

S´ı

S´ı

S´ı

No

S´ı

S´ı

S´ı

No

No

Mala

Buena

Mala

Buena

Baja

Buena

Mala

Regular

Estable

Estable

Estable

Estable

Tabla 2.2: Tecnolog´ıas Web 3D - 2

Cap´ıtulo 2. WebGL 2.3.3.2.

33

¿En qu´ e merece la pena arriesgar?

DirectX y OpenGL podemos descartalas porque est´an orientadas directamente a lenguajes de escritorio que tengan acceso directo al sistema mediante low-level API’s que no interfieren al rendimiento. No son API’s orientadas al navegador. Solo han sido mostradas de forma descriptiva para su comparativa.

Imagen 2.19: Web3D logos: Adobe Flash, WebGL, Unity, Silvelight, HTML5 y Java3D

Empezando por Java. Java3D lleva en la Web desde hace m´as de una d´ecada y nunca ha sido explotado para fines comerciales, ni para una inmersi´on en la Web3D. Sufre de agujeros de seguridad, necesidad de un plugin de gran tama˜ no, como la m´aquina virtual de Java; m´ as el plugin Java3D. Es molesto con los cuadros de di´alogos que aparecen cada vez que se quiere ejecutar algo en ´el. A pesar de sus ventajas, no est´a preparado para albergar lo que esperamos de una Web r´apida y din´amica. Flash, el plugin de Adobe, hasta hace poco no soportaba aceleraci´on 3D. Adobe sac´o hace poco, la versi´ on 11.0 con stage3D, o Molehill, una API que usa OpenGL o DirectX para el renderizado. El incoveniente, bajo mi punto de vista, es que no s´olo tienes que integrar un plugin privativo en la Web sino adem´as conocer Action Script t´ıpico y a˜ nadir un framework para la adaptaci´on a 3D. En muchos de los foros de desarrolladores de Flash comentan lo dif´ıcil que es lidiar directamente con la API 3D por eso el uso de un Framework. Son demasiadas cosas que est´an fuera de control de un desarrollador que tiene que conocer el estado de muchos sistemas cambiantes. Es importante comentar que la nueva especificaci´ on de HTML, HTML5, ha intentando absorber toda el modelaje y animaci´ on de formas para evitar que se use Flash en todo a lo que multimedia se refiera. Por lo tanto Flash ir´ a perdiendo peso poco a poco a medida que HTML5 se incorpore m´as y posiblemente Molehill se vea afectada. Pero es s´olo una suposici´on muy discutible, es dif´ıcil prever que puede ocurrir con Flash. Sigue en la cima de contenido multimedia en la Web. Opci´ on Silverlight. Ha sido la respuesta de Microsoft al gran acaparamiento de Flash. Silverlight s´ olo trabaja bien en Windows. No es nada popular porque no integra ninguna funcionalidad nueva a las existentes y no soporta aceleraci´on hardware. Y si le quitamos la portabilidad de plataformas se queda en una opci´on nula. No ha sido desarrollado con el objetivo de albergar contenido 3D pero tampoco puede. Se espera un respuesta de Microsoft viendo el gran avance del resto pero a´ un no se sabe nada.

Cap´ıtulo 2. WebGL

34

HTML5 introduce grandes avances y opciones, una de ellas es el elemento Canvas. El elemento Canvas de HTML5 permite generar gr´aficos dentro de ´el. Al no tener aceleraci´on 3D se considera un elemento 2D i/o 2.5D ya que la generaci´on de elementos 3D din´amicamente sin ayuda de hardware espec´ıfico es inviable. Por lo tanto es una gran opci´on para desarrollos orientados al 2D. Para 3D real, no es una opci´on. Considero que faltan los dos m´as importantes, Unity y WebGL (no olvidemos Molehill o Stage3D). Unity es una herramienta para la creaci´on de juegos 3D, visualizaciones arquitect´ onicas o animaciones 3D. Soporta aceleraci´on completa de Hardware y requiere de un plugin para su reproducci´on. Su entorno de reproducci´on funciona en Windows, Max OS X, Xbox360, PlayStation3D, Wii, iPad, Iphone y Android. No en Linux. Es impresionante la portatibilidad que han conseguido. Su forma de desarrollo es parecida a la de Flash, orientada a alto nivel y dise˜ no, m´as que a programaci´on de bajo nivel. Esto puede ser una ventaja y una desventaja a la vez. Permite que todos los desarrolladores de Flash puedan trabajar en Unity pero desconcierta a los reales programadores de videojuegos, acostumbrados a lidiar con cuestiones de bajo nivel. Unity es una versi´ on estable y tiene ya sus adeptos en producci´on. Varios juegos ya han sido distribuidos a varias plataformas con ´exito sobre todo en los m´oviles. En el navegador a´ un no han conseguido asentarse como opci´on real pero lo ser´a por su opci´on a exportar en Flash. Es un producto privativo, desarrollo de pago y fuera de los est´andares HTML5. A d´ıa de hoy es la opcion m´ as segura si tu perfil es el correcto para el desarrollo de videojuegos tal y como Unity propone. WebGL ha sido la respuesta de los navegadores libres, que viendo que han hecho un gran esfuerzo por embellecer HTML5 que no soporta aceleraci´on 3D directa, no quieran ver su navegador tapado por un plugin privativo por lo que han decidido implementar una API directa a OpenGL desde el entorno DOM. Su rendimiento es muy bueno. Se han visto ejemplos realmente complejos corriendo a 60FPS, la frecuencia ideal de renderizado. Su portabilidad tambi´en es muy buena est´a atada a HTML5 pero no es una especificaci´ on de ´el. Pero hay dos grandes desventajas. El mayor navegador del mundo no lo soporta, Internet Explorer. Pero la suma en porcentajes del resto supera la mayor´ıa de IE, as´ı que hay cierta esperanza por la comunidad de que lo acaben implantando. Y la otra desventaja es que el desarrollo de aplicaciones 3D en el entorno DOM y de bajo nivel como es WebGL puede llegar a enloquecer. DOM no es el entorno m´as correcto ni Javascript el lenguaje ideal para este tipo de desarrollos. Puede asustar y por eso a´ un no se han visto grandes aplicaciones por parte de grandes empresas 3D, pero s´ı por parte de particulares y Start-Ups independientes. Pero la viabilidad est´a ah´ı, con una mejora del entorno y un peque˜ no empuj´on es un gran candidato a ser la plataforma correcta para el desarrollo de 3D en la Web. Y este proyecto quiere ofrecer esas soluciones, entre otras, a estos problemas.

Cap´ıtulo 3

El Juego 3.1.

Visi´ on Global

El juego es un Resistence 1 Shoot’em up 2 en perspectiva Top-Down 3 en 3D. El objetivo principal del juego es eliminar el m´aximo n´ umero de enemigos sin que te eliminen a t´ı. Se tiene que conseguir una sensaci´on de sentirse acosado y confundido por la escena y por la multitud de enemigos intentando generar tensi´on y r´apidez de movimiento al usuario. Se proporcionara un escenario cerrado para generar una sensaci´on de acorralamiento y fustraci´ on, enemigos de aspecto monstruoso, tales como zombies. Se jugar´a con la iluminaci´ on para generar oscuridad y cierto terror en la escena por no saber controlar la situaci´ on. El juego se tiene que ir acelerando y crear m´as tensi´on a medida que el tiempo pasa ya que es una cuenta atr´as.

Imagen 3.1: Simulaci´ on de un borrador del jugador principal del juego DOOM3. 1

Din´ amica de juegos en el que el objetivo es sobrevivir a ataques. Shoot’em up: g´enero de videojuegos en los que que el jugador controla un personaje y dispara contra hordas de enemigos que van apareciendo en pantalla 3 Perspectiva de visi´ on del jugador principal en tercera persona y desde arriba hacia abajo, viendo como interact´ ua el jugador principal con la escena y los enemigos 2

35

Cap´ıtulo 3. El Juego

3.2.

36

Esquema General

Viendo la definici´ on podemos generar un esquema principal de nuestra aplicaci´ on que nos de una idea de los componentes m´as importantes involucrados. La aplicaci´ on tiene que tener tres sistemas b´asicos: aplicaci´on, l´ogica y vista. La aplicaci´on va a ser la base donde todo se va a desarrollar. La l´ogica va a ser un conjunto de subsistemas encargados de hacer que la aplicaci´on se comporte como debe. Si quieres hacer un juego con la definici´ on anterior va a hacer falta que la l´ogica tenga Inteligencia Artificial, f´ısicas, eventos tales como disparos y ataques. Y la vista va a ser la encargada de mostrar todo lo necesario al usuario mediante modelos, audio y un renderizado m´as un tratamiento de los eventos de usuario para manejar la posici´on del jugador principal.

Imagen 3.2: Esquema General del Juego.

Cap´ıtulo 3. El Juego

3.3.

Capturas de Pantalla

Imagen 3.3: Captura de Pantalla del comienzo del juego.

Imagen 3.4: Captura de Pantalla del juego en funcionamiento.

37

Cap´ıtulo 3. El Juego

Imagen 3.5: Captura de Pantalla del juego mostrando las Bounding Boxes.

Imagen 3.6: Captura de Pantalla del final del juego.

38

Cap´ıtulo 3. El Juego

3.4.

39

An´ alisis

El objetivo del an´ alisis de cualquier Software es entender el problema. Normalmente esta etapa es de las m´ as importantes porque es la primera definici´on de lo qu´e se quiere hacer y a partir de aqu´ı desenvocar´a el resto. No entender el problema puede generar dise˜ nos err´oneos y p´erdida de tiempo. Como primera parte del an´ alisis tenemos que saber las necesidades y condiciones a satisfacer por el Software a realizar mediante una descripci´on completa del comportamiento del sistema. Separaremos los requisitos en funcionales y no funcionales.

3.4.1.

Requisitos Funcionales

Los requisitos funcionales expresan una capacidad de acci´on, funcionalidad y declaraci´on en forma verbal de qu´e tiene que hacer el sistema.



El juego ha de contener una personaje principal que responda a las acciones del usuario: •

Moverse



Apuntar



Disparar



Las acciones del usario deben estar representadas con sus respectivas animaciones.



Las acciones del usario deben estar representadas con sus respectivos sonidos.



El juego tiene que estar desarrollado en una escena realista y cerrada: •

F´ısica: Los elementos de la escena han de comportarse como en la realidad.



Luces: Los elementos de la escena tienen que estar preparados para ser iluminados seg´ un la naturaleza de su composici´on.



El juego tiene que ser capaz de generar enemigos de una forma l´ogica.



Estos enemigos tienen que tener un comportamiento inteligente de: •

Movimiento



Ataque

Cap´ıtulo 3. El Juego 

40

El juego tiene que controlar las acciones de ataque, restando la vida tanto del jugador principal como la de los enemigos.



Si el jugador principal se queda sin vida, se acaba el juego.



El juego durar´ a un tiempo fijo y el objetivo es eliminar tantos enemigos como se pueda.



El juego tiene que ser r´ apido y din´amico de gran acci´on y tensi´on.



Minimizar el acceso al juego simplificando los pasos.



Proporcionar una pantalla de carga que mantenga informado al usuario.



Proporcionar una pantalla que informe al usuario que est´a preparado para empezar.



Proporcionar una pantalla de final de juego que explique los resultados.



El juego ha de guardar las estad´ısticas de las acciones del jugador para que cuando acabe se muestre la puntuaci´on total.



El juego ha de proporcionar una inmersi´on dentro de la escena mediante luces y sonidos.



El juego se tiene que adaptar a todos los posibles tama˜ nos de pantalla.

3.4.2.

Requisitos No Funcionales

Los requisitos no funcionales son tales como los recursos del sistema o restricciones generales del sistema no de su comportamiento.



Estabilidad: Al ser un juego en 3D se ha de garantizar que el cliente es capaz de jugar y que la aplicaci´ on es robusta.



Portabilidad: El juego tiene que garantizar que puede ser ejecutado en aquellas plataformas que soporten WebGL, siempre que el usuario contenga los m´ınimos requisitos de Hardware conformes con el juego.



Compatibilidad: No usar extensiones, ni librer´ıas, ni t´ecnicas fuera de las especificaciones b´ asicas de WebGL ni HTML5.



Usabilidad: Tienes que ser sencillo de usar sin generar confusi´on contextual.



Coste: El juego ha desarrollarse bajo licencias sin coste y con tecnolog´ıas abiertas y est´ andares.

Cap´ıtulo 3. El Juego

3.4.3.

41

Diagrama Entidad - Relaci´ on

El objetivo del siguiente diagrama es identificar las principales entidades (rect´angulos verdes), como se asocian entre ellas (rombos azules) y que propiedades principales tienen (globos rojos). Nos da una idea de qu´e se compone el juego. En este caso, nos muestra la sencillez del juego y que las entidades que lo representan no son complejas. No se quiere ni especificar agregaciones, ni herencias ni atributos propios de dise˜ no, s´olo una proyecci´ on inicial de los datos de la aplicaci´on extra´ıdo de los requisitos funcionales.

Imagen 3.7: Diagrama Entidad-Relaci´on.

Se ha querido simplificar mucho el juego para centrarnos b´asicamente en los aspectos m´as importates como Gameplay, detalle gr´afico y rendimiento, propias de etapas m´ as posteriores, con el objetivo de extraer lo m´aximo de esta tecnolog´ıa, no de ofrecer un juego complejo y fuera del ´ ambito del proyecto. Todo el resto de requisitos funcionales que no se plasman aqu´ı van a ser que ser atendidos en la etapa de dise˜ no.

Cap´ıtulo 3. El Juego

3.5.

42

Especificaci´ on

En esta etapa del desarrollo nos centraremos en describir qu´e tiene que hacer el sistema. Desde un inicio se sab´ıa que los requerimientos funcionales ser´ıan cambiantes debido al desconocimiento de la tecnolog´ıa de implementaci´on y a la flexibilidad de ellos mismos por eso se ha preferido trabajar bajo un modelo de desarrollo de Software m´ as informal, m´ as ´ agil e iterativo que facilite el cambio de un requirimiento sin grandes impactos.

3.5.1.

Metodolog´ıa de Desarrollo

Se usar´ an historias para definir las funciones del sistema. A diferencia de los casos de uso, las historias intentan facilitar el manejo de estos requerimientos. La idea de las historias es capturar el “qui´en”, “qu´e” y “por qu´e” de una forma concisa, casi en una l´ınea. Es una forma de manejar requerimientos sin mucha formalidad y documentos que relajen el trabajo de mantenerlos. Su objetivo principal es responder r´apido a cambios de requisitos gracias a que:



Las historias son peque˜ nas, pueden ser implementadas en d´ıas o semanas.



Permiten la discusi´ on del requerimiento durante su proceso.



Tienen poco mantenimiento.



Permiten dividir el proyecto en peque˜ nos incrementos.



Encajan con Software donde los requerimientos son vol´atiles.



Encajan con Software que se define mediante iteraciones.



Son f´ aciles de asignar a roles espec´ıficos.



Son f´ aciles de testear.

Muchas metodolog´ıas de moda en desarrollo de Software ´agil como Extreme Programming o Scrum trabajan con este modelo de especificaci´on. En el caso de este proyecto, no se ten´ıa 100 % especificado como se quer´ıa que fuera el producto final. M´as bien se dejaron cosas sin especificar que ser´ıan adaptadas seg´ un las capacidades de implementaci´on. Por eso no pod´ıamos referirnos a casos de uso completos. El proceso de desarrollo ha seguido un modelo iterativo incremental que responda a la naturaleza del proyecto y de las historias. Un proceso iterativo incremental es un

Cap´ıtulo 3. El Juego

43

modelo de desarrollo de Software que permite desarrollar versiones cada vez m´as complejas y completas, hasta llegar al objetivo final. Es decir, a medida que cada incremento definido llega a su etapa final se incorporan nuevas historias que fueron analizadas como necesarias.

Imagen 3.8: Representaci´on del Modelo Iterativo Incremental.

Tal y como muestra la figura anterior, este proceso permite que constantemente se est´e redifiniendo, desarrollando y validando los nuevos objetivos. Cada incremento de Software vendr´ a dado por las historias especificadas. Estas nuevas historias generar´an versiones que incluir´ an las historias anteriores produciendo nuevas versiones m´ as completas.

Cap´ıtulo 3. El Juego

3.5.2.

44

Historias

Normalmente las historias vienen dadas por los clientes de la aplicaci´on o por el equipo de negocio. En el caso de este proyecto los propios desarrolladores han sido los generadores de estas historias por eso est´an m´as cerca del lado propiamente inform´atico que de negocio, agilizando a´ un m´as el proceso. Se intenta definir un rol , un algo y un beneficio. Interfaz de Usuario

1. El jugador quiere ver una portada del juego para poder sentirse introducido. 2. El jugador quiere ver c´ omo carga el juego para no creer que el juego ha parado. 3. El jugador necesita conocer las instrucciones para disfrutar del juego. 4. El juego tiene que escalarse al tama˜ no de la pantalla para visualizar todo el contenido correctamente. 5. El juego tiene que poderse visualizar en pantalla completa para mejorar la inmersi´ on del jugador. 6. El jugador tiene que conocer la vida del jugador principal durante el juego para actuar en consecuencia y sobrevivir. 7. El jugador tiene que conocer cuanto tiempo queda de juego para actuar en consecuencia. 8. El juego tiene que presentar una pantalla de fin cuando el juego acaba para informar al usuario de que ha acabado, mostrando la puntuaci´on.

C´ amara 1. El jugador tiene que ver al personaje principal en tercera persona. 2. El jugador tiene que ser capaz de ver la escena para actuar en consecuencia y adelantarse a la acci´ on. Escena 1. La escena tiene que tener una iluminaci´on din´amica. 2. Cada elemento de la escena tiene que tener definido un material que representa la contribuci´ on de luz ambiente, difusa y especular para generar realismo.

Cap´ıtulo 3. El Juego

45

3. La escena tiene que estar decorada seg´ un el contexto del juego. Renderizado 1. El renderizado ha de ser constante y al mayor ritmo posible tendiendo a los 60 Frames por segundo para proporcionar suavidad entre la transici´on de los frames. 2. Se tiene que proporcionar un efecto de disparo que muestre la direcci´on de disparo para que el usuario pueda apuntar con eficacia. 3. Se tiene que proporcionar un efecto de muerte de los enemigos para que el usuario se de cuenta que ese enemigo est´a muerto. Personajes 1. El personaje principal tiene que disparar y moverse para representar las acciones del usuario. 2. Los enemigos tienen que moverse y atacar seg´ un un patr´on l´ogico. 3. Todos los personajes tienen que poseer atributos de ataque y vida. L´ ogica 1. La l´ ogica de los enemigos es acercarse al personaje principal y cuando est´en cerca atacar. 2. La l´ ogica del juego es durante un tiempo finito ir creando m´as enemigos en posiciones aleatoria. 3. Si el tiempo acaba o el jugador principal muere, el juego acaba. 4. El juego ha de capturar los eventos del navegador para que el jugador pueda interactuar. F´ısica 1. Todos los elementos de la escena tienen que tener un comportamiento f´ısico real. 2. El juego tiene que intersectar el disparo con la escena y actuar en consecuencia. Si el disparo da a un enemigo, el enemigo tendr´a que restarse un porcentaje de vida seg´ un los atributos del persnaje principal. Lo mismo para el jugador principal.

Cap´ıtulo 3. El Juego

46

Audio 1. El juego tiene que tener una melod´ıa de fondo que ambiente la escena. 2. Se deben proporcionar sonidos para todas las acciones del juego para que el usuario note la respuesta del sistema a sus acciones.

Cap´ıtulo 3. El Juego

3.6.

47

Arquitectura

El objetivo de la arquitectura es dar soluciones tecnol´ogicas a los problemas previamente expuestos desde una perspectiva de dise˜ no. Es la etapa previa al propio desarrollo.

3.6.1.

Control de Versiones

Se ha usado el control de versiones Subversion que proporciona las soluciones necesarias para los requisitos de este proyecto. Se usar´a la t´ıpica configuraci´on de Trunk,Branches y Tags ya que se ajusta perfectamente a la metodolg´ıa de desarrollo ya explicada.

Imagen 3.9: Control de Versiones.

Como se muestra en la figura anterior el Trunk ser´a el cuerpo de proyecto donde se residir´ a las etapas ya completas. Cuando se incorpora una nueva historia al desarrollo principal, se crear´ a un Branch nuevo donde se aplicar´an las nuevas funcionalidades y hasta que ´esta no este acabada y testeada no se incorporar´a al Trunk. El proceso de incoporporar una rama al Trunk se llama Merge. Durante el desarrollo principal del Trunk hay momentos en los que se quiere preservar ciertas versiones. Las razones pueden ser varias, como una release, una beta o cualquier prop´osito que lo requiera. La forma de preservar una copia es haciendo un Tag. En el Trunk no se ha desarrollado nueva funcionaldiad, s´ olo arreglos o Fixes para mejorar la integraci´on de historias. En conclusi´ on, aparte de ofrecernos un mecanismo de almacenamiento, posibilidad de realizar cambios din´ amicos y un registro hist´orico nos proporciona un soporte f´ısico a las historias.

Cap´ıtulo 3. El Juego

3.6.2.

48

Dise˜ no

Se ha decantado por un modelo en componentes y orientado a objetos. Cada componente ser´ a responsable de unas tareas concretas. La idea es que cada componente sea una caja cerrada de l´ ogica con una fachada de acceso al resto. De esta forma cada componente es independiente, escalable y substituible independientemente del resto.

Imagen 3.10: Modelo de Dise˜ no.

Como se muestra en la figura el sistema est´a dividio en componentes que internamente est´ an construdios por controladores y entidades. Estos componentes exponen una interfaz de acceso para que el sistema y otros componentes tengan acceso a la funcionalidad que ´este implementa. Dentro del sistema hay porciones de l´ogica que no han podido ser encapsuladas en componentes y que residen en controladores y entidades ligadas directamente al sistema.

3.6.3.

Patrones de Dise˜ no

El sistema va a estar partido en componentes o m´odulos, por lo tanto claramente hay un patr´ on de modularidad incluido en el dise˜ no. El patr´ on m´ odulo es una variante del patr´ on singleton con un prop´osito m´as espec´ıfico para organizar nuestro c´odigo en diferente componentes. Dentro de los componentes se le asignar´a la responsabilidad de cierta tarea a las clases que contengan la informaci´on necesaria. Conocido como patr´ on experto. Dentro

Cap´ıtulo 3. El Juego

49

de un componente pueden haber muchos expertos que colaborar´an para ofrecer, en conjunto, la funcionalidad del componente mediante una interfaz. Dentro de los componentes podemos indentificar dos tipos de objetos: los controladores y las entidades. Ambos tipos de clases son expertos en sus respectivas responsabilidades pero la diferencia es que los controladores tienen la responsabilidad de crear las entidades, patr´ on creador, de eliminarlas y de gestionarlas. Las entidades son contenedores de informaci´ on. La entidad no tiene responsabilidad para ejecutar nada fuera de su ´ ambito. El que las maneja es el controlador. Lo que se quiere de esta forma es separar la l´ ogica de creaci´ on, destrucci´on y manejo de la la l´ogica de datos propios de una clase. De esta forma se consigue que dentro de los componentes haya una alta cohesi´ on entre sus miembros pero muy encapsulada para que visto desde fuera se trate como un subsistema externo, como si trabajaramos con librer´ıas. Los controladores ser´an clases Singleton porque no tiene sentido instanciar dos clases con las mismas responsabilidades y estado u ´nico. Se han usado Factor´ıas para aquellos componentes que su objetivo es crear ciertas clases, prepararlas y ofrecerlas al sistema.

3.6.4.

Componentes

En este apartado se van a definir los dise˜ nos de los componentes m´as importantes del juego. No van a ser definidos con todos los atributos reales de la aplicaci´on, s´olo con aquellos que hacen posible realizar las historias.

3.6.4.1.

Render

Este componente es el encargado de cargar los modelos del juego y de renderizarlos. Como se aprecia en el diagrama 3.11 hay un responsable de crear todo el componente que se llama Game Renderer. Game Renderer crear´a tantos controladores como sea necesario. Cada controlador ser´a encargado de un tipo espec´ıfico de Modelo. Ha sido necesario separarlo en diferentes controladores porque la l´ogica de carga de un modelo difere mucho de uno a otro. Cada controlador crear´a, guardar´a y eliminar´a las instancias de Modelos de las que es responsable. Cada controlador tendr´a un programa de pintado espec´ıfico para sus modelos. Por otro lado est´a el ´arbol de entidades. Model es la super clase que contiene todos los atributos comunes de Renderizado. Cada Modelo require de un Material para responder a la iluminaci´on de la escena. Y a partir de aqu´ı desciende una jerarqu´ıa de especializaci´ on necesaria para representar las necesidades de cada tipo de Modelo, la potencia de la orientaci´on a objetos, el polimorfismo.

Cap´ıtulo 3. El Juego

50

Imagen 3.11: Dise˜ no Coponente Render UML.

Cap´ıtulo 3. El Juego

51

La gran responsabilidad de este componente es el ejecutar el Render Loop. A continuaci´ on se muestra de forma simple como funcionar´a el Render Loop del juego mediante un diagrama de secuencia:

Imagen 3.12: Diagrama Secuencia Render Loop.

El Game Renderer le dir´ a a cada controlador que renderize, cada controlador usar´a su programa de renderizado, extraer´a la informaci´on de las entidades y pintar´a lo neceseario. Por lo tanto en cada frame se tiene que ejecutar esto para pintar la escena, entre otras cosas.

Cap´ıtulo 3. El Juego 3.6.4.2.

52

Jugador Principal

Este componente es el encargado de atender las acciones del usuario y aplicarlas al jugador principal.

Imagen 3.13: Dise˜ no Componente Jugador Principal UML.

El controlador de este componente estar´a escuchando los eventos de teclado y rat´on. Seg´ un el Game Play y las propiedades del jugador principal se calcular´an unos incrementos de movimiento y una rotaci´ on del jugador principal, esa informaci´on se depositar´a en la entidad Jugador Principal. Este componente deber´a de transferir toda esta informaci´on al componente de la F´ısica para que este aplique las fuerzas correspondientes.

3.6.4.3.

C´ amara

Este componente aloja la c´amara y sus propiedades. La c´amara ha sido definida en tercera persona y Top Down, por lo tanto tendr´a que abastecerse de la posici´ on del jugador para posicionarla acordemente. La c´amara tendr´a que definir la matriz de proyecci´ on seg´ un las medidas del Viewport

1

y la matriz de Vista.

Imagen 3.14: Dise˜ no Componente C´amara UML. 1

En gr´ aficos 3D el Viewport es la rect´ angulo 2D donde se proyecta la escena 3D seg´ un un c´ amara virtual

Cap´ıtulo 3. El Juego

53

En cada frame habr´ a que posicionar la c´amara en la posicion adecuada, seg´ un la posici´ on del jugador principal y una cierta lejan´ıa definida por entidad Camara para que sea en tercera persona. El controlador se encargar´a de dejar las matrices correctas en cada frame.

3.6.4.4.

Animaciones y Modelos MD5

Para los modelos din´ amicos que tienen animaciones se va a usar los modelos MD5, ap´endice B. Este componente nos ofrecer´a la opci´on de aplicar propiedades de elementos animados a nuestra escena.

Imagen 3.15: Dise˜ no Componente Modelos MD5 UML.

Este componente se nutre de una librer´ıa externa, pintada en color naranja, que nos ofrece b´ asicamente dos clases, md5Mesh y md5Anim. La primera representa la malla de v´ertices del modelo MD5 y la segunda la animaci´on. Durante el renderizado habr´a que llamar a este componente para que haga Skinnning 1

1

de los modelos din´amicos. Seg´ un

Es asociar a una malla de v´ertices un esqueleto articulado mediante pesos y posiciones de los v´ertices

Cap´ıtulo 3. El Juego

54

la l´ogica del juego tambi´en habr´a que ir cambiando esas animaciones seg´ un las acciones del juego.

3.6.4.5.

Audio

Este componente se encargar´a de ofrecer una serie de llamadas que desencadenar´ an en sonidos. Se va a usar una librer´ıa externa, Buzz para la gesti´on de los audios en HTML5. Se explicar´ a con m´ as detalle la integraci´on en la parte de implementaci´on.

Imagen 3.16: Dise˜ no Componente Audio UML.

La librer´ıa nos proporciona una clase sound. El controlador seg´ un su fichero de configuraci´ on cargara todos los sonidos del juego y ofrecera una interfaz de acceso y reproducci´ on.

3.6.4.6.

F´ısica

En el componente de la f´ısica se usar´a una librer´ıa externa llamada JigLibJs. Como las t´ıpicas librer´ıas de f´ısica, necesita que le representemos toda la escena mediante sus componentes, cajas, esferas, planos y gravedad. Mediante una llamada iterativa a ”Simulate“ la librer´ıa simular´ a como se comportarian los cuerpos que le hemos representado. La librer´ıa ofrece m´etodos para aplicar fuerzas a los cuerpos que hemos representado.

Cap´ıtulo 3. El Juego

55

Imagen 3.17: Dise˜ no Componente F´ısicas UML.

La idea es que cada modelo del Sistema tenga una Bounding Box, una caja englobante que lo represente, que limite su volumen colisionable. Esta Bounding Box, se le pasar´ a al modulo de f´ısica que gestionar´a su posici´on seg´ un su simulaci´on. La F´ısica devolver´ a las posiciones y rotaciones de esa Bounding Box y el Modelo se acabar´a pintando donde la Bounding Box diga. De esta forma pintaremos los modelos de la escena en las posiciones que la librer´ıa de la f´ısica ha simulado. Si nos fijamos, todos los modelos del Render tienen una Bounding Box asociada para que puedan ser representadas como un volumen. El controlador de f´ısicas ofrecer´a todos los m´etodos necesarios para que el juego pueda simular las f´ısicas. Cada vez que a˜ nadamos un modelo a la escena, este se tendr´a que a˜ nadir a la f´ısica mediante addBB(BB), pasando como par´ ametro su Bounding Box. Durante la vida de este modelo, una referencia a su Bounding Box quedar´a guardada en el controlador de f´ısicas que simular´a su posici´ on hasta que no la eliminemos. De esta manera separamos la l´ogica de renderizado de la l´ogica de f´ısica mediante una clase intermedia, las Bounding Boxes.

Cap´ıtulo 3. El Juego 3.6.4.7.

56

Shaders Factory

Este componente es una factor´ıa de Shaders. Su objetivo es proporcionar programas ya compilados, los Shaders, a la aplicaci´on. Cualquier componente de la aplicaci´on puede pedir un programa de renderizado por nombre. Mediante un fichero de configuraci´on la factor´ıa cargar´ a y compilar´ a todos los shaders definidos y guardar´a los programas para que cuando alguien se los pida est´en preparados para su uso.

Imagen 3.18: Dise˜ no Componente Shaders Factory UML.

Cap´ıtulo 3. El Juego 3.6.4.8.

57

Iluminaci´ on

Este componente se encarga de gestionar la iluminaci´on de la escena mediante las definiciones de las luces y los materiales.

Imagen 3.19: Dise˜ no Componente Luces UML.

Durante el renderizado habr´a que controlar qu´e luces y materiales se usan en la escena para dar los efectos deseados. Mediante un fichero de configuraci´on se definir´ an las caracter´ısticas de las luces.

Cap´ıtulo 3. El Juego

3.6.5.

58

Sistema Global

En la siguiente imagen se muestra c´omo interact´ uan todos los componentes en sus actividades m´ as importantes. Cada actividad est´a especificada por una letra may´ uscula para su posterior explicaci´ on:

Imagen 3.20: Dise˜ no Sistema UML.

Cap´ıtulo 3. El Juego

59

Actividades m´ as importantes del sistema:



A - El componente de Render pedir´a al componente de Modelos MD5 que ejecute el Skinning de los modelos animados antes de pintarlos.



B - La c´ amara deber´ a de pedir la posici´on del jugador principal para posicionarse en la posici´ on correcta.



C - El componente de Render necesita de varios programas de renderizado seg´ un el tipo de modelo, la factor´ıa de Shaders es la encargada de proporcionarlos.



D - El componente del jugador principal deber´a usar el componente de Audio para reproducir aquellas acciones que necesiten una representaci´on sonora.



E - El componente de Render deber´a de encargarse de a˜ nadir los modelos y sus Bounding Boxes a la f´ısica para despu´es leer las posiciones simuladas y pintarlas en sus posiciones correctas.



F - Hay muchos m´etodos comunes entre todos los componentes, para por ejemplo trabajar con la m´ aquina de estados de WebGL, o por ejemplo para cargar ficheros. Todos estos m´etodos de ´ambito global se han agrupado en una clase com´ un, llamada Utils.



G - Recursos en forma de imagen *.png, *,bmp, *.jpg



H - C´ odigo fuente de los Sshaders, archivos *.c



I - Recursos de los modelos MD5. Modelos: *.md5Mesh , Animaciones: *.md5Anim



J - Recursos de los modelos Wavefront : *.json



K - El jugador principal activar´a ciertas animaciones seg´ un las acciones del usuario.

Cap´ıtulo 3. El Juego

3.6.6.

60

Diagramas de Secuencia de un Frame

En esta diagrama de secuencia se especifica las actividades entre componentes m´ as importantes durante la ejecuci´ on de un frame.

Imagen 3.21: Diagrama de Secuencia de un frame.

Cap´ıtulo 3. El Juego

3.6.7.

61

Ficheros de Configuraci´ on

Todos los componentes que cargan recursos externos o necesitan de una configuraci´on requieren de un fichero que contenga esa informaci´on. Tales como propiedades, rutas, valores, etc. De esta forma es posible cambiar el comportamiento de la aplicaci´ on sin tener que retocar el c´ odigo fuente de la aplicaci´on. Tambi´en de la puerta abierta a que se puedan crear herramientas o Tools que mediante una aplicaci´ on que modifique ese fichero de configuraci´on y la aplicaci´on s´olo tenga que leerla y cargar los elementos necesarios. As´ı podemos separar la l´ogica de dominio de la l´ ogica de datos y configuraci´on.

Cap´ıtulo 3. El Juego

3.7.

62

Implementaci´ on

En esta secci´ on se va a proporcionar la soluci´on extrictamente tecnol´ogica al dise˜ no proporcionado previamente. Antes de hablar como se ha implementado el dise˜ no, hace falta analizar Javascript para entender cual es el prop´osito de este lenguaje y c´omo hay que adaptarse correctamente desde un principio para no cometer errores.

3.7.1.

Javascript

Los desarrolladores, en general, tienen una mala impresi´on del lenguaje de Javascript asociado principalmente a la programaci´on en el navegador que tradicionalmente ha sido dif´ıcil por culpa de la integraci´ on con DOM o con Internet Explorer 6. El lenguaje se ve penalizado por la opini´ on p´ ublica cuando no deber´ıa porque es un gran lenguaje pero incomprendido, ya que acepta desde programadores que no saben lo que hacen, hasta grandes desarrolladores con habilidad para programar. Javascript tiene buenas y malas cosas, as´ı que habr´a que intentar programar s´olo usando las buenas partes. Empezamos por ver que influencias de lenguajes tiene el origen de Javascript:



Self - herencia prototypal y objetos din´amicos.



Scheme - lambda (funciones an´onimas o Closures) y flojo prototipaje.



Java - Sintaxis.



Pearl - expresiones regulares.

3.7.1.1.

Partes Malas

Ahora hablemos de las partes malas de Javascript con las que tendremos que lidiar para no cometer errores al implementar el dise˜ no:



Variables Globales - Uno de los grandes problemas que hay en Javascript es que al no haber una unidad de lincaje para los ficheros fuente todo el asemblado se comete bajo un mismo namespace global donde las variables pueden colisionar.



No namespaces - Afectan a la modularidad del c´odigo.



No hay control de visibilidad en los objetos.

Cap´ıtulo 3. El Juego 

63

Operador “+” - Este operador sirve para sumar enteros y concatenar strings. Habiendo un protipaje flojo no hay diferencia entre los strings y los n´ umeros as´ı que puede generar resultados inesperados.



Typeof - Es una macro del propio lenguaje que nos dice de que tipo es la variable pero hay ciertos tipos incromprensibles como: typeof Object == Object , typeof Array == Object y typeof null == Object.



Demasiados valores vac´ıos - Javascript proporciona muchos Bottom Values que parecen ser lo mismo, pero tiene peque˜ nas diferencias que pueden generar confusi´on: false, null, undefined y NaN.

3.7.1.2.

Partes Buenas

Herencia Hay dos formas de representar la herencia en los lenguajes actuales, una es la Cl´asica, la que todos conocemos y la otra es la Prototypal o Herencia Diferencial, que es casi u ´nicamente usada por Javascript. La Herencia Prototypal o Diferencial es tan potente que puedes usarla como si fuera cl´asica pero no al rev´es. No hay clases, s´olo objetos que heredan de otros objetos mediante links que marcan las diferencias. En la herencia cl´asica se define un objeto mediante la declaraci´ on de una clase y se marca de que otra clase hereda. Cuando creas una instancia se crea todo un cojunto de instancias seg´ un la cadena de herencia y todas son iguales. Por lo tanto la clase nos ofrece el comportamiento, la estructura y la instancia de los datos. En Herencia Prototypal, todos los objetos del mismo tipo comparten un objeto llamado Prototype. Y todo lo que est´e declarado en Prototype de un objeto lo heredar´ an el resto de objetos que contengan el mismo tipo de prototype para as´ı compartir el comportamiento. No existe el concepto clase del cual t´ u heredas, sino que coges directamente el comportamiento del objeto que quieras.

Cap´ıtulo 3. El Juego

64

Funciones Las funciones en Javascript son objetos de primera clase1 . La u ´nica forma que hay de crear un scope en Javascript es creando una funci´on, ya que este lenguaje usa function scope y no block scope como Java o C++. Un ejemplo claro ser´ıa ´este:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

// Function Scope function getCounter () { var i = 0; return function () { console . log (++ i ) ; } } // Crea un scope al invocar la funci´ o n con i = 0 y // se guarda en la variable counter , el return de la funci´ o n getCounter , // que es otra funci´ o n que imprime i pre incrementada por consola var counter = getCounter () ; // Invocamos counter ya que esta guardaba una funci´ on . // Se recupera el scope , se incrementa i , resultado : 1 counter () ; // Si la volvemos a llamar , recuperamos el scope // donde fue creada la funcion del return , recupera i ,1 , // y se incrementa en 1 , igual a 2. counter () ;

´ digo Fuente 3.1: Ejemplo Data Hiding Javascript Co

Esta forma de crear ´ ambitos privados mediante funciones son com´ unmente llamadas Closures. Para trabajar c´ omodamente con las Closures hay que cambiar la forma de pensar. En otros lenguajes estamos acostumbrados a crear una clase, a˜ nadirle datos y alg´ un tipo de comportamiento tambi´en. Las clases son estado con datos y funciones, veamos un ejemplo de c´ omo se implementar´ıa una clase en Javascript:

1 Se tratan a las funciones como objetos de primer orden. Se pueden pasar funciones como argumentos a otras funciones, devolverlas como valores de otras funciones y asignarlas a variables.

Cap´ıtulo 3. El Juego

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

65

// Classes en Javascript function Compo rtamient o ( config ) { this . config = config ; this . doIt = function ( param ) { if ( this . config . flag ) { ... } else { ... } } } var b = new Co mportami ento ({ flag : false }) ; b . doIt ( " item " ) ;

´ digo Fuente 3.2: Clases en Javascript Co

En este ejemplo, lo que estamos haciendo es crear una funci´on que se comporte como una clase. Las funciones son objetos por lo tanto podemos extender de ellas y crear los miembros. this.config guardar´a la config pasado en construcci´on y this.doIt ser´a una funci´ on miembro de la clase. As´ı es como trabajar´ıamos con otros lenguajes, creamos una clase, a˜ nadimos datos a la clase y creamos m´etodos que trabajen con los datos locales. Para trabajar con Closures, de forma id´onea, vamos a cambiar la forma de expresarlo. En vez de crear una clase de la cual voy a instanciar, se crea un generador de comportamiento.

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// Closures en Javascript function d a m e C o m p o r t a m i e n t o ( config ) { return function () { if ( config . flag ) { ... } else { ... } } } var b = d a m e C o m p o r t a m i e n t o ({ flag : false }) ; b ( " item " ) ;

´ digo Fuente 3.3: Closures en Javascript Co

Cap´ıtulo 3. El Juego

66

Cuando se llama a dameComportamiento ´este me a devolver un objeto funci´on, la que est´ a en el return y va a guardar el estado con el que fue llamado(flag:false). O sea siempre que llame a dameComportamiento va a crear una funcion return nueva con su propio scope que guardar´ a el valor pasado en la llamada. Esto pasa gracias a que la funci´ on invocada crea un scope nuevo y cada scope nuevo pueda guardar sus valores propios. Por lo tanto la variable b va a contener una funci´on que esta guardando el estado con la que fue declarada. Y por u ´ltimo podemos llamar a la variable ”b“, que contiene una funci´ on que internamente guarda los datos, para que ejecute el comportamiento. Las Closures son el pensamiento inverso a lo que estamos acostumbrados. No creamos clases que son estado con comportamiento sino que creamos comportamiento que tiene el estado que necesita. Y la gracia, es que puedes pasar ese comportamiento por todo el programa porque las funciones en Javascript son de primera clase. Objetos Todo en Javascript son objetos, funciones y arrays tambi´en. Y todos los objetos son mutables, quiere decir, que puedes cambiar sus propiedades en tiempo de ejecuci´on. Y no s´olo eso, la potencia real, puedes cambiar todo el comportamiento de una sola vez.

Cap´ıtulo 3. El Juego

3.7.2.

67

Render Loop

Casi todos los juegos en HTML5 que han surgido en los u ´ltimos meses funcionan con un Game Loop mediante setInterval() o setTimeout(). Estos m´etodos son funciones propias de Javascript para manejar el tiempo. Por ejemplo “setInterval(function,1000)” ejecutar´ a autom´ aticamente “function” cada segundo. Desde que naci´ o el renderizado mediante la GPU con WebGL las aplicaciones son consumidoras de muchos recursos. Pongamos el caso que tenemos un navegador con 5 pesta˜ nas abiertas y las 5 cada una con un juego ejecut´andose. El navegador estar´a ejectuando los Game Loops de cada uno de ellos estando s´olo visible uno de ellos porque cada uno de ellos definio su game loop son setIntervals, y el navegador no es capaz de descubrir para qu´e lo estas usando. Los navegadores Web han proporcionado una soluci´on a este problema se llama requestAnimationFrame. En cada navegador se llama diferente. Funciona casi igual que la funci´ on setTimeOut(). T´ u le pides al navegador una animaci´on con un callback dentro de la llamada, esta llamada ser´a la llamada que ejecutar´a tu juego o Game Loop. De esta forma el navegador te ir´a dando frames seg´ un crea conveniente, por ejemplo cuando tu canvas sea visible. La forma correcta de implementar un Game Loop, en nuestro caso, el Render Loop es esta:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

// Creamos una funci´ o n r e q u e s t A n i m a t i o n F r a m e que sea un wrapper cross platform window . r e q u e s t A n i m a t i o n F r a m e = ( function () { // Comprobamos para cada Navegador su propio r e q u e s t A n i m a t i o n F r a m e return

window . r e q u e s t A n i m a t i o n F r a m e

||

// Chromium

window . w e b k i t R e q u e s t A n i m a t i o n F r a m e ||

// Webkit

window . m o z R e q u e s t A n i m a t i o n F r a m e

||

// Mozilla Geko

window . o R e q u e s t A n i m a t i o n F r a m e

||

// Opera Presto

window . m s R e q u e s t A n i m a t i o n F r a m e

||

function ( callback , element ) {

// IE Trident ? // Fallback function

window . setTimeout ( callback , 1000/60) ; } }) () ; // Esta es la funci´ o n que se ejecutar´ a i terativa mente function tick () { r e q u e s t A n i m F r a m e ( tick ) ; // Pedimos la siguiente animaci´ o n con el mismo callback doSomething () ;

// Lo que se ejecutar´ a en cada frame .

}

´ digo Fuente 3.4: Game Loop en HTML5 Co

Cap´ıtulo 3. El Juego

3.7.3.

68

Instanciaci´ on

Antes de entrar en detalle en la implementaci´on de los m´odulos tenemos que tener en cuenta que durante el pintado de un frame hay que enviar mucha geometr´ıa a la GPU. Mucha de esta geometr´ıa puede estar repetida y tenemos que evitar la redundancia. Pongamos por ejemplo que queremos pintar un modelo Wavefront:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

S t a t i c M o d e l s C t rl . prototype . draw = function draw () { var model = null ; // Recorrer todos los modelos de la escena for ( var i = 0; i < this . models . length ; i ++) { // Coger el modelo actual model = this . models [ i ]; // Chequear que esta completamente cargado if ( this . isModelLoaded ( model ) ) { // Setear los Buffers en GPU this . setBuffers ( model ) ; // Setear los materiales this . setMaterials ( model ) ; // Pintar la geometr´ ıa this . draw ( model ) ; } } }

´ digo Fuente 3.5: Pintado sin instanciaci´on Co

De esta forma por ejemplo, si tenemos un modelo Wavefront representando un coche y lo queremos pintar 4 veces, estaremos seteando los Buffers y los materiales 4 veces. En otras palabras, estaremos enviando a la GPU los mismos datos 4 veces. Si este modelo llega a tener 60000 caras, esto es muy costoso e innecesario. Por eso hace falta que cada modelo tenga la posibilidad de instanciar tantos modelos de su mismo tipo y en el Draw aprovechar los seteos en GPU solo una vez. Para hacer esto necesitamos que la entidad Modelo tenga un Array de instancias que contenga la informaci´ on u ´nica a esa instancia, rotaci´on, posici´on y escala.

Cap´ıtulo 3. El Juego

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

69

S t a t i c M o d e l s C t rl . prototype . draw = function draw () { var model = null ; // Recorrer todos los modelos de la escena for ( var i = 0; i < this . models . length ; i ++) { // Coger el modelo actual model = this . models [ i ]; // Chequear que esta completamente cargado if ( this . isModelLoaded ( model ) ) { // Setear los Buffers en GPU this . setBuffers ( model ) ; // Setear los materiales this . setMaterials ( model ) ; // Pintar la geometr´ ıa this . drawInstances ( model ) ; } } } S t a t i c M o d e l s C t rl . prototype . drawInstances = function drawInstances ( model ) { var scale = null ; // Recorrer todas las instancias del modelo for ( var i = 0; i < model . instances . length ; i ++) { //

Pintar la instancia i

this . draw ( model . instances [ i ]) ; } }

´ digo Fuente 3.6: Pintado con instanciaci´on Co

De esta forma conseguiremos que pintar 4 coches sea m´as barato en cuanto a rendimiento. Ya que los 4 coches ser´an 4 instancias del modelo coche y solo se setear´an los Buffers y los Materiales una vez, ya que comparten las mismas propiedades. En el Draw espec´ıfico de cada instancia podemos modificar la posici´on de cada instancia.

Cap´ıtulo 3. El Juego

3.7.4.

70

Componentes

En esta secci´ on se va a presentar el c´odigo m´as importante de los componentes principales. El c´ odigo est´ a modificado para la presentaci´on escrita y su entendimiento. Para consultar el c´ odigo real de producci´on referirse al c´odigo fuente adjunto con esta memoria. El objetivo de esta secci´on es ver la soluci´on de implementaci´on de las partes m´as importantes de cada componente seg´ un la tecnolog´ıa descrita.

3.7.4.1.

Patr´ on M´ odulo

Javascript no proporciona nada para encapsular el c´odigo directamente sino las herramientas para que lo hagas t´ u mediante un patr´on de construcci´on propio. Mediante las propiedades din´ amicas, las closures y sus scopes vamos a intentar crear un m´odulo que encapsule la l´ ogica interna. Mediante las closures podemos esconder los datos privados que necesitamos y publicar solo lo que queramos, dejando limpio el ´ambito global de la aplicaci´ on.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// Singleton Module var s in gl e to nM od u le = function () { // Priavate and unique part var privateVar ; function privateFunc () { ... } // Public Part return { f i r s t P u b l i c M e t h o d : function (a , b ) { ... privateVar ... }, s e c o n d P u b l i c M e t h o d : function ( c ) { ... privateFunc () ... } } }() ;

´ digo Fuente 3.7: Patr´on M´odulo en Javascript Co

´ Esta es la forma de poder crear un m´odulo Singleton en Javascript con datos internos no visibles. Creamos una funci´on an´onima y directamente la ejecutamos (l.17 del c´ odigo fuente anterior). Al ejecutarla, se crean las partes privadas y se devuelve a la variable singletonModule un literal con los m´etodos que queremos retornar. Estos m´etodos tendr´ an acceso a las partes privadas porque est´an definidas internamente en

Cap´ıtulo 3. El Juego

71

la closure pero desde fuera s´ olo se podr´a acceder a los m´etodos expuestos en el return, encapsulando as´ı el c´ odigo.

Cap´ıtulo 3. El Juego 3.7.4.2.

72

Camera El primer componente que hay que implementar es la c´amara. De esta forma tendremos el posicionamiento que queramos desde un principio y no nos tendremos que preocupar m´as por ello. Antes de explicar la implementaci´on de la c´amara de este juego hay que entender c´omo vamos a trabajar con las matrices y la transformaci´on de coordenadas. Los modelos que vamos a pintar estar´ an en coordenadas de modelo, coordenadas respecto su centro la posici´on (0,0,0). Seg´ un como queramos transformar ese modelo (posici´on, rotaci´on y escalado) definiremos una matriz de modelo para cada transformaci´on. Esta matriz ser´a multiplicada por los v´ertices del modelo y as´ı conseguir que ese objeto ahora est´e en coordenadas de mundo, con su posici´on real respecto al centro de la escena. La escena no siempre es vista desde la posici´on 0 y menos en este juego que la c´amara est´a en constante movimiento. La c´amara definir´a otra matriz, la view Matrix, que multiplicar´a al resultado anterior consiguiendo que las

Imagen 3.22: Secuencia de transformaciones de los v´ertices en los diferentes sistemas de coordenadas

posiciones de mundo sean vista en el sistema de coordenadas de la c´amara, view/eye coordinates. Ahora tenemos toda la escena en coordenadas de la c´amara. Seg´ un el viewport de proyecci´on, la

c´amara tendr´ a que difinir un fustrum de visi´on que genere una projecci´on en 2D, esto se consigue mediante una matriz de proyecci´on, que ser´a definida seg´ un los par´ametros del viewport, projection Matrix. Ahora s´ı tenemos la escena en coordenadas de clipping que es lo que OpenGL necesita para acabar de completar la secuencia. Los u ´ltimos dos pasos son internos de WebGL.

Cap´ıtulo 3. El Juego

73 En conclusi´on, la c´amara tiene que proporcionar a la aplicaci´on dos matrices: la viewMatrix y la projectionMatrix. Empecemos por la projectionMatrix, esta matriz va a definir la perspectiva de proyecci´on. Usaremos una librer´ıa llamada glMatrix para la gesti´on de matrices. Tambi´en nos proporcionara un m´etodo para generar una matriz de perspectiva con los t´ıpicos par´ametros: campo de visi´on, Z Far , Z near, y relaci´ on de aspecto. La viewMatrix tendr´a que posicionar

Imagen 3.23: C´ amara en tercera Persona

la c´amara en el lugar adecuado de la escena. En este caso estamos usando una c´amara en tercera persona que seguir´a al jugador all´ı donde se mue-

va. Por lo tanto, tal y como se muestra en la imagen, la c´amara habr´a que posicionarla en el lugar del jugador (posX, posY, posZ), separarla un incremento de X, subirla en el eje Y, un incremento Y, y por u ´ltimo, inclinarla para que mire hacia el jugador. En el siguiente c´ odigo vemos como se crean las dos matrices. 1 2 3 4 5 6 7 8

CameraCtrl . prototype . s e t P r o j e c t i o n M a t r i x = function () { // PROJECTION MATRIX // Create perspective Matrix using glMatrix // @param FOV - Angle field of view // @param ra - relaci´ o n de aspecto . Anchura del canvas entre la altura . // @param ZN - Plano Zeta near . // @param ZF - Plano Zeta far . // Matrix - Matrix donde depositar la proyecc´ on . mat4 . perspective ( this . _FOV , canvas . width / canvas . height , this . _ZN , this . _ZF , pMatrix ) ;

9 10 11 12 13 14 15 16 17 18 19 20 21 22

} CameraCtrl . prototype . setViewMatrix = function () { // VIEW MATRIX // Setear una matriz identidad mat4 . identity ( viewMatrix ) ; // Inclinar la c´ a mara en el eje X tantos radianes como inclinationX // y depositar la rotation en viewMatrix mat4 . rotate ( viewMatrix , inclinationX , [1 , 0 , 0]) ; // Trasladar la matrix a la posici´ o n del player y posicionar en tercera persona // seg´ u n los incrementos X e Y mat4 . translate ( viewMatrix , [ - posX - incX , - posY - incY , - posZ ]) ; }

´ digo Fuente 3.8: Matrices de C´amara Co

Cap´ıtulo 3. El Juego 3.7.4.3.

74

Render

El componente de render es el encargado de en cada frame pintar todo lo correspondiente en la escena. Es muy importante que la ejecuci´on durante el frame sea lo m´ as r´apido posible para que la aplicaci´on tenga un frame rate decente. A continuaci´ on se expone como est´a implementado el controlador de Wavefront Models que se encarga de pintar los modelos importados Wavefront. Como hemos visto en instanciaci´ on, la idea es cargar el Modelo prototipo y a partir de ah´ı pintar tantas instancias como queramos. Es un ejemplo de las llamadas que habr´a que hacer a WebGL para pintar. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

S t a t i c M o d e l s C t rl . prototype . draw = function draw () { var model = null ; // Recorrer todos los modelos de la escena for ( var i = 0; i < this . models . length ; i ++) { // Coger el modelo actual model = this . models [ i ]; // Chequear que esta completamente cargado if ( this . isModelLoaded ( model ) ) { // Setear los Buffers en GPU this . setBuffers ( model ) ; // Setear los materiales this . setMaterials ( model ) ; // Pintar la geometr´ ıa this . drawInstances ( model ) ; } } } S t a t i c M o d e l s C t rl . prototype . setBuffers = function setBuffers ( model ) { // Enviar Coordenadas de Textura a la GPU gl . bindBuffer ( gl . ARRAY_BUFFER , model . texturesBuf ) ; gl . v e r t e x A t t r i b P o i n t e r ( cur rentPro gram . attribute . textureCoord , model . texturesBuf . itemSize , gl . FLOAT , false , 0 , 0) ;

27 28 29 30

// Enviar Normales a la GPU gl . bindBuffer ( gl . ARRAY_BUFFER , model . normalsBuf ) ; gl . v e r t e x A t t r i b P o i n t e r ( cur rentPro gram . attribute . vertexNormal , model . normalsBuf . itemSize , gl . FLOAT , false , 0 , 0) ;

31 32 33 34

// Enviar las posiciones de los Vertices a la GPU gl . bindBuffer ( gl . ARRAY_BUFFER , model . vertexesBuf ) ; gl . v e r t e x A t t r i b P o i n t e r ( cur rentPro gram . attribute . vertexPosition , model . vertexesBuf . itemSize , gl . FLOAT , false , 0 , 0) ;

35 36 37

}

Cap´ıtulo 3. El Juego 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71

75

S t a t i c M o d e l s C t rl . prototype . setMaterials = function setMaterials ( model ) { // Mediante el componente de iluminaci´ o n seteamos los materiales this . lights . setMaterials ( currentProgram , model . materials ) ; } S t a t i c M o d e l s C t rl . prototype . drawInstances = function drawInstances ( model ) { var scale = null ; // Recorrer todas las instancias del modelo for ( var i = 0; i < model . instances . length ; i ++) { // Push State utils . mvPushMatrix () ; // Coger la instancia scale = model . instances [ i ]. scale ; // Coger su posici´ o n seg´ u n la Bounding Box simulada por la F´ ı sica mat4 . multiply ( mMatrix , model . instances [ i ]. bb . g e t D r a w P o s i t i o n M a t () ) ; // Escalar el modelo en la matriz de modelo . mat4 . scale ( mMatrix ,[ scale , scale , scale ]) ; // Enviar la Matrix a GPU utils . s e t M o d e l M a t r i x U n i f o r m s () ; // Draw Instance gl . drawArrays ( gl . TRIANGLES , 0 , model . vertexesBuf . numItems ) ; // Pop State utils . mvPopMatrix () ; } }

´ digo Fuente 3.9: Ejemplo de pintado de un modelo completo Co

Este es el conjunto de llamadas que tenemos que hacer para pintar cualquier modelo en WebGL. Lo importante de este c´odigo es ver c´omo el setBuffers y el setMaterials se hace una vez por modelo y luego por cada instancia se pinta reusando esos valores. El pintado de una instancia requiere que guardemos la matriz de estado de modelo, usemos los valores de posici´ on, orientaci´on y escalado de esa instancia y enviemos a la GPU la geometr´ıa mediante DrawArrays.

Cap´ıtulo 3. El Juego 3.7.4.4.

76

Modelos

El juego tiene varios tipos de modelos que a continuaci´on especifico:



Modelos B´ asicos - Estos modelos b´asicos representan las figuras geom´etricas del juego, tales como planos (paredes, suelo y techo) y prismas (columnas). Han sido creados a mano ya que no presentan gran complejidad.



Modelos Wavefront - Nos servir´an para decorar la escena con modelos est´aticos. Los modelos Wavefront son una especificaci´on est´andard de modelos en 3D. En el ap´endice A expongo todo la especificaci´on y la importaci´on a la aplicaci´on. En este caso, la tem´ atica es un parking, as´ı que usaremos modelos Wavefront de coches.



Modelos MD5 - Son Modelos animados para representar tanto a los enemigos como al jugador principal. En el ap´endice B expongo todo la especificaci´on, importaci´ on y modificaci´ on.

Imagen 3.24: Tipos de Modelos usados. En orden de izquiera a derecha: b´asicos, Wavefront y MD5.

Cap´ıtulo 3. El Juego 3.7.4.5.

77

Iluminaci´ on

Una de las grandes apuestas de este juego ha sido dotar a la escena de realismo. Para proporcionar realismo a una escena se requiere de iluminaci´on din´amica. La iluminaci´ on din´amica afecta a todos los puntos de la escena, seg´ un los valores de la luz, seg´ un la posici´ on del punto y seg´ un el material del punto. Se ha usado el modelo Phong con alguna variante. El modelo Phong o conocido como Per Pixel Lighting es un modelo de iluminaci´on que describe la forma en que una superficie refleja la luz como la combinaci´on de reflexi´on difusa, especular y ambiente, seg´ un la siquientes f´ ormulas:

I = Iambiente + Idif usa + Iespecular Iambiente = Lambiente ∗ Kambiente Idif usa = Ldif usa ∗ Kdif usa ∗ max(0, N ∗ L) Iespecular = Lespecular ∗ Kespecular ∗ max(0, R ∗ V )n I es el color total acumulado en componentes RGB llamado Intensidad de la luz. L son las componentes ambiente,difusa,especular en RGB de la intesidad de la luz. K son los coeficientes ambiente,difusa,especular en RGB del material que va a reflejar la luz.

Imagen 3.25: Modelo Phons de Iluminaci´on.

Intensidad Luz Ambiente La luz ambiente es el resultado de multiplicar las reflexiones de luz en todas las direcciones. Se modela usando un coeficiente constante para cada material. Es la luz constante en una escena.

Cap´ıtulo 3. El Juego

78

Intensidad Luz Difusa La intensidad de la luz difusa, es la intensidad de luz que un material refleja en todas direcciones. Esto significa que la cantidad de luz reflejada por una material no depende de la posici´on de visi´on solo de los ´angulos entre la normal de la superficie y la direcci´on de la luz. Por lo tanto, la intesidad de luz difusa es el resultado de multiplicar la componente de luz difusa por el coeficiente difuso del material por el coseno entre la normal N y la direcci´on de la luz desde Imagen 3.26: Intensidad Luz Difusa

la misma posici´on. El coseno de dos vectores normalizados se puede expresar como la

multiplicaci´ on de los dos vectores, simplicficando c´alculos: N ∗ L = |N | ∗ |L| ∗ cos(N, L) El coseno puede dar negativo, valor que no queremos lo que quiere decir que la luz est´a en el lado contrario a la normal y por lo tanto no refleja la luz difusa. Lo podemos arreglar con el m´ aximo de 0 y el valor del coseno, quedando la f´ormula as´ı: Idif usa = Ldif usa ∗ Kdif usa ∗ max(0, N ∗ L)

Intensidad Luz Especular Casi todas las superficies en la vida real no son totalmente difusas sino que dependiendo de c´omo se miren, hay zonas que tienen m´as reflexi´on que otras y hasta puede a ver puntos especulares generados por materiales muy brillantes y m´axima reflexi´ on seg´ un el punto de vista. Este punto de vista es el vector R, que es la direccii´on de reflexi´on, la invertida al direcci´on de la luz L seg´ un la normal N. R se calcula como: Imagen 3.27: Intensidad Luz Especular

R = 2 ∗ N ∗ (N ∗ L) − L

Cap´ıtulo 3. El Juego

79

Y el vector V es la direcci´ on de visi´on desde donde se est´a mirando el objeto. Por lo tanto la luz especular depende del ´angulo de reflexi´on de la luz con el vector de visi´on. Se eleva a n, que especifica el brillo del material. Y se acaba multiplicadno por la intensidad de la luz especular y por el coeficiente especular del material: Iespecular = Lespecular ∗ Kespecular ∗ max(0, R ∗ V )n

Definici´ on de la luz y de los materiales Seg´ un este modelo de iluminaci´on tenemos que definir en nuestro programa una luz con una posici´ on y unos valores de intensidad. Y para cada modelo de la escena unos coeficientes de reflexi´ on para que tengan un aspecto real. Definici´ on de la entidad Luz seg´ un unos par´ametros que vendr´an del fichero de configuraci´ on del m´ odulo de luces que definir´a las componentes:

1 2 3 4 5 6 7 8 9 10

var Light = function ( pX , pY , pZ ,

// Posici´ on

aR , aG , aB ,

// Intensidad Luz Ambiente

dR , dG , dB ,

// Intensidad Luz Difusa

sR , sG , sB ) {

// Intensidad Luz Especular

// Guardar los Valores en el objeto this . position =[ pX , pY , pZ ]; this . ambient =[ aR , aG , aB ]; this . diffuse =[ dR , dG , dB ]; this . specular =[ sR , sG , sB ]; }

´ digo Fuente 3.10: Entidad Luz Co

En el siguiente c´ odigo mostramos un m´etodo que crea un material. Los valores de entrada vendr´ an dados por las propiedades del objeto. Este c´odigo est´a alojado en el componente Utils para que cualquier otro componente que requiera crear un material, lo pueda hacer:

1 2 3 4 5 6 7 8 9 10 11

crea teMateri al : function ( ka , kd , ks ,s , texture ) { var material = [];

// Definir un Material Vac´ ıo

material . ka = []; material . ka . r = ka [0];

// Coeficientes RGB ambiente

material . ka . g = ka [1]; material . ka . b = ka [2]; material . ka . a = 1.0; material . kd = []; material . kd . r = kd [0]; material . kd . g = kd [1];

// Coeficientes RGB difusos

Cap´ıtulo 3. El Juego 12 13 14 15 16 17 18 19 20 21 22 23

80

material . kd . b = kd [2]; material . kd . a = 1.0; material . ks = []; material . ks . r = ks [0];

// Coeficientes RGB especulares

material . ks . g = ks [1]; material . ks . b = ks [2]; material . ks . a = 1.0; material . shininess = s ; // Coeficiente de brillo material . mapDiffuse = texture ;

// Textura del Objeto difuso

return material ; }

´ digo Fuente 3.11: Entidad Material Co

En este punto ya tenemos definido el modelo de iluminaci´on, las luces y los materiales. Falta enviar esta informaci´ on a GPU y realizar los c´alculos en los Shaders. En cada frame tenemos que enviar esta informaci´on a GPU. La luz es la misma para todos los modelos de la escena por lo tanto solo habr´a que enviarla una vez. Y para cada modelo habr´ a que enviar un material espec´ıfico, que se comporte como deba. El c´alculo de luz hay que hacerlo en cada frame ya que el punto de visi´on cambia todo el rato dependiendo donde est´ a el jugador. Los c´ alculos se expondr´an en la parte de Shaders. A continuaci´ on se especifica como enviar esta informaci´on a GPU.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

// Program - Es el Programa de shading que usaremos en la GPU . // Enviar Luz a GPU LightsCtrol . prototype . setLight

= function ( program ) {

// Abmient color uniform gl . uniform3fv ( program . uniform . ambientColor , this . ambientLight ) ; gl . uniform4fv ( program . uniform . lights [ i ]. position , this . light [ i ]. position ) ; gl . uniform4fv ( program . uniform . lights [ i ]. diffuse , this . light [ i ]. diffuse ) ; gl . uniform4fv ( program . uniform . lights [ i ]. specular , this . light [ i ]. specular ) ; gl . uniform4fv ( program . uniform . lights [ i ]. ambient , this . light [ i ]. ambient ) ; } // Enviar Material a GPU Lights . prototype . setMaterials

= function ( program , material ) {

gl . uniform3fv ( program . uniform [ " material . ka " ] , [ material . ka .r , material . ka .g , material . ka . b ]) ; gl . uniform3fv ( program . uniform [ " material . kd " ] , [ material . kd .r , material . kd .g , material . kd . b ]) ; gl . uniform3fv ( program . uniform [ " material . ks " ] , [ material . ks .r , material . ks .g , material . ks . b ]) ; gl . uniform1f ( program . uniform [ " material . shininess " ] , material . shininess ) ; gl . uniform1i ( program . uniform . sampler , 0) ;

Cap´ıtulo 3. El Juego 27 28 29 30 31 32 33 34 35 36 37 38 39

81

// Enviar Textura si tiene if ( material . mapDiffuse != undefined ) { gl . activeTexture ( gl . TEXTURE0 ) ; gl . bindTexture ( gl . TEXTURE_2D , material . mapDiffuse ) ; gl . uniform1f ( program . uniform [ " material . hasMapDiffuse " ] , 1.0) ; } else { gl . uniform1f ( program . uniform [ " material . hasMapDiffuse " ] , 0.0) ; } }

´ digo Fuente 3.12: Entidad Material Co

Con este c´ odigo y el render ya tenemos preparada la geometr´ıa de todos los modelos, la luz din´ amica que haya en el juego y los materiales. S´olo queda completar el c´alculo en los shaders.

Cap´ıtulo 3. El Juego 3.7.4.6.

82

Factor´ıa de Shaders

Los Shaders de la aplicaci´ on son recursos externos escritos en GLSL ES, un lenguaje de programaci´ on basado en C, por eso su extensi´on es “.c”. Este componente tiene un responsabilidad muy concreta e importante. En carga de la aplicaci´on tiene que cargar todos los shaders, compilarlos, linkarlos y ofrecerlos al resto de la aplicaci´on para su uso inmediato. Hay dos tipos de Shaders: Vertex Shaders y Fragment Shaders. La mejor forma de pensar en qu´e es un Shader es entenderlo como un c´odigo en C y su compilador. Aqu´ı funciona muy parecido. La m´aquina interna de OpenGL ES compilar´a ese c´odigo en un objeto, despu´es de la compilaci´on ese objeto puede ser linkado en un programa final, un binario. En OpenGL ES el programa final necesitar´a ser generado con dos objetos, un Vertex Shader y un Fragment Shader y as´ı generar finalmente un programa final. Ese programa final podr´ a ser enviado a GPU cuando lo necesitemos usar. Igual que en los compiladores de C puede haber errores de compilaci´on y errores de linkado ya que tiene que haber una cierta l´ogica entre ambos Shaders, por ejemplo que las variables varying sean las mismas entre ambos. Ahora expondremos el c´ odigo m´as importante de este componente, la creaci´on de un programa, para conocer las llamadas principales de WebGL y la representaci´on del dise˜ no:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

/* Rep resenta ci´ o n de la entidad l´ o gica Programa */ var Program = function ( vs_src , fs_src , program_name , attribs , uniforms , subUniforms ) { // Declarar Variables this . _program = null ; this . _program_name = program_name ; // Rutas C´ o digo Fuente this . _vS_src = vs_src ; this . _fS_src = fs_src ; // Cargar Programa this . _program = l o a d P r o g r a m F r o m P a t h ( this . _vS_src , this . _fS_src ) ; } /* Generar un programa GLSL ES */ function l o a d P r o g r a m F r o m P a t h ( vxPath , fsPath ) { // Cargar C´ o digo Fuente var vxStr = loadFile ( vxPath ) ; var fsStr = loadFile ( fsPath ) ;

Cap´ıtulo 3. El Juego 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

// Generar Objetos Compilados de Cada Shader var vertexShader = this . getShader ( vxStr , gl . VERTEX_SHADER ) ; var fragmen tShader = this . getShader ( fsStr , gl . FR AG ME N T_ SH AD E R ) ; // Crear un programa vac´ ıo var program = gl . createProgram () ; // Ascociarles los objetos vertex shader y fragment shader gl . attachShader ( program , vertexShader ) ; gl . attachShader ( program , fragm entShad er ) ; // Lincar los objetos en el programa gl . linkProgram ( program ) ; // Chequear errores if (! gl . g e t P r o g r a m P a r a m e t e r ( program , gl . LINK_STATUS ) ) { alert ( " Could not initialise shaders " ) ; } return program ; } /* Generar un objeto shader */ function getShader ( str , type ) { var shader ; // Crear un Shader Vac´ ıo shader = gl . createShader ( type ) ; // Asociarle el c´ o digo fuente gl . shaderSource ( shader , str ) ; // Compilar el shader gl . compileShader ( shader ) ; // Chequear errores if (! gl . g e t S h a d e r P a r a m e t e r ( shader , gl . COMP ILE_STAT US ) ) { alert ( gl . g e t S h a d e r I n f o L o g ( shader ) ) ; return null ; } // Devolver el objeto shader compilado return shader ; }

´ digo Fuente 3.13: Carga y representaci´on de los Programas de Shading Co

83

Cap´ıtulo 3. El Juego 3.7.4.7.

84

Shaders

En esta secci´ on se expone y se explican los Shaders usados para calcular todo el modelo de iluminaci´ on, posicionamiento y texturizaci´on. En este punto del desarrollo tenemos en GPU todo lo necesario para implementar los c´alculos. Para entender los Shaders primero hay que decir c´omo se ha partido la l´ogica entre los v´ertices y los fragmentos. C´ alculos Vertex Shader

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31



Calcular posici´ on del v´ertice en Eye space.



Calcular normal del v´ertice en Eye space.



Calcular posici´ on del punto de visi´on en Eye space.



Calcular Intensidad Luz Ambiente Iambiente = Lambiente ∗ Kambiente



Calcular parte constante de la luz Difusa Tdif usa = Ldif usa ∗ Kdif usa

// DEFINITIONS struct Light { vec4 position ; vec4 ambient ; vec4 diffuse ; vec4 specular ; }; struct Material { vec3 ka ; vec3 kd ; vec3 ks ; float shininess ; float hasMapDiffuse ; }; // ATTRIBUTES attribute vec3 v ertexPos ition ; attribute vec2 textureCoord ; attribute vec3 vertexNormal ; // UNIFORMS uniform vec3 ambientColor ; uniform Light lights [10]; uniform Material material ; uniform mat4 mvMatrix ; uniform mat4 pMatrix ; uniform mat3 nMatrix ; uniform mat4 vMatrix ;

Cap´ıtulo 3. El Juego 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

85

// VARYINGS varying mat4 vMtlLighting ; varying vec2 vTextureCoord ; varying vec3 v T r a n s f o r m e d N o r m a l ; varying vec4 vPosition ; varying vec4 vCamPos ; void l i g h t i n g _ p e r _ f r a g m e n t () { vec3 _ambient ; vec3 _diffuse ; vec3 _ ambient Global ;

// Normales a Eye Space mediante la inversa de la ModelViewMat v T r a n s f o r m e d N o r m a l = normalize ( nMatrix * vertexNormal ) . xyz ; // Coordenadas Textura vTextureCoord = textureCoord ; // Intensidad Luz Difusa - t´ e rmino constante _diffuse = material . kd * vec3 ( lights [0]. diffuse ) ; // Intensidad Luz Ambiente _ambient = vec3 ( material . ka ) * vec3 ( lights [0]. ambient ) ; _ambi entGlob al = vec3 ( ambientColor ) * vec3 ( material . ka ) ; // Setear las intensidades en una Varying Mat4 // para optimizar espacio vMtlLighting [0] = vec4 ( _ambient + _ambientGlobal ,1.0) ; vMtlLighting [1] = vec4 ( _diffuse ,1.0) ; // Calcular Punto de Vision en Eye Space para la luz especular vCamPos = vMatrix * vec4 (0.0 ,0.0 ,0.0 , 1.0) ; // Calcular la Posici´ o n en World Coordinates vPosition = mvMatrix * vec4 ( vertexPosition , 1.0) ; // Calcular la Posici´ o n en Clipping Coordinates gl_Position = pMatrix * vMatrix * mvMatrix * vec4 ( vertexPosition , 1.0) ; } void main ( void ) { l i g h t i n g _ p e r _ f r a g m e n t () ; }

´ digo Fuente 3.14: Vertex Shader Co

Cap´ıtulo 3. El Juego

86

C´ alculos Fragment Shader

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43



Calcular el resto del t´ermino difuso no constante: Idif usa = Tdif usa ∗ max(0, N ∗ L)



Calcular la intensidad especular:Iespecular = Lespecular ∗ Kespecular ∗ max(0, R ∗ V )n



Calcular la Intensidad Total: I = Iambiente + Idif usa + Iespecular



Calcular el color final: aplicar textura, si la tiene, y combinarla con la luz.

// DEFINITIONS struct Light { vec4 position ; vec4 ambient ; vec4 diffuse ; vec4 specular ; }; struct Material { vec3 ka ; vec3 kd ; vec3 ks ; float shininess ; float hasMapDiffuse ; }; // UNIFORMS uniform vec3 ambientColor ; uniform Light lights [10]; uniform Material material ; uniform sampler2D sampler ; uniform bool useLighting ; uniform bool useTextures ; uniform int renderType ; // VARYINGS varying mat4 vMtlLighting ; varying vec2 vTextureCoord ; varying vec3 v T r a n s f o r m e d N o r m a l ; varying vec4 vPosition ; varying vec4 vCamPos ;

void l i g h t i n g _ p e r _ p i x e l ( void ) { // Variables Temporales vec3 _diffuse , _specular , lightDir ; float NdotL , NdotHV ; float attenuation ; // Extraer los c´ a lculos del Vertex Shader vec3 ambient = vec3 ( vMtlLighting [0][0] , vMtlLighting [0][1] , vMtlLighting [0][2]) ; vec3 diffuse = vec3 ( vMtlLighting [1][0] , vMtlLighting [1][1] , vMtlLighting [1][2]) ; vec3 specular = material . ks ;

Cap´ıtulo 3. El Juego 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

float shininess = material . shininess ; // Tiene Textura ? float hasMapDiffuse = material . hasMapDiffuse ; // Normal interpolada vec3 normal = normalize ( v T r a n s f o r m e d N o r m a l ) ; // Calcular Direcci´ o n de la luz e intensidad seg´ un // posici´ o n del v´ e rtice if ( lights [0]. position . w == 0.0) // directional light ? { attenuation = 1.0; // no attenuation lightDir = normalize ( vec3 ( lights [0]. position ) ) ; } else // point or spot light { vec3 v e r t e x T o L i g h t S o u r c e = vec3 ( lights [0]. position - vPosition ) ; float distance = length ( v e r t e x T o L i g h t S o u r c e ) ; attenuation = 20.0 / distance ; // linear attenuation lightDir = normalize ( v e r t e x T o L i g h t S o u r c e ) ; } // Calcular max ( N *L ,0) para la luz difusa NdotL = max ( dot ( normal , lightDir ) ,0.0) ; // Calcular la intensidad difusa total _diffuse = attenuation * diffuse * NdotL ; // C´ a lculos Luz Especular // Calcular Vector punto de Vision vec3 viewDirection = normalize ( vec3 ( vCamPos - vPosition ) ) ; // Comprobar si el lado de reflexi´ o n es correcto if ( NdotL >

0.0)

{ // Calcular ( R * V ) NdotHV = max (0.0 , dot ( reflect ( - lightDir , normal ) , viewDirection ) ) ; // Calcular Intensad Especular Total _specular = attenuation * vec3 ( specular ) * vec3 ( lights [0]. specular ) * pow ( NdotHV , shininess ) ;

86 87 88 89 90 91 92 93 94 95 96 97

} else { _specular = vec3 (0.0 ,0.0 ,0.0) ; } vec4 fragmentColor = vec4 (1.0 , 1.0 , 1.0 , 1.0) ;

87

Cap´ıtulo 3. El Juego 98 99 100 101 102 103 104 105

88

// Si tiene Textura if ( floatToBool ( hasMapDiffuse ) ) { // Extraer colores de la textura seg´ u n las coordenadas . fragmentColor = texture2D ( sampler , vec2 ( vTextureCoord .s , vTextureCoord . t ) ) ; } // C´ a culo Color total , combinaci´ o n de la luz con el color de la textura si tiene .

106

gl_FragColor = vec4 ( ( ambient + _diffuse + _specular ) * vec3 ( fragmentColor ) , fragmentColor . a ) ;

107 108 109 110 111 112

} void main ( void ) { l i g h t i n g _ p e r _ p i x e l () ; }

´ digo Fuente 3.15: Fragment Shader Co

Estos dos Shaders descritos son los m´as importantes del juego. En el desarrollo del juego y en modo de Debug se han usado muchos otros con otros prop´ositos. Por ejemplo, para pintar los modelos MD5 se han usado otros Shaders, con los mismos principios pero cambiando el c´ alculo de v´ertices porque entraban cambios de posiciones dependiendo de las animaciones. Tambi´en para pintar la l´ınea de disparo se ha usado un Shader mucho m´as sencillo porque s´ olo hab´ıa que pintar una l´ınea con efecto l´aser semi-transparente. Pero la idea principal de nuestro modelo de iluminaci´on, texturizaci´on y c´alculo de posici´ on ha sido ´este.

Cap´ıtulo 3. El Juego 3.7.4.8.

89

Jugador Principal

Veamos como est´ an implementadas las funciones m´as importantes de este componente. Principalmente la gesti´ on del input del usuario. Empecemos por ver el c´ odigo de cuando se pulsa el bot´on izquierdo del rat´on, el disparo del jugador:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

md5PlayerCtrl = function () { ... ... // Mouse click handler canvas . a d d E v e n t L i s t e n e r ( ’ mousedown ’ , function ( event ) { // Comprobar que no est´ e disparando if ( self . m d 5 C u r r e n t A n i m T y p e != AnimType . SHOOT ) { // Actualizar estados self . stats . bullets_shot ++; // Pedir a la f´ ı sica un ray Casting seg´ u n el disparo physics . rayCasting ( self . _phy_idx , self . pos [0] , 0, self . pos [2] , self . orientation , 0) ; // Parar la animaci´ o n actual self . md5 Current Anim . stop ( self . instance ) ; // Setear animaci´ o n de disparo self . md5 Current Anim = self . md5Anims [ AnimType [ " SHOOT " ]]; self . m d 5 C u r r e n t A n i m T y p e = AnimType . SHOOT ; // Activar Animaci´ on self . md5 Current Anim . play ( self . instance ) ; // Reproducir Audio Disparo audio . fire () ; } } , false ) ; ... ... }

´ digo Fuente 3.16: Fire Handler Co

Cap´ıtulo 3. El Juego

90

Lo principal es ver c´ omo se asocia un evento DOM al controlador del player, en este caso el evento mousedown. Solo cogemos el evento si se produce dentro del canvas, dentro de la pantalla de juego. El resto del c´odigo esta auto explicado. Otra de las partes interesantes de este controlador es manejar los moviemientos del jugador. Leer el input de teclado y asociarlo a un movimiento y a una animaci´on. El siguiente c´ odigo se ejecuta cada frame, en el render loop, y mira que teclas hay presionadas y act´ ua en consecuencia:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

/* Draw Player Generico */ md5PlayerCtrl . prototype . drawPlayer = function () { // Guardar Estado utils . mvPushMatrix () ; // Aplicar fuerzas de movimiento this . se tP l ay er Fo r ce s () ; // Recoger resultados de la f´ ı sica this . s e t P h y s i c s C h a n g e s () ; // Renderizado this . draw () ; // Eliminar Estado utils . mvPopMatrix () ; } /* Posicionar al Player */ md5PlayerCtrl . prototype . s e t P h y s i c s C h a n g e s = function () { // Leer la posici´ o n del player seg´ u n la simulaci´ o n de la f´ ı sica physics . readObject ( this . _phy_idx , this . pos , this . _rotationMat ) ; } /* Aplicar Fuerzas de Movimiento */ md5Player . prototype . s et P la ye rF o rc es = function () { timeNow = new Date () . getTime () ; elapsed = timeNow - lastTime ; // calcular el incremento de movimiento seg´ u n la velocidad // Y seg´ u n el tiempo anterior . inc = this . speed * elapsed ; // Handle Movimiento seg´ u n las teclas W ,A ,S , D if ( pressedKeys [ ’W ’. charCodeAt (0) ]) { incZ = - inc ; nextAnim = AnimType . WALK_STRAIGHT ; } if ( pressedKeys [ ’S ’. charCodeAt (0) ]) { incZ = inc ; nextAnim = AnimType . WALK_BA CKWARDS ; } if ( pressedKeys [ ’A ’. charCodeAt (0) ]) { incX = inc ; nextAnim = AnimType . WALK_LEFT ; } if ( pressedKeys [ ’D ’. charCodeAt (0) ]) { incX = - inc ; nextAnim = AnimType . WALK_RIGHT ; }

Cap´ıtulo 3. El Juego 43 44 45 46 47 48 49 50 51 52 53 54 55

91

// Enviar a la f´ ı sica los incrementos de movimiento como fuerzas physics . s e tP la ye r Fo rc es ( this . _phy_idx , incX , incY , incZ ) ; // Calcular animaci´ o n seg´ u n la orientaci´ on nextAnim = this . c a l c N e x t A n i m O r i e n t a t i o n ( nextAnim ) ; // Aplicar Animaci´ on if ( this . m d 5 C u r r e n t A n i m T y p e != nextAnim ) { if (( this . m d 5 C u r r e n t A n i m T y p e != AnimType . SHOOT ) || ( this . m d 5 C u r r e n t A n i m T y p e == AnimType . SHOOT && ( this . instance . currentFrame %this . instance . c u rr en tM a xF ra me ) > Math . floor ( this . instance . cu rr en t Ma xF ra m e /2) ) )

56 57 58 59 60 61 62 63 64

{ this . md 5Current Anim . stop ( this . instance ) ; this . m d 5 C u r r e n t A n i m T y p e = nextAnim ; this . md 5Current Anim = this . md5Anims [ nextAnim ]; this . md 5Current Anim . play ( this . instance ) ; } } lastTime = timeNow ; }

´ digo Fuente 3.17: Movement Handler Co

Como se aprecia en el c´ alculo del incremento de movimiento, hace falta calcularla en funci´ on del tiempo, transucrrido desde la u ´ltima vez que se ejecut´o, para que m´aquinas con un frame Rate m´ as r´ apido, no generen un movimiento m´as r´apido.

Cap´ıtulo 3. El Juego 3.7.4.9.

92

F´ısica

Este componente ha sido implementado alrededor de 7 veces. Con diferentes librer´ıas y diferentes formas de ejecutarlo. Todas las librer´ıas de f´ısica en Javascript penalizan tanto el rendimiento que el frame rate cae en picado haciendo imposible un renderizado decente adem´ as de funcionar a una frecuencia diferente a la de render. Por eso se ha visto la necesidad de implementarlo en un hilo de ejecuci´on externo. En HTML5 hay la posibilidad de ejectuar Javascript scripts en un hilo aparte, Web Workers. Cada navegador los implementa de una forma pero la especificaci´on dice que tienen que correr independientemente al hilo principal. En el cap´ıtulo 4, se entrar´a m´as en detalle en la forma de trabajar con los Web Workers para dar a la aplicaci´on un entorno bueno de ejecuci´ on. Por lo tanto en lo que al componente se refiere, se va a ejecutar en segundo plano y se va comunicar con el render loop o el hilo principal mediante un controlador que har´ a de handler con el Web Worker, as´ı aislamos dentro del componente el core de la f´ısica para que en un futuro cambiar la librer´ıa o cambiar la implementaci´on sea solo cambiar las llamadas que ejecuta el handler y no tener que cambiar todo el componente. La gran responsabilidad de la librer´ıa de f´ısica que hemos usado,JigLibJS , es representar el mundo virtual de renderizado en un mundo f´ısico. El mundo f´ısico se configura con una gravedad y con volumenes o planos. Para cada elemento f´ısico se le asocia una forma, plano, cubo o esfera y se le asigna una masa y una fricci´on como atributos m´ as importantes. Seg´ un los requerimientos de tu aplicaci´on puedes simular como se comporta ese mundo f´ısico seg´ un una frencuencia. Una frecuencia muy alta gener´a simulaciones cada poco tiempo y puede que bloquee tu aplicaci´on porque no da tiempo a ejectuar todos los c´ alculos de colisi´ on. Pero una frecuencia muy baja generar´a simulaciones cada poco tiempo y crear´ a una simulaci´on que no parezca real, a trompos. Despu´es de varias sesiones de testing, seg´ un los vol´ umenes de nuestra apliaci´on, se ha conseguido establecer un int´ervalo de ejecuci´ on de 30 pasos por segundo, la mitad del frame rate. A esta frecuencia la f´ısica simula las posiciones cada 2 frames de render sin verse penalizado. Por lo tanto, el componente estar´a formado por un controlador de f´ısica que se encargar´a de publicar la f´ısica a la aplicaci´on y de manejar el Web Worker. Para comunicarse con un Web Worker la u ´nica forma, a d´ıa de hoy, es mediante cadenas de texto. Para un mejor manejo se usar´ an cadenas de texto en JSON, as´ı la conversi´on y la extracci´ on de datos es muy f´ acil. El c´ odigo de este componente es bastante complicado de resumir y no ser´ıa did´actico exponerlo en la memoria. Para consultar la implementaci´on exacta referirse al archivo physics.js y worker.js del c´ odigo fuente. Se expondr´a un diagrama de funcionamiento para explicar como trabaja el componente.

Cap´ıtulo 3. El Juego

93

Imagen 3.28: F´ısicas en un Web Worker.

Lo importante de este componente es ver c´omo la librer´ıa de f´ısicas trabaja en un Web Worker aislada del hilo principal de ejecuci´on a una frecuencia fw = 1000/30 ms (milisegundos) y va devolviendo los resultados de la emulaci´on al controlador del hilo principal. Este controlador guardar´a las posiciones de todas las simulaciones para que el resto de la aplicaci´ on consulte a cada frame, en este caso el frame rate de renderizado ideal es: fr = 1000/60 ms (milisegundos), las posiciones donde pintarse.

Cap´ıtulo 3. El Juego 3.7.4.10.

94

Audio

Este componente expone la librer´ıa Buzz al resto de la aplicaci´on. No se expone la l´ogica sino directamente los sonidos que se quieren reproducir. Se es consciente de la poca escalabilidad de este dise˜ no pero no se requer´ıa m´as para el juego.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

// Construcci´ o n del componente define ([ " js / globals . js " ,

// Importar Variables Globales

" js / util / buzz . js " // Importar la librer´ ı a Buzz ] , function () { " use strict " // Preload los sonidos buzz . defaults . preload = ’ auto ’; // No autoplay buzz . defaults . autoplay = false ; // No loop sounds buzz . defaults . loop = false ; // Tipos de Audio - Cross Browser ones buzz . defaults . formats = [ ’ ogg ’ , ’ wav ’ ]; ... // Cargar el sonido de disparo var sgfire1 = new buzz . sound ( " data / sounds / wetfire3 " , { formats : [ " wav " ] , preload : true , autoload : false , loop : false }) ; // Funci´ o n de reproducci´ o n del sonido de disparo del jugador principal function fireSound () { // Sonido de Recarga del arma si llega al l´ ı mite de disparos if ( shoots %nshoots == nshoots -1) { shoots = shoots +1; reload . play () ; } // Si el sonido de recarga ha acabado , reproducir disparo if ( reload . isEnded () ) { shoots = shoots +1; sgfire1 . play () ; } } ...

Cap´ıtulo 3. El Juego 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

95

// M´ e todos p´ u blicos al resto de la aplicaci´ on return { c h e c k T y p e s S u p p o r t e d : checkTypesSupported , muteAll : muteAll , unmuteAll : unmuteAll , fire : fireSound ,

// Ejemplo de i mplement aci´ o n de arriba

playerPain : playerPainSound , enemyHit : enemyHitSound , enemyAttack : enemyAttackSound , playTheme : themeSound , fadeTheme : fadeTheme , reload : reloadSound , isPa rtyStart ed : isPartyStarted , playerDeath : function () { playerDeath . play () ; } }; }) ;

´ digo Fuente 3.18: Implementaci´on Componente Audio. Co

En este fragmento de c´ odigo se aprecia c´omo se crea el componente incluyendo la librer´ıa Buzz. Una vez incluida, se inicializa. Se ha puesto de ejemplo la creaci´on de un sonido, en este caso el de disparo. Adem´as de la funci´on de reproducci´on que maneja el sonido creado anteriormente tambi´en se ha a˜ nadido c´omo se publica ese m´etodo en el retorno del componente.

Cap´ıtulo 4

T´ ecnicas, Optimizaciones y Rendimiento Tenemos que ser conscientes que este tipo de aplicaciones van a ser ejecutadas por muy diferentes tipos de Hardware y tendr´an un l´ımite. Es responsabilidad del desarrollador adaptarse y ofrecer la mejor respuesta posible, si es que existe.

4.1. 4.1.1.

Javascript Arrays

Es muy com´ un en Javascript usar Arrays para almacenar datos. Los Arrays en Javascript son objetos y por lo tanto est´an dotados de todas las propiedades que estos tienen, adem´ as de la herencia diferencial de los Arrays. Al tener tantas posibles operaciones el compilador de Javascript tiene diferentes formas de representarlo y estas representaciones puede ser costosas si no tenemos cuidado. El compilador tiene dos formas de almacenar los Arrays: Sparse Arrays y Dense Arrays. Como desarrollador no puedes elegir cual de las dos implementaciones usar, se har´a autom´aticamente. Sparse Arrays son como los Arrays del lenguaje C, trozos de memoria contigua. Los Dense Arrays, en cambio, son como tablas de Hash. Dense Arrays tienen un coste mucho m´as alto as´ı que lo que tenemos que hacer es intentar dejar el c´odigo de tal forma que el compilador nos genere Sparse Arrays. Veamos un ejemplo 1 2 3

var a = new Array () ; // Declarar un array no inicializado // Acceder din´ a micamente a la posici´ o n 1000. a [1000] = 8;

´ digo Fuente 4.1: Javascript Arrays Mala Idea Co

96

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

1 2 3 4 5

97

// Buena idea - Sparse Arrays // Declarar un array inicializado var a = new Array (1001) ; // Acceder din´ a micamente a la posici´ o n 1000. a [1000] = 8;

´ digo Fuente 4.2: Javascript Arrays Buena Idea Co

El primer c´ odigo se implementara como Dense Arrays porque el compilador no sabe cuanto ocupa el array y encima vamos a acceder a posiciones no inicializadas, nada parecido a un t´ıpico array en C, por lo tanto directamente se implementa como un Dense Arrays y no queremos que sea as´ı. En el segundo ejemplo declarado un Array con un tama˜ no espec´ıfico por lo tanto el compilador puede tratarlo como un Array en C y pedir una memoria fija para todo el almacenamiento, opci´on mucho m´as eficiente que usar tablas de Hash. As´ı que en conclusi´on, deber´ıamos siempre declarar los tama˜ nos de los Arrays para que su implementaci´on interna sea la m´as ajustada posible. Es algo muy sencillo a tener en cuenta que ofrece un gran redimiento a la aplicaci´on.

4.1.2.

Objetos

Los objetos en Javascript son representados internamente como un array de clave - valor. Las propiedades pueden cambiar din´amicamente al igual que la cadena de herencia diferencial. Cuando declaras un objeto este va a crear una estructura interna llamada Hidden Class que lo representa y todas las instancias de ese objeto van a estar representadas por la misma Hidden Class. Si, a continuaci´on, a˜ nadimos propiedades din´amicamente, ya que Javascript lo permite, esa Hidden Class deja de ser u ´til para esa instancia y tenemos que pagar el coste de volver a crearla para representar esa propiedad din´amica que estamos a˜ nadiendo, pogamos un ejemplo.

1 2 3 4 5 6 7 8 9 10

// Declaraci´ o n de un Objeto function Vec2 (x , y ) { this . x = x ; this . y = y ; } // Creaci´ o n e inici alizaci´ on var v0 = new Vec2 (5 ,7) ; // A~ n adir propiedad din´ a micamente cambiando la HiddenClass solo de v0 . v0 . z = 12;

´ digo Fuente 4.3: Javascript Objects Mala Idea Co

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

98

Este c´ odigo es ineficiente porque estamos a˜ nadiendo la propiedad z din´amicamente a una instancia v0 que tiene una Hidden Class de Vec2. Como Vec2 no tiene definida z tenemos que crear otra Hidden Class para v0 siendo este proceso muy costoso. La forma correcta para hacer nuestro c´ odigo eficiente es definir objetos fijos y no a˜ nadir y quitar propiedades din´ amicamente para que todas las instancias compartar la misma Hidden Class.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// Declaraci´ o n de un Objeto function Vec3 (x ,y , z ) { this . x = x ; this . y = y ; this . z = z ; } // Declaraci´ o n de un Objeto function Vec2 (x , y ) { this . x = x ; this . y = y ; } // Creaci´ o n e inici alizaci´ o n de vec2 var v0 = new Vec2 (5 ,7) ; // // Creaci´ o n e inicia lizaci´ o n de vec3 var v1 = new Vec3 ( v0 .x , v0 .y ,12) ;

´ digo Fuente 4.4: Javascript Objects Buena Idea Co

De esta forma tenemos dos objetos diferentes para representar los datos que necesitemos y cada uno de ellos con su Hidden Class y sin adici´on ni eliminaci´on de propiedades din´amicas. Los objetos son como Arrays por lo tanto tienen dos posibles representaciones internas igual que los Arrays: Sparse y Dense. Como se ha comentado, en la secci´on anterior lo que queremos es que se implementen como Sparse por la eficiencia de acceso y creaci´on. ¿Qu´e es lo que convierte nuestros objetos en Arrays de C o en tablas de Hash? Una de las causas, que puede generar que nuestro objeto sea representado como una tabla de Hash, es que el objeto tenga muchas propiedades. Tambi´en puede ser causado por cambiar las propiedades din´amicamente ya que la representaci´on no encaja como un Array fijo de memoria y tiene que ser implementado por una tabla de Hash din´amica. As´ı que lo que necesitamos hacer es crear objetos peque˜ nos y fijos. De esta forma el compilador nos ofrecer´ a la mejor implementaci´on posible, Arrays parecidos a los de C.

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

4.1.3.

99

Optimizaciones internas de Javascript

Los motores de Javascript, aparte de interpretar tu c´odigo, intentan optimizarlo y buscar patrones de ejecuci´ on para optimizarlo al m´aximo. Hay que intentar hacer c´odigo lo m´ as apto posible para que estos compiladores puedan optimizarlo al m´aximo. Hay ciertos aspectos que tenemos que evitar para que el compilador no se estrese y decida no optimizar:



Funciones muy grandes no son candidatas para ser optimizadas.



ForIn bucles.



Conversi´ on de n´ umeros a String.



Try-Catch.

Las optimizaciones que se hacen son especulativas ya que no siempre tu c´odigo va a ser ejecutado de forma parecida en todos los casos. Pero es bueno que sea as´ı. Quiero decir, hay que intentar programar para que las funciones se ejecuten de la misma forma intentando no cambiar su comportamiento din´amicamente para que esas optimizaciones especulativas se conviertan en c´odigo optimizado y v´alido. Si en alg´ un caso, habiendo ya substituido el c´ odigo lento por el optimizado, se cambia el comportamiento de esa funci´ on, el motor tendr´ a que deoptimizarlo porque ya no valdr´a y no queremos vernos penalizados por esto: Optimizar, aplicar, deoptimizar y ejecutar el nuevo c´odigo no optimizado, situaci´ on bastante tr´agica. En conclusi´ on, tenemos que dejar al optimizador c´odigo sencillo, con tipos fijos, predicible y no din´ amico porque aunque nosotros no estemos escribiendo el c´odigo nativo, el compilador s´ı y hay que dejarselo f´acil para que lo asimile y lo traduzca. Una peque˜ na regla general es que cuanto m´ as tu c´odigo se parezca al lenguaje C o C++ m´as r´apido va a ser.

4.1.4.

Garbage Collector

Uno de los grandes problemas de aplicaciones grandes en Javascript es el Garbage Collector, el encargado de limpiar toda esa memoria ya no referenciada en tu c´odigo. Normalmente los motores de Javascript tiene dos ´areas de memoria, una para los objetos de corta vida y otra zona para los objetos de larga vida para optimizar la b´ usqueda. Lo que hay que intentar es minimizar el coste del Garbage Collector. Por ejemplo los objetos se pueden promocionar de corta a larga vida y este paso es muy costoso porque se copia

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

100

memoria de un lado a otro. Lo que no queremos es que nada m´as ser promociando a la memoria de larga vida este objeto tenga que ser borrado porque ya no se usa. As´ı que se tiene que programar intentado que los objetos: tengan una vida larga o una vida corta. No queremos objetos con vida media porque consumir´an rendimiento innecesariamente. Teniendo en cuenta lo comentado, el Gargage Collector se dedicar´a a limpiar esos objetos no referenciados. Siendo conscientes de esto hay que intentar dejar de referenciar esos objetos cuando no los queramos y no dejarlos referenciados si no los vamos a usar. Puede ser una tarea dura si no eres consciente desde un principio porque la generaci´ on de Closures pueden esconder much´ısimas referencias sin que t´ u lo sepas una vez el c´odigo ya est´ a escrito. Pero la mejor forma de evitar el impacto del Garbage Collector en su limpieza es no generar basura. No generar basura quiere decir que si el Garbage Collector va a estar mirando todas las referencias de los objetos de tu programa, no generes objetos nuevos innecesarios. Pogamos un ejemplo:

1 2 3 4 5 6 7 8

// Funci´ o n que suma dos vectores y devuelve el resultado en un vector nuevo function add ( vecA , vecB ) { return new Vector ( vecA . x + vecB .x , vecA . y + vecB .y , vecA . z + vecB .z , ); }

´ digo Fuente 4.5: Javascript Garbage Collector Mala Idea Co

En este c´ odigo cada vez que sumamos dos vectores se crea un tercero. Proceso que en muchos casos va a ser innecesario y encima costoso si ejecutamos esta tarea en cada Frame muchas veces. Normalmente este tipo de operaciones se pueden hacer dejando el resultado en una de los par´ ametros de entrada ahorr´andonos el coste de crear nuevos objetos:

1 2 3 4 5 6

// Funci´ o n que suma dos vectores y devuelve el resultado en el primer vector function addTo ( vecA , vecB ) { vecA . x = vecA . x + vecB . x ; vecA . y = vecA . y + vecB . y ; vecA . z = vecA . z + vecB . z ; }

´ digo Fuente 4.6: Javascript Garbage Collector Buena Idea Co

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

4.1.5.

101

Render Loop Memory

Al ser una aplicaci´ on de renderizado en tiempo real la ejecuci´on de esta aplicaci´ on es continua en un bucle de Frames. Hay que ser conscientes de que cada 16 milisegundos se va a ejecutar el mismo c´ odigo continuamente por lo tanto cualquier optimizaci´on por peque˜ na que sea en el bucle va a tener un gran impacto en el rendimiento general de la aplicaci´ on ya que se va a ejecutar much´ısimas veces durante el proceso de ejecuci´on. Un ejemplo claro para entender como trabajar con bucles de renderizado es la gesti´ on de matrices. En cada Frame tenemos que hacer el c´alculo de los diferentes sistema de coordenadas ya que las posiciones cambian continuamente. Para hacer el cambio de sistemas de coordenadas hay que generar una serie de matrices, como se explic´o en el componente de la c´ amara. Este c´alculo se va a ser exactamente el mismo pero con diferentes n´ umeros as´ı que nos tenemos que asegurar que el c´alculo no genere memoria nueva. Una de las peque˜ nas t´ecnicas para esto es usar variables de m´odulo para realizar c´alculos en vez de declarar nuevas variables cada frame. Otro ejemplo en c´odigo: 1 2 3 4 5 6 7

// vec3 . direction // Generates a unit vector pointing from one vector to another // // Params : // vec - origin vec3 // vec2 - vec3 to point to // dest - Optional , vec3 receiving operation result . If not specified result is written to vec

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

// // Returns : // dest if specified , vec otherwise vec3 . direction = function ( vec , vec2 , dest ) { if (! dest ) { dest = vec ; } var x = vec [0] - vec2 [0]; var y = vec [1] - vec2 [1]; var z = vec [2] - vec2 [2]; var len = Math . sqrt ( x * x + y * y + z * z ) ; if (! len ) { dest [0] = 0; dest [1] = 0; dest [2] = 0; return dest ; } len = 1 / len ; dest [0] = x * len ; dest [1] = y * len ; dest [2] = z * len ; return dest ; };

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento 33 34 35 36 37 38

102

// Funci´ o n que retorna un vector direcci´ on function g e t E n e m y D i r e c t i o n ( enemy ) { var direction = vec3 . create () ; direction = vec3 . direction ([ cX ,0.0 , cZ ] , enemy . bb . ge t Dr aw Po s it io n () , direction ) ; return direction ; }

´ digo Fuente 4.7: Memoria Render Loop Mala Idea Co

En este c´ odigo para cada enemigo del juego y para cada frame tenemos que calcular la direcci´ on de movimiento con la funci´on getEnemyDirection este c´odigo tiene varios puntos negros que por si solos no son importantes pero si somos conscientes de que se va a jecutar tantas veces por enemigo y por Frame es un coste que penaliza en muchos aspectos. Primero en rendimiento y segundo en el Garbage Collector. Veamos como ser´ıa la forma correcta:

1 2 3 4 5 6 7 8

// Declaraci´ o n de direction fuera del hilo de ejecuci´ o n de render var direction = vec3 . create () ; // Funci´ o n que retorna un vector direcci´ on function g e t E n e m y D i r e c t i o n ( enemy ) { vec3 . direction ([ cX ,0.0 , cZ ] , enemy . bb . g et D ra wP os i ti on () , direction ) ; return direction ; }

´ digo Fuente 4.8: Memoria Render Loop Buena Idea Co

En este c´ odigo hemos extra´ıdo la creaci´on de “direction” fuera del hilo principal para que sea una variable global del m´odulo y no se tenga que crear cada vez que ejecutamos esta funci´ on. Aparte del ahorro de memoria, si nos fijamos en la llamada a vec3.direction el u ´ltimo par´ ametro es donde se depostiva el valor resultado, por lo tanto no hace falta retornarlo y volverlo a asignar. Es un ejemplo de como una peque˜ na correcci´on puede afectar al c´ omputo global de la aplicaci´on ya que tanta creaci´on de memoria puede generar un par´ on en la limpieza de basura y peque˜ nos arreglos de rendimiento acaban generando un buen resultado.

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

4.2.

103

Reglas Generales

En esta secci´ on nos vamos a centrar en reglas generales de como trabajar en una aplicaci´ on con WebGL que todo programa deber´ıa de cumplir.

4.2.1.

Llamadas a WebGL

glDraw: Reducir al m´ınimo posible las llamadas de pintado. Cuando hacemos esta llamada estamos enviando un flujo de datos a la GPU y tenemos que aprovecharlo al m´aximo posible para no parar el Stream de datos. As´ı que si por ejemplo tenemos que pintar 50 columnas es mejor empaquetarlas en u ´nico glDraw con el inconveniente que el c´alculo de posici´ on habr´ a que hacerlo en GPU y no en CPU. glGet o glRead: Todas las llamadas de WebGL que empiezan por glGet o glRead son costosas de por si porque estamos leyendo datos de la GPU usando un circuito inverso, ya que normalmente se env´ıan datos a la GPU, no se leen de ellos. Como se explic´o en la secci´ on del funcionamiento interno de los navegadores, el mecanismo de lectura de datos de la GPU funciona mediante un forma extra˜ na y costosa. S´olo es recomendable usarlas en modo de Debug o para prop´ositos muy espec´ıficos en los que se sabe el bajo rendimiento de estas llamadas porque generan comandos Flush en la GPU para acabar de ejecutar el estado actual y poder leer el dato demandado. glGetError: Esta llamada es muy t´ıpica para recoger los errores en las llamadas de WebGL. Como se ha dicho anteriormente en entornos de producci´on hay que quitarlas. Redundancia: Hay que identificar todas aquellas llamadas que sean repetitivas en WebGL. Hay muchas veces que enviamos a GPU m´as datos de los necesarios, repitiendo por ejemplo algunos datos uniforms que han sido previamente enviados por otro m´odulo. Hay que enviar los menos datos posibles. En WebGL se guarda el estado entre Frame y Frame as´ı que podemos ahorrarnos ciertos env´ıos de datos si sabemos que no van a cambiar y solo enviar los cambios cuando convengan. glCompileShader y glLinkProgram: Estas instrucciones son muy costosas y no deber´ıamos hacerlas durante el bucle de renderizado es mejor crear, compilar y linkar los shaders en carga de nuestro programa y tenerlos preparados para cuando se necesiten y no hacerlo din´ amicamente en tiempo de ejecuci´on.

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

4.2.2.

104

Buffering

Hay varias etapas en la vida de un Frame. Como vemos en la imagen lo primero que se ejecuta el RAF (Request Animation Frame) durante este proceso se ejecuta el c´odigo de tu aplicaci´ on y se env´ıan las llamadas pertinentes a GPU mediante WebGL. Al final de este proceso el navegador se encarga de recoger toda esa informaci´on y pintarla en el navegador como debe, communmente llamado Composite o compositor.

Imagen 4.1: Etapa de un frame en WebGL.

Pero por la naturaleza de los navegadores, pueden llegar eventos externos como click de rat´ on o temporizadores que de repente lleguen a tu aplicaci´on y tengan que ser ejecutados en ese momento. Por ejemplo en la imagen nos llega un click de rat´on al final de un Frame y ese click desencadena en algunos c´alculos costosos, como el c´alculo del disparo. Ese c´ alculo mientras esta siendo ejecutado esta relantizando el Frame siguiente generando un Frame Rate inestable y malo para la percepci´on del usuario que justo cuando clica hay una peque˜ na interrupci´on de pintado.

Imagen 4.2: Buffering Input Usuario.

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

105

Lo que queremos es hacer un Buffer que deje las acciones ah´ı guardadas y cuando nosotros queramos atenderlas. No queremos que se procesen autom´aticamente por el navegador. Estas acciones son el input del usuario y lo temporizadores internos de nuestra aplicaci´ on. De esta forma tenemos el control del input del usuario fuera de RAF y no perjudicamos al frame rate de la aplicaci´on aunque haya una exhaustiva cantidad de eventos externos. De esta forma leeremos el buffer para tratarlo en el siquiente Frame con una latencia de 16 milisegundos constante sin que el Frame Rate se vea afectado. La idea principal es pasar de s´ıncrono a as´ıncrono. El ejemplo siguiente muestra como se hace Buffering de teclado en Javascript y como luego se lee as´ıncronamente.

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// Buffer de teclas presionadas var pressedKeys = new Array (128) ; // Tecla presionada window . a d d E v e n t L i s t e n e r ( ’ keydown ’ , function ( event ) { // Guardamos para la tecla keyCode que est´ a presionada pressedKeys [ event . keyCode ]= true ; } , false ) ; // Tecla despresionada window . a d d E v e n t L i s t e n e r ( ’ keyup ’ , function ( event ) { // Guardamos para la tecla keyCode que NO est´ a presionada pressedKeys [ event . keyCode ]= false ; } , false ) ;

´ digo Fuente 4.9: Buffering Javascript Co

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// Lectura del Buffer para el movimiento del usuario de las teclas WASD . if ( pressedKeys [ ’W ’. charCodeAt (0) ]) { incZ = - inc ; nextAnim = AnimType . WALK_STRAIGHT ; } if ( pressedKeys [ ’S ’. charCodeAt (0) ]) { incZ = inc ; nextAnim = AnimType . WALK_BA CKWARDS ; } if ( pressedKeys [ ’A ’. charCodeAt (0) ]) { incX = inc ; nextAnim = AnimType . WALK_LEFT ; } if ( pressedKeys [ ’D ’. charCodeAt (0) ]) { incX = - inc ; nextAnim = AnimType . WALK_RIGHT ; }

´ digo Fuente 4.10: Lectura del Buffer Javascript Co

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

4.3.

106

CPU vs GPU

Antes de embarcarse en un proyecto de estas caracter´ısticas hay que entender bien los prop´ ositos de cada unidad de proceso. La GPU es una CPU con un prop´osito espec´ıfico por lo tanto tienen diferentes aspectos de rendimiento. Algunas tareas son m´as r´apidas en CPU y otras en GPU por eso es importante saber cual es el significado real de cada uno de ellos a la hora de decidir que debe ejecutarse en CPU y que debe ejecutarse en GPU ya que muchas veces podemos decidir en cual de los dos hacerlo. GPU: Graphics Processing Unit La GPU est´ a dise˜ nada para manejar grandes cantidades de datos en flujo de operaciones sencillas que sean paralelizables. En otras palabras, trabajos no paralelizables o datos muy costosos no son el prop´osito de estos procesadores. CPU: Central Processing Unit La CPU en cambio est´ a dise˜ nada para ejecutar una instrucci´on lo m´as r´apido posible. Y rinde bien con operaciones complejas en una u ´nica instrucci´on en peque˜ nos flujos de datos. Con estas sencillas definiciones, podemos entender que en la unidad gr´afica tienen que ejecutarse todas aquellas cosas que no sean dependientes entre v´ertices y p´ıxeles. De esta forma, conseguiremos paralelizar todo el trabajo y aprovechar al m´aximo las dos unidades de proceso. Pero el entorno de WebGL es especial. Nuestra aplicaci´on no est´a corriendo en un proceso u ´nico dedicado, est´a bajo el mando de un navegador y encima en Javascript, que para darnos una r´apida idea es la mitad de r´apido que C++ 1.

Conociendo este rendimiento tan bajo de Javascript podemos ver el sistema como si

tuvi´eramos una CPU m´ as lenta de lo normal y una GPU potente. Entonces habr´a ciertas cosas que alomejor t´ıpicamente son realizadas en CPU que deber´ıan ir a la GPU para liberar la CPU. Estas cosas o tareas necesitan seguir unas reglas porque la GPU tiene su prop´ osito. En reglas generales todo lo que no tenga estado ir´a a la GPU. Si modifica los v´ertices ir´ a en el Vertex Shader y si modifica los p´ıxeles ir´a en el Fragment Shader. No es una regla general, pero Javascript es lento y todo lo que se pueda hacer fuera de ese ´ambito ser´ a mejor siempre que tengamos en cuenta el prop´osito de las operaciones.

1

C++ vs Javascript Link

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

4.4.

107

Rendimiento Pipeline

En el proceso de pintado de WebGL hay muchas etapas e intervienen muchos factores y unos dependen de otros. As´ı que mejorar un aspecto puede impactar negativamente en otro. Por lo tanto a la hora de optimizar el Pipeline de WebGL hay que seguir unas gu´ıas fijas. Esta secci´ on ha sido integramente copiada del libro GPU Gems de NVidida. Lo importante es entender e identificar todos los pasos del Pipeline de WebGL, adaptarlo al siguiente gr´ afico y buscar donde est´a nuestro cuello de botella si lo hay.

Imagen 4.3: Rendimiento Pipeline.

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

4.5.

108

Web Workers

Un Web Worker es un Script Javascript ejecutado en HTML que es procesado en segundo plano e independientemente.

Imagen 4.4: Esquema Web Worker. Imagen de un Blog de Microsoft.

Como vemos en la figura un Web Worker trabaja en paralelo con el hilo principal. No tiene acceso al elemento Document del navegador con lo que no se puede comunicar con la interfaz gr´ afica HTML pero nos proporciona un hilo de ejecuci´on extra para hacer trabajos en segundo plano. Una aplicaci´ on de WebGL tiene un bucle de renderizado dependiente del Navegador. Durante cada iteraci´ on tenemos que ser capaces de pintar la escena con WebGL, simular la f´ısica, ejecutar la l´ ogica del juego y simular la Inteligencia Artifical entre otras muchas cosas. Cuando por ejemplo integr´e la f´ısica en este bucle no era capaz de simular tantas cosas en un frame. 16 milisegunos no eran suficientes. As´ı que gracias a los Web Workers es posible ejectuar en paralelo tareas extras como la f´ısica. De esta forma hacemos que la f´ısica en vez de trabajar s´ıncronamente con el renderizado trabaje as´ıncronamente con el sistema de render. Este forma es perfecta porque si uno de los dos componentes se relantiza no impacta en el otro, aparte de separar la l´ ogica y no generar cohesi´on. Sabemos que el bucle de renderizado tiene que ir a 60 FPS porque es lo ´ optimo para refrescar la pantalla en sincronizaci´on (VSync). Pero no hay necesidad de que la f´ısica simule a esta velocidad. Gracias a los Web Workers

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

109

podemos simular la f´ısica a una frecuencia diferente que se adapte con las caracter´ısticas del cliente y sea adaptativa.

Imagen 4.5: Esquema Web Worker con la f´ısica.

De esta forma el hilo principal de la aplicaci´on solo se tiene que encargar de tratar el estado actual y pintar las posiciones de los elementos simulados en el WebWorker. ¿Pero como tratamos tanta informaci´on? Mediante las Bounding Boxes. Cada elemento de la escena tiene una Bounding Box que engloba su volumen. Este volumen ser´a pasado al WebWorker que lo a˜ nadir´ a a su sistema y simular´a a cada paso su posici´on. Esa posici´ on sera devuelta a las Bounding Boxes originales y el hilo principal solo se dedicar´a a leer esa posici´ on para pintarlo ah´ı. Si quit´aramos las f´ısicas todos los elementos estar´ıan en su posici´ on original sin ning´ un tipo de movimiento. La caja Messages representa la forma en que el Web Worker se comunica con la aplicaci´ on principal. Estos Messages son JSON, estructuras de datos de Javascript que representan objetos. As´ı que en este caso es muy f´acil representar un Array de Posiciones que es lo que necesitamos para pintar los elementos. En conclusi´on, existen dos mundos en esta aplicaci´ on el mundo del Render, que se dedica a pintar los modelos en las posiciones de sus Bounding Boxes y el mundo f´ısico, una representaci´on de esas Bounding Boxes que seg´ un su masa, fricci´on y fuerzas externas simular´a esas posiciones.

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

4.6.

110

Optimizar Pintado

Normalmente cuando queremos pintar varios elementos en una escena hay que ordenar la escena de atr´ as a adelante para conseguir una visibilidad correcta y que los elementos m´ as cercanos sean los u ´ltimos en ser pintados. Por ejemplo en Canvas 2D se usa mucho. Se ordena por profundidad (z-index) y se pinta en orden.

Imagen 4.6: Pintado L´ogico en Canvas.

Pero esta ser´ıa la peor forma de pintar en WebGL porque estar´ıamos llamando dos veces al pintado del cubo cuando podr´ıamos enviarlo solo una vez. Y es aqu´ı donde entra tambi´en el Test de Profundidad. En OpenGL es com´ un pintar de adelante a atr´ as primero ordenado por estado. Primero se ordenan los objetos de la misma forma para optimizar las llamadas de pintado en una sola y luego se ordena de lo que se ve primero a lo que ve u ´ltimo. De esta forma todos los fragmentos que no superen el test de profundiad no ser´ an tratados y por lo tanto no se ejecutar´an en el Fragment Shader. Si lo hici´eramos alrev´es como en Canvas cada forma geom´etrica que pintamos estar´ıa por delante de la anterior y todos los fragmentos se ejecutar´ıan en los shaders innecesariamente. Es la forma l´ ogica de aprovecharse del test de profundidad.

Imagen 4.7: Pintado L´ogico en WebGL.

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

111

La ordenaci´ on por estado quiere decir que se ordenen los elementos de la siguiente manera:



1o - Por FrameBuffer or por Contexto.



2o - Por Programa/Buffer/Textura



3o - Por Uniforms/Samplers

Una vez tenemos esta ordenaci´on, lo siquiente ser´ıa ordenarlos por profundidad como mostraba la imagen anterior. En el caso de tener elementos traslucidos, como es nuestro caso con el rayo l´ aser de disparo, hay que pintarlos de atr´as hacia delante para que se acumule el color transparente a cada capa que a˜ nadamos. En conclusi´on hay que seguir este orden:

Imagen 4.8: Orden de Pintado en WebGL.

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

4.7.

112

Optimizar Geometr´ıa

Hay una lista de cosas que tenemos que tener en cuenta cuando lidiamos con mucha geometr´ıa. Las recomendaciones que hago son las siguientes:



Reducir el n´ umero de V´ertices - usar buffers de ´ındices.



Reducir datos por v´ertice - M´as r´apido de enviar a GPU.



Intentar alinear los datos de los atributos a 4 bytes.



Usar los datos m´ as peque˜ nos posibles: BY T E < SHORT < F LOAT

Por ejemplo para enviar dos tri´angulos a GPU podemos hacerlo de la siguiente forma.

Imagen 4.9: Tri´angulos sin ´ındices.

De esta manera estamos enviando la informaci´on de los 6 v´ertices que pueden ser su posici´ on, sus coordenadas de textura, su material y dem´as cosas. Si usamos ´ındices podemos reusar los v´ertices que comparten arista reduciendo a 4 el n´ umero de v´ertices que tenemos que enviar y solo referenciarlos con un array de ´ındices de tipo peque˜ no:

Imagen 4.10: Tri´angulos con ´ındices.

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

113

A la hora de recorrer los Arrays de Datos para enviarlos a GPU hay dos formas t´ıpicas. Tener una serie de Arrays (Struct of Arrays) o un Array con los datos contiguos (Array of Structs). Para aprovechar la localidad espacial de los datos es mejor usar un u ´nico Array as´ı cuando la CPU cargue los datos se podr´a aprovechar de localidad espacial de los siguientes datos y aprovechar el ”cacheo“.

Imagen 4.11: Estructura de datos.

Cap´ıtulo 4. T´ecnicas, Optimizaciones y Rendimiento

4.8.

114

Optimizar Shaders Pongamos por ejemplo que queremos pintar un cubo. Hay que hacer una llamada a glDraw. Esa llamada desenvoca en 6 ejecuciones del Vertex Shader por los 6 v´ertices de un cubo. Y finalemente esos 6 v´ertices se convierten en muchos fragmentos, muchos m´as que v´ertices. As´ı que siempre tendremos menos llamadas de pintado que v´ertices y que fragmentos. L´ogicamente todo lo que computemos antes mejor. Si es posible hacer el c´alculo de matrices una vez en CPU que miles de veces en

Imagen 4.12: Pir´ amide de n´ umero de llamadas.

GPU mejor. Y normalmente muchos de los c´alculos que se hacen en el Fragment Shader se pueden pasar al Vertex Shader con interpolaci´on. En este

proyecto mucha de la l´ ogica de iluminaci´on se ha pasado al Vertex Shader por puro rendimiento, como el c´ alculo de luz difusa y ambiente. Los Shaders son c´ odigos muy delicados y deben ser tratados con detalle. Hay que intentar siempre usar la precisi´on m´ınima posible tanto en los atributos como en las variables Varying. Tambi´en es aconsejable cambiar de programas para proporcionar diferentes niveles de detalle. Hay muchos elementos en una escena que si nosotros sabemos que est´ an lejos del punto de visi´on alomejor no necesitan un c´alculo tan preciso de la luz o de Skinning. Es recomendable usar trucos matem´aticos que reduzcan los ciclos de ejecuci´ on mientras el resultado est´e bien. Unos ejemplos:



Multiplicar por dos : anum_verts; ++i) { vec3_t finalVertex = { 0.0f, 0.0f, 0.0f }; /* Calcular la posicion final del v´ ertice final con los pesos */ for (j = 0; j < mesh->vertices[i].count; ++j) { const struct md5_weight_t *weight = &mesh->weights[mesh->vertices[i].start + j]; const struct md5_joint_t *joint = &joints[weight->joint]; /* Calcular la transormaci´ on seg´ un el peso */ vec3_t wv; Quat_rotatePoint (joint->orient, weight->pos, wv); /* la suma de todos los weight->bias deber´ ıa de ser 1.0 */ finalVertex[0] += (joint->pos[0] + wv[0]) * weight->bias; finalVertex[1] += (joint->pos[1] + wv[1]) * weight->bias; finalVertex[2] += (joint->pos[2] + wv[2]) * weight->bias; } ... }

Ap´endice B. Modelos MD5

142

Fichero md5Anim Este fichero contiene las animaciones del Skeleton de los modelos md5mesh de esta forma:



Una jerarqu´ıa de Skeleton con Flags para cada Joint de los datos de la animaci´on.



Una Bounding Box para cada frame de la animaci´on.



Un Frame Base desde donde la animaci´on comienza.



Una lista de frames, cada uno conteniendo los datos para computar el Skeleton desde su Frame Base.

Lo primero que econtramos en el fichero es: numFrames numJoints frameRate numAnimatedComponents NumFrames es el n´ umero de frames de la animaci´on. Una animaci´on est´a compuesta por varios frames, en cada una una posici´on del Skeleton. Reproduciendo una detr´as de otra conseguimos una animaci´ on. NumJoitns es el n´ umero de Joints del Skeleton Frame. Tiene que ser el mismo que el n´ umero de Joints del md5mesh. FrameRate es numero de Frames por segundo de pintado de la animaci´on. NumAnimatedComponents es el n´ umero de par´ ametros por frame usados para computar el Frame Skeleton. Despu´es de esta cabecera viene la jerarqu´ıa del Skeleton. Proporciona la informaci´ on para construir los Frames del Skeleton desde el Frame Base.

hierarchy { "name"

parent flags startIndex

... }

Name es el nombre del Joint. Parent es el ´ındice del Joint padre. Si es -1 es que no tiene y startIndex es el ´ındice inicial desde donde empezar a computar el Frame Skeleton.

Ap´endice B. Modelos MD5

143

Despu´es de la jerarqu´ıa del Skeleton vienen las Bounding Boxes de cada frame de esta forma: bounds { ( min.x min.y min.z ) ( max.x max.y max.z ) ... } Representan una caja mediante dos puntos en el espacio. Las coordenadas est´an en sistema de coordenadas de modelo y son u ´tiles para computar colisiones. Despu´es de las Bounding Boxes vienen los datos del Frame Base. Contiene las posiciones y orientaciones (cuaterniones) de cada Joint desde donde el Skeleton Frame ser´a construido. Para cada Joint: baseframe { ( pos.x pos.y pos.z ) ( orient.x orient.y orient.z ) ... } No se expone el c´ odigo para generar una animaci´on porque es muy largo. Se dispone de muchas formas de hacerlo si buscamos online. Hay un claro ejemplo en esta web: http://devmaster.net/posts/loading-and-animating-md5-models-with-opengl

Ap´endice B. Modelos MD5

B.3.

144

Importaci´ on

Cuando surgieron los modelos MD5 como c´odigo libre mucha gente se dedic´o a probarlos en sus aplicaciones y tambi´en en WebGL. Hay una prueba de carga de modelos md5Mesh con animaciones md5Anims en WebGL. Hemos usado el mismo Script para cargar nuestros modelos. El c´ odigo fuente original de este c´odigo es: http://media.tojicode.com/webgl-samples/md5Mesh.html Este c´ odigo, creado por un tercero, lo que hace es exactamente leer y generar las animaciones y mallas con pesos del modelo en Javascript. Pero su rendimiento es bueno solo para un modelo. Si instanciamos cuatro o cinco modelos el rendimiento cae en picado hacia 10 FPS por modelo. O sea hace inviable que este c´odigo pueda ser usado en una aplicaci´ on que necesite varios modelos como la nuestra. Haciendo instrumentalizaci´on del c´odigo hubo una funci´on que destacaba en tiempo de ejecuci´ on, el Skinning. El proceso de asociar cada v´ertice a una posici´on seg´ un sus pesos. Como hemos visto en el c´odigo anteior es un bucle con muchas iteraciones e internamente con muchas operaciones. ¿C´omo lo solucionamos? Ten´ıa dos posibilades para probar una es pasarlo el Skinning a un WebWorker y que se hiciera en un hilo aparte o aprovechar la GPU y en el Vertex Shader hacer Skinning por V´ertice. Decid´ı probar hacer Skinning por GPU y funcion´o.

B.4.

Skinning GPU

Este desarrollo ha proporcionado la posibilidad de instanciar varios modelos MD5 a partir de una modificaci´ on de este c´odigo: http://media.tojicode.com/webgl-samples/md5Mesh.html. La modificaci´ on ha sido pasar el Skinning a la GPU. Esto quiere decir que a la hora de renderizar el modelo no vamos a enviarle la posici´on real de los v´ertices sino que vamos a enviarle la informaci´ on de los v´ertices, los pesos y las Joints para que internamente haga el c´ alculo de posici´ on en la GPU.

Ap´endice B. Modelos MD5

145

Empecemos por el c´ odigo del Script original externo. Para hacer Skinnig se hace lo mismo que lo expuesto en el c´ odigo anterior pero en Javascript.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

for ( var i = 0; i < meshes . length ; ++ i ) { var mesh = meshes [ i ]; var meshOffset = mesh . vertOffset + arrayOffset ; // Calculate transformed vertices in the bind pose for ( var j = 0; j < mesh . verts . length ; ++ j ) { var vertOffset = ( j * V ER TE X _E LE ME N TS ) + meshOffset ; var vert = mesh . verts [ j ]; vx = 0; vy = 0; vz = 0; nx = 0; ny = 0; nz = 0; tx = 0; ty = 0; tz = 0; vert . pos = [0 , 0 , 0]; for ( var k = 0; k < vert . weight . count ; ++ k ) { var weight = mesh . weights [ vert . weight . index + k ]; var joint = joints [ weight . joint ]; // Rotate position quat4 . multiplyVec3 ( joint . orient , weight . pos , rotatedPos ) ; // Translate position vx += ( joint . pos [0] + rotatedPos [0]) * weight . bias ; vy += ( joint . pos [1] + rotatedPos [1]) * weight . bias ; vz += ( joint . pos [2] + rotatedPos [2]) * weight . bias ; // Rotate Normal quat4 . multiplyVec3 ( joint . orient , weight . normal , rotatedPos ) ; nx += rotatedPos [0] * weight . bias ; ny += rotatedPos [1] * weight . bias ; nz += rotatedPos [2] * weight . bias ; // Rotate Tangent quat4 . multiplyVec3 ( joint . orient , weight . tangent , rotatedPos ) ; tx += rotatedPos [0] * weight . bias ; ty += rotatedPos [1] * weight . bias ; tz += rotatedPos [2] * weight . bias ; } // Position vertArray [ vertOffset ] = vx ; vertArray [ vertOffset +1] = vy ; vertArray [ vertOffset +2] = vz ;

Ap´endice B. Modelos MD5 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

146

// TexCoord vertArray [ vertOffset +3] = vert . texCoord [0]; vertArray [ vertOffset +4] = vert . texCoord [1]; // Normal vertArray [ vertOffset +5] = nx ; vertArray [ vertOffset +6] = ny ; vertArray [ vertOffset +7] = nz ; // Tangent vertArray [ vertOffset +8] = tx ; vertArray [ vertOffset +9] = ty ; vertArray [ vertOffset +10] = tz ; } }

´ digo Fuente B.1: Skinning por CPU Co

Como hemos dicho este c´ odigo es claramente costoso. Por cada v´ertice y por cada peso hay que hacer muchos c´ alculos cosa en que la GPU es buena en paralelizar peque˜ nos trozos de l´ ogica y m´ as si son matem´aticas. En vez de hacer un bucle en CPU usemos el paralelismo de la GPU para conseguir hacer el Skinning sin bucles. Ahora voy a exponer el Vertex Shader para hacer GPU Skinning, este c´odigo es de mi propia autor´ıa y ha proporcionado que podamos usar modelos din´amicos animados v´ıa Skinning en GPU. O sea nos hemos aprovechado del Script de carga del c´odigo externo y la parte de Skinning que era la m´ as costosa la he pasado a GPU mejorando el rendimiento.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

uniform mat4 projectionMat ; uniform mat4 modelViewMat ; uniform mat4 j o i n t s O r i e n t a t i o n [28]; uniform mat4 jointsPos [28]; attribute vec4 weights ; attribute vec4 weightsBias ; attribute mat4 weightPos ; attribute mat4 weightNormal ; attribute mat4 weightTangent ; /* Funcion para multiplicar un cuaterni´ o n por un vector */ void quatMulVec3 ( in vec4 quat , in vec3 vec , inout vec3 dest ) { float x = vec [0] , y = vec [1] , z = vec [2]; float qx = quat [0] , qy = quat [1] , qz = quat [2] , qw = quat [3];

// calculate quat * vec float ix = qw * x + qy * z - qz * y ; float iy = ( qw * y ) + ( qz * x ) - ( qx * z ) ; float iz = qw * z + qx * y - qy * x ; float iw = ( -1.0) * qx * x - qy * y - qz * z ;

Ap´endice B. Modelos MD5 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

dest [0] = ix * qw + iw * ( -1.0) * qx + iy * ( -1.0) * qz - iz * ( -1.0) * qy ; dest [1] = iy * qw + iw * ( -1.0) * qy + iz * ( -1.0) * qx - ix * ( -1.0) * qz ; dest [2] = iz * qw + iw * ( -1.0) * qz + ix * ( -1.0) * qy - iy * ( -1.0) * qx ; } /* Extraer la orientaci´ o n seg´ u n el ´ ı ndice de los Atributos */ void fjointOr ( in int idx , inout vec4 jointOr ) { int mat = int (( float ( idx ) +0.1) /4.0) ; int submat = int ( float (( idx - mat *4) ) +0.1) ; mat4 jointMatOr = j o i n t s O r i e n t a t i o n [ mat ]; if ( submat == 0) { jointOr = vec4 ( jointMatOr [0][0] , jointMatOr [0][1] , jointMatOr [0][2] , jointMatOr [0][3]) ;

41 42 43 44

} else if ( submat == 1) { jointOr = vec4 ( jointMatOr [1][0] , jointMatOr [1][1] , jointMatOr [1][2] , jointMatOr [1][3]) ;

45 46 47 48

} else if ( submat == 2) { jointOr = vec4 ( jointMatOr [2][0] , jointMatOr [2][1] , jointMatOr [2][2] , jointMatOr [2][3]) ;

49 50 51 52

} else if ( submat == 3) { jointOr = vec4 ( jointMatOr [3][0] , jointMatOr [3][1] , jointMatOr [3][2] , jointMatOr [3][3]) ;

53 54 55 56 57 58 59 60 61 62 63 64 65

} } /* Extraer la posicion seg´ u n el ı ´ ndice de los Atributos */ void fjointPos ( in int idx , inout vec4 jointPos ) { int mata = int (( float ( idx ) +0.1) /4.0) ; int submata = int ( float (( idx - mata *4) ) +0.1) ; mat4 jointMatPos = jointsPos [ mata ]; if ( submata == 0) { jointPos = vec4 ( jointMatPos [0][0] , jointMatPos [0][1] , jointMatPos [0][2] , jointMatPos [0][3]) ;

66 67 68 69

} else if ( submata == 1) { jointPos = vec4 ( jointMatPos [1][0] , jointMatPos [1][1] , jointMatPos [1][2] , jointMatPos [1][3]) ;

70 71

} else if ( submata == 2)

147

Ap´endice B. Modelos MD5 72 73

148

{ jointPos = vec4 ( jointMatPos [2][0] , jointMatPos [2][1] , jointMatPos [2][2] , jointMatPos [2][3]) ;

74 75 76 77

} else if ( submata == 3) { jointPos = vec4 ( jointMatPos [3][0] , jointMatPos [3][1] , jointMatPos [3][2] , jointMatPos [3][3]) ;

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

} } /* Misma funci´ o n que en CPU pero s´ o lo con un bucle , el de los pesos por v´ e rtice */ void skin ( inout vec3 p_pos , inout vec3 p_normal , inout vec3 p_tangent ) { // Variables Temporales int j =0 , idx = 0; float weightJoint = -1.0 , w ei g ht Jo in t Bi as = -1.0; debug = vec4 (0.0 ,0.0 ,1.0 ,1.0) ; p_pos = vec3 (0.0 ,0.0 ,0.0) ; p_normal = vec3 (0.0 ,0.0 ,0.0) ; p_tangent = vec3 (0.0 ,0.0 ,0.0) ; vec3 rotatedPos = vec3 (0.0 ,0.0 ,0.0) ; vec4 jointOr = vec4 (0.0 ,0.0 ,0.0 ,0.0) ; vec4 jointPos = vec4 (0.0 ,0.0 ,0.0 ,0.0) ; vec3 wPos = vec3 (0.0 ,0.0 ,0.0) ; vec3 wNormal = vec3 (0.0 ,0.0 ,0.0) ; vec3 wTangent = vec3 (0.0 ,0.0 ,0.0) ; int a = 0; // Un m´ a ximo de 4 pesos por v´ e rtice for ( int i = 0; i < 4; i ++) { weightJoint = float ( weights [ i ]) ; w ei gh tJ o in tB ia s

= weightsBias [ i ];

if ( weightJoint > -0.5) { fjointOr ( int ( weightJoint +0.001) , jointOr ) ; fjointPos ( int ( weightJoint +0.001) , jointPos ) ; wPos = vec3 ( weightPos [ i ][0] , weightPos [ i ][1] , weightPos [ i ][2]) ; quatMulVec3 ( jointOr , wPos , rotatedPos ) ; p_pos [0]+=( jointPos [0]+ rotatedPos [0]) * w ei gh t Jo in tB i as ; p_pos [1]+=( jointPos [1]+ rotatedPos [1]) * w ei gh t Jo in tB i as ; p_pos [2]+=( jointPos [2]+ rotatedPos [2]) * w ei gh t Jo in tB i as ; wNormal = vec3 ( weightNormal [ i ][0] , weightNormal [ i ][1] , weightNormal [ i ][2]) ;

117 118 119 120 121 122 123

quatMulVec3 ( jointOr , wNormal , rotatedPos ) ; p_normal [0]+=( rotatedPos [0]) * w ei g ht Jo i nt Bi as ; p_normal [1]+=( rotatedPos [1]) * w ei g ht Jo i nt Bi as ; p_normal [2]+=( rotatedPos [2]) * w ei g ht Jo i nt Bi as ;

Ap´endice B. Modelos MD5 124 125

149

wTangent = vec3 ( weightTangent [ i ][0] , weightTangent [ i ][1] , weightTangent [ i ][2]) ;

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148

quatMulVec3 ( jointOr , wTangent , rotatedPos ) ; p_tangent [0]+=( rotatedPos [0]) * w e ig ht Jo i nt Bi as ; p_tangent [1]+=( rotatedPos [1]) * w e ig ht Jo i nt Bi as ; p_tangent [2]+=( rotatedPos [2]) * w e ig ht Jo i nt Bi as ; } } }

void main ( void ) { // Declarar los valores de salida e ini cializar los a 0. vec3 _s_pos = vec3 (0.0 ,0.0 ,0.0) ; vec3 _s_normal = vec3 (0.0 ,0.0 ,0.0) ; vec3 _s_tangent = vec3 (0.0 ,0.0 ,0.0) ; // Hacer Skinning y conseuir los valores de pos , normal y tangente skin ( _s_pos , _s_normal , _s_tangent ) ; // Multiplicar por la model * view * proyeccion para conseguir el // punto definitivo . gl_Position = projectionMat * modelViewMat * vec4 ( _s_pos , 1.0) ;; }

´ digo Fuente B.2: Skinning por GPU Co

Con este c´ odigo hacemos lo mismo que en CPU pero de una forma totalmente paralela gracias al prop´ osito de la GPU y de forma eficiente. Toda la informaci´on que no cambia de la malla de v´ertices es enviada como atributos por v´ertice, como son los pesos , su contribuci´ on, su posici´on, normal y tangente. Estos valores siempre son los mismos para cada v´ertice, por eso se puede enviar en forma de atributo, en un Buffer Est´atico que no cambie nunca: attribute vec4 weights; attribute vec4 weightsBias; attribute mat4 weightPos; attribute mat4 weightNormal; attribute mat4 weightTangent;

Ap´endice B. Modelos MD5

150

En cambio, seg´ un el estado de la animaci´on tenemos que enviar en cada frame el Skeleton entero, sus posiciones y orientaciones. En cada frame estos valores cambian ya que la animaci´ on va sucediendo y el Skeleton, por lo tanto, se mueve. La computaci´ on del Skeleton por frame est´ a hecha en CPU porque no es costosa pero es enviada a GPU para hacer el Skinning en forma de uniforms:

uniform mat4 jointsOrientation[28]; uniform mat4 jointsPos[28];

Las restricciones que podemos ver en estas dos declaraciones de atributos y uniforms son los inconvenientes de pasarlo a GPU, que tienen un l´ımite de valores. En el formato de modelos de MD5 cada v´ertice puede tener un n´ umero ilimitado de pesos pero hay una restriccion en el n´ umero de atributos que puedes pasar a un Vertex Shader. En este caso el l´ımite es 4. O sea, he limitado que el n´ umero de pesos por v´ertice sean 4 porque caben en un vec4 y las respectivas posiciones para cada peso caben en una mat4 = vec4 ∗ 4. Para todos aquellos v´ertices que ten´ıan m´as de 4 pesos se han descartado todos aquellos pesos que conten´ıan menos influencia, menos Bias, su factor de contribuci´on. En conclusi´ on, se ha partido la l´ogica de los modelos en 2. Una parte, es el c´alculo del Skeleton Frame que se hace por CPU leyendo el md5Anim y calculando el Skeleton resultante. Y otra, el Skinning que se ha pasado a GPU. De esta forma en el bucle de render desaparece el coste de hacer Skinning y nos da la posibilidad de instanciar m´ as modelos, casi sin coste. El resultado de ello se puede comprobar en la aplicaci´on.

Bibliograf´ıa [1] Mat Buckland, Programming game ai by example, Programming Game AI by Example provides a comprehensive and practical introduction to the bread and butter AI techniques used by the game development industry (2004). [2] Douglas Crockford, Javascript: The good parts, Considered the JavaScript expert by many people in the development community (2008). [3] Randima Fernando, Gpu gems: Programming techniques, tips and tricks for realtime graphics, GPU Gems is a compilation of articles covering practical real-time graphics techniques arising from the research and practice of cutting edge developers (2004). [4] David Flanagan, Javascript: The definitive guide: Activate your web pages (definitive guides), The Definitive Guide has been the bible for JavaScript programmers (2011). [5] Jason Gregory, Game engine architecture, This book covers both the theory and practice of game engine software development (2010). [6] Eric Lengyel, Mathematics for 3d game programming and computer graphics, third edition, Mathematical concepts that a game developer needs to develop 3D computer graphics and game engines at the professional level (2011). [7] Randima Fernando y Tim Sweeney Matt Pharr, Gpu gems 2: Programming techniques for high-performance graphics and general-purpose computation, GPU Gems is a compilation of articles covering practical real-time graphics techniques arising from the research and practice of cutting edge developers (2005). R es 2.0 programming guide, Provide start to finish guidance [8] Aaftab Munshi, Opengl

for maximizing the interfaces value in a wide range of high-performance applications (2008). [9] Nehe, Rigid body physics in Javascript, http://nehe.gamedev.net/.

151

Bibliography

152

[10] Hubert Nguyen, Gpu gems 3, GPU Gems is a compilation of articles covering practical real-time graphics techniques arising from the research and practice of cutting edge developers (2007). [11] Hugh Malan y Mike Weiblen Randi J. Rost, Opengl shading language (3rd edition), Guide to writing shaders (2009). [12] Jim Sangwine, Rigid body physics in Javascript, http://www.jiglibjs.org/. [13] Dave Shreiner, Opengl programming guide: The official guide to learning opengl, versions 3.0 and 3.1, Provides definitive and comprehensive information on OpenGL and the OpenGL Utility Library (2009). [14] Giles Thomas, Creation of lessons as a way of teaching WebGL, http:// learningwebgl.com/. [15] Eric Haines y Naty Hoffman Tomas Akenine-Moller, Real-time rendering, third edition, Modern techniques used to generate synthetic three-dimensional images in a fraction of a second (2008).