scieee AI-readable full text Open interactive document viewer

Repositorio Institucional de Documentos

Abstract

En este Proyecto de Fin de Carrera se ha creado un paisaje procedural formado por varios elementos como montañas, playa, agua, un planeta y nubes. Todos ellos se han creado utilizando el ruido de Perlin para generar formas fractales. Este algoritmo se ejecuta en la GPU ya que esta ofrece capacidades operacionales de cálculo general, con todos sus núcleos trabajando en paralelo. La aplicación se ha desarrollado utilizando la API Direct3D 11, una de las más usadas en el mundo de los gráficos. Además este API contiene varias herramientas que sirven para controlar el estado de actividad de la unidad gráfica, incluyendo el tiempo que está trabajando, lo que nos ha permitido comparar el comportamiento de la CPU y la GPU. Finalmente, se ha integrado el algoritmo de ruido de Perlin en el entorno de desarrollo de videojuegos UDK para demostrar la capacidad técnica para adaptar y aplicar los conocimientos adquiridos en aplicaciones comerciales ya existentes Gallego Molina, Juan; Serón Arbeloa, Francisco José

Full text

Proyecto Fin de Carrera Generación de paisajes procedurales con Direct3D y GPU Autor D. Juan Gallego Molina Director Dr. D. Francisco José Serón Arbeloa Escuela de Ingeniería y Arquitectura 2013/2014 Gracias a todos los compañeros que he tenido durante estos años, tanto en Zaragoza como en mis aventuras en Cork y Donosti, donde me he sentido como en casa. A los profesores que me han ayudado durante mi formación, sobre todo a Paco por sus consejos. Y en especial a mi familia, sobre todo a mi hermano, que ya me avisó de los peligros de querer ser ingeniero. Generación de paisajes procedurales con Direct3D y GPU RESUMEN Las modernas unidades de procesamiento gráfico ofrecen capacidades operacionales de cálculo general. Este ámbito está cobrando importancia ya que muchos cálculos pueden derivarse a la GPU, donde la existencia de numerosos núcleos trabajando en paralelo permite obtener una mayor rapidez en la resolución de algunos problemas, tanto de índole científico como para los videojuegos. En este ámbito es donde surge este Proyecto de Fin de Carrera. En concreto se ha creado un paisaje procedural formado por varios elementos como montañas, playa, agua, un planeta y nubes. Todos ellos se han creado utilizando el ruido de Perlin para generar formas fractales. Dicho algoritmo está especialmente aconsejado para la generación de objetos naturales y además es adecuado para aprovechar la capacidad de cálculo en paralelo de las unidades de procesamiento gráfico, ya que cada vértice puede calcularse de manera individual. Además de los aspectos relacionados con la geometría de estos elementos se han tenido en cuenta otros componentes de una pipeline gráfica como son el modelo de iluminación y sombras, el texturizado de los objetos, el audio, el movimiento de la cámara y la animación de algunos elementos. La aplicación se ha desarrollado utilizando la API Direct3D 11 de Microsoft, ya que es una de las más usadas en el mundo de los gráficos y nos permite aprovechar las características de la GPU. Además este API contiene varias herramientas que sirven para controlar el estado de actividad de la unidad gráfica, incluyendo el tiempo que está trabajando, lo que nos ha permitido poner de manifiesto el beneficio de una ejecución en paralelo. Finalmente, se ha integrado el algoritmo de ruido de Perlin en el entorno de desarrollo de videojuegos UDK, Unreal Development Kit, donde se ha usado para crear un planeta fractal. De esta manera se demuestra la capacidad técnica para adaptar y aplicar los conocimientos adquiridos en aplicaciones comerciales ya existentes. 5 ÍNDICE DE CONTENIDOS RESUMEN........................................................................................................................5 ÍNDICE DE CONTENIDOS.............................................................................................7 ÍNDICE DE TABLAS.......................................................................................................8 ÍNDICE DE FIGURAS.....................................................................................................9 1. INTRODUCCIÓN.......................................................................................................13 1. 1. Contexto tecnológico..........................................................................................13 1. 2. Objetivos.............................................................................................................15 1. 3. Estructura del documento...................................................................................16 2. TRABAJO REALIZADO...........................................................................................17 2. 1. Fractales y ruido..................................................................................................17 2. 2. Direct3D..............................................................................................................22 2. 3. Creación de la escena..........................................................................................26 2. 4. Análisis de rendimiento.......................................................................................36 2. 5. Integración en UDK............................................................................................44 3. CONCLUSIONES.......................................................................................................47 3. 1. Resultados obtenidos..........................................................................................47 3. 2. Líneas futuras......................................................................................................47 3. 3. Valoración personal.............................................................................................48 4. BIBLIOGRAFÍA.........................................................................................................49 ANEXOS ANEXO A: GESTIÓN DEL PROYECTO......................................................................55 A. 1. Planificación.......................................................................................................55 A. 2. Herramientas utilizadas......................................................................................56 ANEXO B: COMPARACIÓN DE MOTORES..............................................................57 B. 1. Herramientas disponibles...................................................................................57 B. 2. Imágenes comparativas......................................................................................64 ANEXO C: LA PIPELINE GRÁFICA...........................................................................71 C. 1. Etapas.................................................................................................................71 C. 2. HLSL..................................................................................................................72 ANEXO D: LA PIPELINE DE CÁLCULO....................................................................75 D. 1. Etapas.................................................................................................................75 D. 2. Características....................................................................................................75 ANEXO E: APLICACIÓN ESTUDIADA......................................................................79 E. 1. Contexto.............................................................................................................79 E. 2. Diseño.................................................................................................................80 E. 3. Aplicación...........................................................................................................81 E. 4. Shader de cálculo................................................................................................91 E. 5. Shaders de renderizado.......................................................................................94 E. 6. Resultados...........................................................................................................96 ANEXO F: RUIDO Y FRACTALES..............................................................................97 F. 1. Ruido...................................................................................................................97 F. 2. Tipos de ruido......................................................................................................97 F. 3. Fractales..............................................................................................................98 ANEXO G: DOCUMENTACIÓN DE LA APLICACIÓN...........................................101 G. 1. Lista de clases..................................................................................................101 G. 2. Documentación de clases.................................................................................101 7 ÍNDICE DE TABLAS Tabla 1: Geometría de la escena......................................................................................34 Tabla 2: Llamada draw 1.................................................................................................36 Tabla 3: Llamada draw 2.................................................................................................37 Tabla 4: Llamadas draw 3 -12.........................................................................................37 Tabla 5: Llamada draw 13...............................................................................................37 Tabla 6: Equipos de prueba..............................................................................................38 Tabla 7: Dedicación.........................................................................................................55 Tabla 8: Herramientas utilizadas.....................................................................................56 Tabla 9: Motores. Animación..........................................................................................57 Tabla 10: Motores. Audio................................................................................................58 Tabla 11: Motores. Cinemáticas......................................................................................58 Tabla 12: Motores. Networking.......................................................................................58 Tabla 13: Motores. Editor................................................................................................59 Tabla 14: Motores. Físicas...............................................................................................59 Tabla 15: Motores. Iluminación.......................................................................................60 Tabla 16: Motores. Inteligencia Artificial........................................................................60 Tabla 17: Motores. Programación....................................................................................61 Tabla 18: Motores. Renderizado......................................................................................62 Tabla 19: Motores. Shaders y materiales.........................................................................63 Tabla 20: Motores. Terreno..............................................................................................63 Tabla 21: Motores. Otros.................................................................................................64 8 ÍNDICE DE FIGURAS Figura 1: Gradientes generados en 2 dimensiones..........................................................18 Figura 2: Malla en 2 dimensiones, n = 2.........................................................................19 Figura 3: Octavas de ruido...............................................................................................20 Figura 4: Ruido de Perlin, paso 1....................................................................................20 Figura 5: Ruido de Perlin, paso 2....................................................................................21 Figura 6: Ruido de Perlin, paso 3....................................................................................21 Figura 7: Arquitectura gráfica..........................................................................................22 Figura 8: Pipeline gráfica................................................................................................23 Figura 9: Pipeline computacional....................................................................................23 Figura 10: Threads en la GPU.........................................................................................24 Figura 11: Mip-mapping..................................................................................................25 Figura 12: Recursos en la pipeline gráfica......................................................................25 Figura 13: Recursos en la pipeline computacional..........................................................26 Figura 14: Escena con un solo fractal..............................................................................27 Figura 15: Escena con 2 fractales....................................................................................27 Figura 16: Escena con los 3 fractales..............................................................................28 Figura 17: Escena completa.............................................................................................28 Figura 18: Cálculo del fractal de la playa y las montañas...............................................29 Figura 19: Superficie del planeta fractal..........................................................................31 Figura 20: Planeta con nubes...........................................................................................31 Figura 21: Cielo...............................................................................................................32 Figura 22: Animación de los pájaros...............................................................................32 Figura 23: Paisaje 100% - 649.800 vértices....................................................................35 Figura 24: Paisaje 50% - 324.900 vértices......................................................................35 Figura 25: Paisaje 25% - 162.450 vértices......................................................................35 Figura 26: Paisaje 10% - 64.979 vértices........................................................................36 Figura 27: Escena 1.........................................................................................................40 Figura 28: Escena 2.........................................................................................................40 Figura 29: Tiempo de inicio (s).......................................................................................41 Figura 30: Tiempos en la GPU (ms)................................................................................42 Figura 31: Tiempos en la CPU (ms)................................................................................43 Figura 32: Tiempos escena 1 GPU vs CPU (ms).............................................................44 Figura 33: Ruido de Perlin en UDK................................................................................45 Figura 34: Escena UDK...................................................................................................46 Figura 35: Muestra UDK 1..............................................................................................65 Figura 36: Muestra UDK 2..............................................................................................65 Figura 37: Muestra UDK 3..............................................................................................66 Figura 38: Muestra UDK 4..............................................................................................66 Figura 39: Muestra CryEngine 1.....................................................................................67 Figura 40: Muestra CryEngine 2.....................................................................................67 Figura 41: Muestra Cryengine 3......................................................................................68 Figura 42: Muestra CryEngine 4.....................................................................................68 Figura 43: Muestra Unity 1.............................................................................................69 Figura 44: Muestra Unity 2.............................................................................................69 Figura 45: Muestra Unity 3.............................................................................................70 Figura 46: Muestra Unity 4.............................................................................................70 Figura 47: Pipeline gráfica..............................................................................................71 9 •Generación de una escena formada por elementos naturales. •Comprender el funcionamiento de la API Direct3D, la pipeline gráfica y computacional. •Implementar la creación de agua, montañas, nubes y planetas basados en algoritmos procedurales fractales y ruido de Perlin. •Comparar el rendimiento de la implementación entre CPU y GPU •Integrar parte de las funcionalidades implementadas dentro de un motor gráfico para videojuegos ya existente. 1. 3. Estructura del documento La memoria del PFC se divide en los siguientes capítulos: I. Trabajo realizado. Se describen los algoritmos fractales que se han implementado. Posteriormente se explica la estructura conceptual de Direct3D, que es la API elegida para desarrollar el trabajo. Después se pasa a describir el paisaje modelado y el algoritmo en que se basa su apariencia. Se continúa comparando el rendimiento obtenido usando una CPU y una GPU y se finaliza comentando la integración de parte de las funcionalidades desarrolladas en un motor gráfico para videojuegos ya existente. II. Conclusiones. Se comentan los resultados obtenidos, las conclusiones alcanzadas y posibles maneras de continuar en el futuro con el trabajo. III. Bibliografía. Referencias de artículos y libros utilizados. IV. Anexos. En ellos se profundiza en distintos aspectos del proyecto que, por brevedad, no pueden comentarse en el cuerpo principal de la memoria. 16 2. TRABAJO REALIZADO En esta sección se detalla el trabajo realizado a lo largo del PFC. Se comienza describiendo los fractales, sección 2.1, y se sigue con el estudio de Direct3D, API utilizada para crear nuestra aplicación, en la sección 2.2. Después se explica la creación de la escena, sección 2.3. A continuación se compara el rendimiento obtenido usando una CPU y una GPU, sección 2.4. Por último, se integra parte del trabajo realizado en el motor Unreal Engine, sección 2.5. 2. 1. Fractales y ruido 2. 1. 1. Fractales Un fractal es un patrón que se repite a diferentes escalas para describir la forma de un objeto irregular que no se podría obtener con los métodos de la geometría clásica. Éstos nos permiten, a través de algoritmos, reproducir la complejidad de elementos típicos de la naturaleza como son las montañas, las nubes o incluso planetas enteros. Aunque las ideas que existen tras estos métodos se remontan al siglo 17 en esa época los matemáticos estaban limitados a las imágenes que podían reproducir ellos mismos. Es por eso que empiezan a cobrar importancia a partir de los años 70, cuando aparecen los primeros gráficos por computador. Es en el año 1979 cuando se crea la primera animación con elementos fractales, un corto de 2 minutos llamado “Vol Libre” de Loren Carpenter [5]. No obstante, el primer trabajo formal en el que se define el concepto de los fractales tal y como los conocemos ahora se debe a Benoit Mandelbrot en su libro “The Fractal Geometry of Nature”, publicado en 1982 [6]. En ese año también se crea el primer planeta fractal mostrado en el cine en la película Star Trek II, también por Carpenter y su equipo de Pixar [7]. Desde entonces las imágenes creadas usando fractales han evolucionado junto con la tecnología, que cada vez permite alcanzar mayor realismo. Algunas de estas imágenes pueden verse en el ANEXO F: RUIDO Y FRACTALES. Es importante mencionar la aparición en 1985 del Ruido de Perlin, ideado por Ken Perlin [8]. Con él comenzaron los métodos procedurales de generación de fractales que aún hoy se siguen utilizando. En el mismo anexo citado se presenta una descripción más detallada de las funciones usadas en estos métodos. Finalmente, comentar la existencia de la primera edición del libro “Texturing & Modelling: A Procedural Approach” en 1994 [9]. Es uno de los libros de referencia sobre este tema y el que ha servido, en su tercera edición, como guía e inspiración para el trabajo realizado en este PFC. En ese mismo libro, además de tratar los problemas tradicionales encontrados al trabajar con fractales, también se presentan las técnicas más actuales y los nuevos retos aparecidos al usar unidades gráficas como herramienta de cálculo. 17 2. 1. 2. Ruido de Perlin El ruido de Perlin es una función matemática que usa interpolación entre varios gradientes precalculados para obtener un valor entre -1 y 1 que varía pseudoaleatoriamente en el espacio o en el tiempo. Además tiene la particularidad de ser coherente, es decir, el ruido cambia suavemente y no existen discontinuidades [10]. La función puede definirse en cualquier espacio, sea cual sea su número de dimensiones. El espacio de n dimensiones que elijamos se divide en una malla definida para cada punto con coordenadas enteras y a cada uno de ellos se le asigna uno de los gradientes precalculados. Dichos gradientes se generaban inicialmente de manera aleatoria con componentes entre -1.0 y 1.0, descartando aquellos cuya longitud sea superior a 1 y normalizando el resto para evitar desviaciones alineadas con alguno de los ejes del espacio. En una revisión posterior Perlin decidió dejar de generar gradientes aleatorios ya que la manera de asignarlos, comentada a continuación, otorga suficiente aleatoriedad. Desde entonces se utilizan gradientes que van desde el centro de un objeto de referencia de dimensiones unidad, como un cuadrado en 2 dimensiones o un cubo en un espacio 3D, a las aristas de dicho objeto y se normalizan, como vemos en la figura 1. La asignación de estos gradientes a cada punto de una malla de dimensión [t, t] se realiza utilizando una permutación aleatoria de números enteros de tamaño t y las coordenadas del punto, como mostramos a continuación para un espacio de dos dimensiones: Grad es una tabla de dimensión [4, 2] con los gradientes g0, g1, g2 y g3. Perm es una tabla de dimensión t con una permutación de enteros de tamaño t. P es un punto de la malla con coordenadas (x,y). El gradiente asignado a dicho punto es: Grad[modulo(4, Perm[modulo(t, y+Perm[x])])]. Para obtener el valor del ruido en cualquier punto de entrada p a la función debemos usar los 2n puntos de la malla que lo rodean, como vemos en la figura 2. 18 Figura 1: Gradientes generados en 2 dimensiones Para cada uno de ellos tendremos un gradiente asignado, en este caso g1 – g4, y un vector que va desde cada punto de la malla que rodea a p, tras lo cual obtendremos el producto escalar de cada pareja de vectores asociados al mismo punto de la malla, como se ve en el ejemplo de 2 dimensiones: d1 = producto escalar (p1, g1) d2 = producto escalar (p2, g2) d2 = producto escalar (p3, g3) d2 = producto escalar (p4, g4) Finalmente interpolamos los 2n productos escalares usando la función 6p15 – 15p14 + 10p13 para obtener el valor del ruido. En el ejemplo dado primero interpolamos en x entre d1 y d2 y entre d3 y d4. A continuación interpolamos los 2 resultados obtenidos en y para obtener el ruido final r. Elegimos esta función por ser una curva de forma “S” que crece suavemente de 0 a 1 exagerando la cercanía a ambos extremos. Además tanto su primera como su segunda derivada son 0 si las evaluamos en 0 o en 1, lo que evita que se vean discontinuidades cuando tenemos una superficie desplazada usando el ruido de Perlin y debemos calcular la normal. Este procedimiento permite generar una función de ruido con frecuencia 1 y amplitud 1. Para definir la función con frecuencia f y amplitud a debemos cambiar algunas cosas. Lo primero que modificamos es que el espacio se dividirá en una malla de dimensión [f * t, f * t] y la asignación de los gradientes será como sigue: Grad es una tabla de dimensión [4, 2] con los gradientes g0, g1, g2 y g3. Perm es una tabla de dimensión t con una permutación de enteros de tamaño t. P es un punto de la malla con coordenadas (x,y). El gradiente asignado a dicho punto es Grad[modulo(4, Perm[modulo(t, y+Perm[modulo(t, x)])])]. 19 Figura 2: Malla en 2 dimensiones, n = 2 Además usaremos como punto de entrada a la función f * p y el valor que se devolverá finalmente a * r. La función de ruido es utilizada posteriormente para generar un fractal. Para ello se suman distintas frecuencias de ruido a diferentes escalas, llamadas octavas si cada frecuencia es múltiplo de 2, como se explica con más detalle en el ANEXO F: RUIDO Y FRACTALES y se muestra en la figura 3. Además del ruido de Perlin hay otros tipos de ruido, cuya información se puede consultar en el mismo anexo que acabamos de citar [11]. 2. 1. 3. Ruido de Perlin en la GPU A continuación se explica cómo se ha implementado el ruido de Perlin en la GPU, utilizando algunas figuras explicativas y pseudocódigo [12]. Aunque se han implementado versiones en 2 y 3 dimensiones aquí solo se explica la primera ya que es sencillo añadir una o varias dimensiones adicionales. Inicialmente creamos una permutación de números enteros de tamaño 1024 y se generan los 4 gradientes que van desde el centro de un cuadrado a sus aristas. Ambos datos se guardarán en texturas, que además de permitirnos hacer accesos rápidos se encargan de hacer la operación módulo automáticamente. Cada punto p con coordenadas reales (x,y) cae dentro de una celda de la malla, la cual podemos localizar a partir de la esquina (x0, y0). De esta misma manera se puede obtener el vector que va desde dicha esquina al punto p, como se observa en la figura 4. p es el punto en el que queremos calcular el ruido de Perlin; P = floor (p); p1 = p – P; 20 Figura 4: Ruido de Perlin, paso 1 Figura 3: Octavas de ruido Usando este vector p1 obtenemos el resto de vectores que van desde las esquinas de la celda al punto p, como se muestra en la figura 5. p2 = p1 + (-1, 0); p3 = p1 + (0, -1); p4 = p1 + (-1, -1); Ahora que tenemos los 4 vectores debemos obtener los gradientes asignados a cada punto de la malla para obtener los productos escalares. Para ello necesitamos saber los índices correspondientes para indexar la tabla de gradientes, los cuales se obtienen usando la permutación de números enteros, como se ve en la figura 6. A = permutación(P.x) + P.y; B = permutación(P.x + 1) + P.y; g1 = gradiente(permutación(A)); g2 = gradiente(permutación(B)); g3 = gradiente(permutación(A+1)); g4 = gradiente(permutación(B+1)); Finalmente, se obtiene el producto escalar de cada vector con el gradiente correspondiente y se interpolan los resultados obtenidos, usando la curva 6t5 – 15t4 + 10t3 evaluada en p1, para hallar el valor final del ruido. d1 = dot(p1, g1); d2 = dot(p2, g2); d3 = dot(p3, g3); d4 = dot(p4, g4); ix1 = interpolación en x de d1 y d2; ix2 = interpolación en x de d3 y d4; ruido final = interpolación en y de ix1 y ix2; 21 Figura 5: Ruido de Perlin, paso 2 Figura 6: Ruido de Perlin, paso 3 2. 2. Direct3D La API utilizada para interaccionar con la GPU en nuestra aplicación es Direct3D 11, la cual está diseñada para poder ser accedida directamente mediante código en C/C++ [4] [13] [14] [15]. 2. 2. 1. Arquitectura y pipeline La arquitectura gráfica seguida en Windows puede verse en la figura 7. La aplicación está en el nivel más alto y es la que controla la escena: objetos 2D y 3D, animaciones, texturas, etc. Ésta interactúa principalmente con Direct3D, que convierte los datos de alto nivel a un formato que pueda ser utilizado por el user mode driver, un driver de alto nivel que no puede acceder directamente al kernel de la GPU para simplificar su funcionamiento y reducir el número de errores. Este driver se encarga de la traducción a instrucciones que puedan ser ejecutadas por la unidad de procesamiento gráfico y sus resultados se pasan al siguiente nivel, DXGI, que maneja los recursos de la GPU y se comunica directamente con la tarjeta gráfica. Este nivel está preparado para funcionar con próximas iteraciones de Direct3D, por lo que también puede accederse directamente desde la aplicación si necesitamos realizar alguna operación que aún no está implementada en la API Direct3D 11. Finalmente, el último nivel es el propio hardware. Ya que la aplicación se comunica principalmente con Direct3D el programador puede olvidarse del tipo de dispositivo gráfico que esté instalado, abstrayéndose de la complejidad del hardware disponible. Además es importante conocer la configuración de la pipeline de Direct3D, cómo fluyen los datos y se transforman en cada etapa. 22 Figura 7: Arquitectura gráfica La principal pipa es la gráfica, mostrada en la figura 8, que se encarga de transformar una escena 3D en una imagen en 2 dimensiones. Entre las etapas de la pipeline destacan 2, el “vertex shader”, que realiza operaciones sobre vértices, y el “píxel shader”, que hace operaciones sobre píxeles. Además existe una pipeline de cómputo para otras aplicaciones, completamente independiente de la pipa anterior y compuesta por una sola etapa llamada “compute shader”, como se ve en la figura 9. En esta pipeline el hardware disponible permite ejecutar operaciones con números enteros y reales, vectores de hasta 4 componentes o matrices de hasta 4x4, ya que disponemos de operadores para hacer sumas, restas, multiplicaciones y divisiones escalar x escalar, escalar x vector, escalar x matriz, vector x vector, vector x matriz y matriz x matriz. Además se pueden hacer operaciones lógicas, logaritmos en base 2 y raíces cuadradas. La pipeline computacional nos permite considerar la GPU como un conjunto de procesadores trabajando en paralelo, cada uno de ellos ejecutando un hilo de procesamiento o thread. La disposición de estos hilos, que se unen formando grupos, puede apreciarse en la figura 10, siendo los elementos rojos los grupos de threads y los azules threads concretos. 23 Figura 8: Pipeline gráfica Figura 9: Pipeline computacional Ambas pipelines se explican con más detalle en el ANEXO C: LA PIPELINE GRÁFICA y el ANEXO D: LA PIPELINE DE CÁLCULO. 2. 2. 2. Interacción con Direct 3D Los datos en memoria que utiliza la GPU, ya sea en su comunicación con la CPU o en el intercambio de información interna entre etapas, se llaman recursos y Direct3D los cataloga en 2 tipos: buffers y texturas. Los buffers son conjuntos unidimensionales de datos y se usan para guardar información sobre la geometría u otros datos necesarios en los shaders. Hay 3 tipos de buffers: •Vertex Buffers: Contienen los vértices que definen la geometría de la escena. El formato de cada vértice deber ser especificado por el desarrollador. •Index Buffers: Contienen punteros a los vértices para no repetir información en caso de que algún vértice se utilice en varios polígonos. •Constant Buffers: Contienen cualquier otro tipo de datos. Los formatos de estos datos pueden ser muy variados, existiendo tipos predefinidos, como reales o enteros, y la posibilidad de especificar alguna estructura propia. Las texturas representan conjuntos de datos estructurados en 1, 2 o 3 dimensiones. Cada elemento de una textura se conoce como texel y contiene información en uno de los muchos formatos predefinidos, siendo los que contienen información de colores los más habituales, como el formato RGBA de 128 bits. La mayoría de unidades de procesamiento gráfico contienen hardware especializado para trabajar con texturas, por lo que si nuestros datos encajan en alguno de los formatos disponibles deberíamos utilizarlas ya que su manejo será mas eficiente que si usáramos un buffer. 24 Figura 10: Threads en la GPU Con el uso de texturas también surge la idea de mip-map, distintas versiones de la misma imagen usando diferentes resoluciones. Éstas se pueden intercambiar en función del detalle que necesitemos en cada momento, usando más o menos espacio en la memoria de la GPU. Este concepto se muestra en la figura 11. Además las texturas van asociadas con un “sampler state”, un objeto que define como se realiza el acceso a la textura desde un shader. Con los tipos de recursos explicados debemos introducir el concepto de vista. Una vista representa una instancia de un recurso, es decir, la asociación de un recurso a una etapa concreta de la pipeline. La idea surge porque un mismo recurso puede ser accedido desde distintas partes de la pipa, las texturas, por ejemplo, siempre deben asociarse a la pipeline mediante una vista. Éstas pueden ser de solo lectura o de lectura y escritura. En las figuras 12 y 13 pueden verse los lugares en ambos tipos de pipeline donde pueden asociarse los recursos, con fondo rojo. 25 Figura 11: Mip-mapping Figura 12: Recursos en la pipeline gráfica Además hay unos pocos pájaros, diez, para completar el fondo. Son objetos muy sencillos, apenas 16 vértices en el mismo plano, y con posiciones distintas de las alas que se van intercambiando en el tiempo para crear la animación de vuelo, como se muestra en la figura 22. Cada pájaro tiene información de 3 posiciones, incluida la inicial, obtenidas de manera aleatoria y que marcan su camino. Sabiendo la posición actual y la próxima podemos hacer que se muevan por el paisaje en una linea recta que cambia cuando el pájaro alcanza su meta. Debido a su sencillez tampoco es necesario calcular sus normales. 2. 3. 5. Resto de los elementos de la escena Además de los objetos que se ven hay otros elementos que componen la escena y que pasamos a comentar en este apartado. •Cámara: por supuesto es necesaria una cámara para observar la escena. Es posible usar el teclado para girarla y desplazarla. También se han precalculado dos trayectorias para observar la escena completa. •Iluminación: la escena está iluminada por una luz direccional que simula la procedente de un sol lejano, además de existir una luz ambiental para evitar zonas de total oscuridad. De la misma manera que con la cámara es posible cambiar la dirección de la luz utilizando el teclado. Además en el pixel shader se usa el modelo de iluminación de Phong, que usa las 3 componentes típicas para calcular la interacción de la luz con un objeto: luz ambiental, luz difusa y luz especular. 32 Figura 21: Cielo Figura 22: Animación de los pájaros •Materiales y texturas: las montañas, la playa, diferenciada en función de las zonas secas y las mojadas, y el agua, con zonas de espuma, están formadas por materiales distintos que sirven para definir el color de cada objeto y algunos factores que indican como interaccionan con la luz. Además las montañas y la playa tienen texturas, descargadas de páginas gratuitas2 3 4, que sirven para definir el color de cada píxel y para aplicar bump mapping, modificando el valor de la normal, y obtener mayor realismo [20]. •Sombras: el sombreado de la escena se hace con un sencillo mapa de sombras que se calcula cada vez que movemos la luz [21]. Posteriormente, cuando estamos renderizando la escena y queremos ver si un pixel está iluminado o no solo hay que acceder a dicho mapa. Para evitar que queden sombras muy marcadas también se utiliza el valor en los píxeles vecinos, interpolando y suavizando así los bordes [22]. •Sonido: además de los componentes visuales se han añadido un par de sonidos que representan el mar y las gaviotas. Para esto se ha utilizado la API XAudio2 de Microsoft, usada para cargar los sonidos .wav, obtenidos de internet5, y reproducirlos en un bucle infinito mientras dure la aplicación [23]. 2. 3. 6. Detalles de la geometría Salvo los pájaros todos los objetos mostrados por pantalla se definen usando listas de triángulos. Estas constan de dos partes, una lista de vértices, que se guarda en un “vertex buffer”, y una lista de punteros a los vértices, guardados en un “index buffer”. De esta manera cada 3 índices tenemos definido un nuevo triángulo y no es necesario repetir los vértices que formen parte de varios polígonos a la vez. El caso de los pájaros es especial porque son objetos muy sencillos. En este caso solo definimos los vértices, guardados en un “vertex buffer”. Los 3 primeros vértices sirven para definir el primer triángulo y a partir de ese momento cada nuevo vértice suma otro triángulo, formado por este nuevo vértice y los 2 inmediatamente anteriores. A continuación, en la tabla 1, vamos a detallar el número de vértices, índices y triángulos de cada uno de los objetos que acabamos de mencionar. 2 http://www.texturex.com 3 http://www.blendswap.com 4 http:// www. colorburned.com 5 http:// www. soundjax.com 33 Elemento Vértices Índices Triángulos Montañas 324.900 1.942.566 647.522 Playa 324.900 1.942.566 647.522 Agua 324.900 1.942.566 647.522 Pájaro (x10) 16 0 14 Planeta 152.202 955.200 318.400 Cielo 82 480 160 Palmera 1 1.008 1.008 336 Palmera 2 1.008 1.008 336 Palmera 3 1.008 1.008 336 Palmera 4 1.008 1.008 336 Palmera 5 276 276 92 Total: 1.131.452 6.787.686 2.262.702 Tabla 1: Geometría de la escena En esta tabla podemos observar que la mayoría de polígonos se encuentran en los planos que se utilizan para crear los fractales. Esto es así porque necesitamos suficientes triángulos para añadir el detalle, ya que no subdividimos la geometría como se hace en otras aproximaciones para generar objetos procedurales [24]. Como en este proyecto se pretende simplemente usar la GPU y comprobar sus capacidades el elevado número de polígonos no importa, pero en caso de que necesitáramos obtener un framerate estable suficientemente alto, como en un videojuego, podría ser importante reducir el número de triángulos [25]. Creando un fichero .obj desde la aplicación y usando un software externo podemos reducir el número de vértices y comprobar los resultados visualmente, algo que se muestra en las figuras 23 - 26 con los planos de la montaña y la playa. En estas figuras se aprecia que es posible reducir el número de vértices y polígonos sin que se aprecien diferencias a primera vista. Solo cuando bajamos del 25% de datos originales se empiezan a hacer visibles los triángulos de la malla, aunque en función del detalle que queramos y mediante la aplicación de texturas y materiales, algo que en el ejemplo se ha evitado por sencillez, podríamos reducir más el número de polígonos y seguir teniendo un buen resultado visual. 34 35 Figura 23: Paisaje 100% - 649.800 vértices Figura 24: Paisaje 50% - 324.900 vértices Figura 25: Paisaje 25% - 162.450 vértices 2. 4. Análisis de rendimiento Una vez finalizada la aplicación se van a tomar varias medidas de tiempos que nos permitan ver el comportamiento de la CPU y la GPU en varios equipos distintos. 2. 4. 1. Definición de la escena La escena está compuesta por los elementos mencionados en el apartado anterior y, como a la hora de renderizar la geometría se usan varias llamadas “draw” distintas para ejecutar la pipeline gráfica, vamos a mostrar la siguiente información agrupada de esta manera. Para cada llamada se indicarán los polígonos dibujados y el espacio en memoria de la GPU, en bytes, que ocupan todos los datos de entrada necesarios: buffers de vértices e índices, programas shaders, resto de buffers y texturas. Las tablas 2 - 5 muestran cada llamada en el orden en que se ejecutan. •Cielo: tabla 2 Triángulos dibujados 160 Tamaño del Vertex Buffer 5.576 B Tamaño del Index Buffer 1.920 B Tamaño del Vertex Shader 15.256 B Tamaño del Pixel Shader 14.512 B Tamaño de los buffers constantes 192 B Tamaño de las texturas 66.354.240 B Total de memoria de video usada 66.391.696 B Tabla 2: Llamada draw 1 36 Figura 26: Paisaje 10% - 64.979 vértices •Planeta: tabla 3 Triángulos dibujados 318.400 Tamaño del Vertex Buffer 10.825.736 B Tamaño del Index Buffer 3.820.800 B Tamaño del Vertex Shader 19.660 B Tamaño del Pixel Shader 46.536 B Tamaño de los buffers constantes 208 B Tamaño de las texturas 4.288 B Total de memoria de video usada 14.717.228 B Tabla 3: Llamada draw 2 •Pájaro: tabla 4 Triángulos dibujados 14 Tamaño del Vertex Buffer 1088 B Tamaño del Index Buffer 0 B Tamaño del Vertex Shader 15.108 B Tamaño del Pixel Shader 14.336 B Tamaño de los buffers constantes 192 B Tamaño de las texturas 0 B Total de memoria de video usada 30.724 B Tabla 4: Llamadas draw 3 -12 •Montañas, playa, agua y palmeras: tabla 5 Triángulos dibujados 1.944.002 Tamaño del Vertex Buffer 66.572.544 B Tamaño del Index Buffer 23.328.024 B Tamaño del Vertex Shader 23.720 B Tamaño del Pixel Shader 33.992 B Tamaño de los buffers constantes 464 B Tamaño de las texturas 335.229.192 B Total de memoria de video usada 425.187.936 B Tabla 5: Llamada draw 13 37 Podemos ver que la última llamada es la que más memoria consume y, por tanto, en la que nos deberíamos fijar si queremos disminuir el espacio a ocupar, no solo en polígonos, como ya hemos mencionado anteriormente, sino también en el número de texturas y su resolución. En este caso los datos de entrada ocupan unos 400 MB, por lo que en GPUs con poca memoria podría aparecer la necesidad de mover datos entre la memoria gráfica y la principal del sistema, con el consecuente tiempo adicional. Otra posible opción sería dividir la última en varias llamadas distintas, cada una más pequeña que la original, puesto que hay objetos distintos. La pega de esta solución es que añade más operaciones en la CPU, ya que hay cambiar más veces de contexto, y esto puede ser el cuello de botella de la aplicación. 2. 4. 2. Equipos Se han utilizado 4 equipos distintos para realizar las pruebas, los tres primeros de sobremesa y uno último portátil, que vamos a definir en función de su CPU, GPU y memoria RAM, ya que son los componentes que más pueden influir en los resultados. En la tabla 6 tenemos las especificaciones de todos ellos. Equipo 1 Equipo 2 Equipo 3 Equipo 4 CPU Intel Core i7-4770 Intel Core i7-2600 AMD Phenom II X4 955 Intel Core i5-430M Núcleos 4 4 4 2 Threads 8 8 4 4 Frecuencia 3,40 GHz 3,40 GHz 3,20 GHz 2,27 GHz Caché L1 4 x 64 KB 4 x 64 KB 4 x 128 KB 2 x 64 KB Caché L2 4x 256 KB 4x 256 KB 4 x 512 KB 2 x 256 KB Caché L3 8 MB 8 MB 6 MB 3 MB GPU NVIDIA GeForce GTX 660 NVIDIA GeForce GTX 570 AMD Radeon HD 6850 ATI Mobility Radeon HD 5470 Núcleos 960 480 960 80 Frecuencia 980 MHz 732-1464 MHz gráficos-shaders 775 MHz 750 MHz Tamaño VRAM 2 GB DDR5 1,25 GB DDR5 1 GB DDR5 512 MB DDR5 Ancho de banda 144,2 GB/s 152 GB/s 128 GB/s 24,6 GB/s RAM 8 GB DDR3 6 GB DDR3 8 GB DDR3 4 GB DDR3 Ancho de banda 12.800 MB/s 12.800 MB/s 12.800 MB/s 8.500 MB/s Tabla 6: Equipos de prueba Debemos comentar la particularidad de la GPU del equipo 2, ya que en ella los procesadores de shaders funcionan a una frecuencia distinta que el resto de la unidad, en este caso el doble. 38 2. 4. 3. Pruebas Para realizar las pruebas van a recolectarse datos sobre el tiempo de inicio de la aplicación, con operaciones ejecutadas en la CPU, y de varios tiempos obtenidos de dos vistas distintas de la escena, calculadas en la GPU. Para obtener los datos de la unidad gráfica usamos la interfaz “Query” de Direct3D 11, que permite obtener información sobre el estado de la ejecución de la tarjeta gráfica [26]. Estos son los datos recogidos: •Tiempo de inicio de la aplicación en segundos (CPU). •Tiempo total de un frame en milisegundos (GPU). •Tiempo de cálculo del agua de un frame milisegundos (GPU). •Tiempo de renderizado de un frame en milisegundos (GPU). En todos los equipos se harán pruebas usando la CPU y la GPU en conjunto así como con la CPU en solitario, ya que Direct3D permite que un procesador normal simule el funcionamiento de una unidad gráfica. Aunque es posible implementar nuestro propio software de virtualización Microsoft ofrece uno en el propio SDK de DirectX, conocido como Windows Advanced Rasterization Platform (WARP), que es el que usaremos [27]. En cuanto a las imágenes escogidas, se ha elegido una vista aérea del paisaje, que podría encajar en una escena de una película o un videojuego, y otra más alejada donde se ven todos los elementos pero donde también se observan los límites del propio paisaje. Cada vista está definida por los siguientes factores, que también se obtienen usando la interfaz “Query” : •Vértices de entrada a la pipeline •Triángulos de entrada a la pipeline •Invocaciones de Vertex Shaders •Invocaciones de Píxel Shaders •Triángulos dibujados A continuación, en las figuras 27 y 28, se muestran y especifican ambas escenas: 39 Vértices de entrada: 6.787.846 Triángulos de entrada: 2.262.702 Ejecuciones de Vertex Shaders: 2.269.388 Ejecuciones de Pixel Shaders: 3.245.677 Triángulos dibujados: 1.886.400 Vértices de entrada: 6.787.846 Triángulos de entrada: 2.262.702 Ejecuciones de Vertex Shaders: 2.269.388 Ejecuciones de Pixel Shaders: 1.915.028 Triángulos dibujados: 2.262.568 40 Figura 27: Escena 1 Figura 28: Escena 2 El primer factor puede llevar a confusión ya que al detallar la geometría de la escena hemos dicho que ésta se hacía con 1.131.452 vértices, mientras que aquí tenemos 6 veces esa cantidad. Esto se debe a que la pipeline cuenta cada vértice que entra, aunque estén repetidos. De hecho el número de vértices coincide exactamente con el número de vértices de los pájaros más el número de índices del resto de objetos, ya que cada uno de ellos hace referencia a un vértice. Ambas imágenes tienen una resolución de 1280 x 720, por lo que en total hay 921.600 píxeles. No obstante en las dos vistas se hacen más invocaciones a pixel shaders de las estrictamente necesarias ya que distintos objetos pueden situarse sobre el mismo pixel, aunque finalmente solo uno se muestre. La segunda escena contiene más triángulos pero estos ocupan menos píxeles de la imagen ya que los objetos se ven más pequeños, por lo que serán necesarias menos invocaciones de píxel shaders. Por eso esperamos que los tiempos obtenidos sean menores, poniendo de manifiesto el tiempo que consume el cálculo del color de un píxel. 2. 4. 4. Tiempos de inicio En la figura 29 puede verse el tiempo en segundos necesario para iniciar la aplicación, o lo que es lo mismo, crear todos los objetos necesarios durante la ejecución de la pipeline. Se presentan los resultados cuando el dispositivo a ejecutar los comandos Direct3D es la GPU y cuando es la CPU. Observamos que iniciar el dispositivo cuando éste es el procesador principal cuesta algo más por el hecho de tener que cargar el software de virtualización WARP, que ya hemos mencionado. 2. 4. 5. Cálculo y renderizado en la GPU Podemos ver en la figura 30 los tiempos obtenidos en ambas escenas usando la GPU. Se observa como el cálculo del agua se lleva la mayor parte del tiempo del cálculo del frame, siendo algo menos en la segunda escena gracias a las medidas comentadas en el apartado 2.3.2: “no es necesario añadir el máximo detalle en zonas muy alejadas de la 41 Figura 29: Tiempo de inicio (s) información en la memoria compartida, reduciendo el número de cálculos repetidos y el tiempo de acceso a los datos necesarios. Además sería interesante traducir la implementación de nuestro ruido y los fractales al sistema de materiales de UDK, usando siempre nodos predefinidos en vez de escribir nuestro propio código. Aunque la red de nodos final sería más compleja el editor está desarrollado para trabajar con estos y genera código HLSL optimizado, a diferencia del que indiquemos directamente nosotros, que se traduce tal cual a los shaders finales. 3. 3. Valoración personal La realización de este proyecto me ha permitido aplicar muchos de los conocimientos adquiridos a lo largo de estos 5 años de carrera, suponiendo la culminación de un largo e importante periodo de mi vida, el cual me marcará durante los años que están por llegar. Además me ha otorgado la oportunidad de estudiar, comprender y trabajar con herramientas que se usan en el mundo actual, concretamente con tecnologías que se usan en el mundo de los videojuegos, donde espero poder trabajar. De especial utilidad creo que van a ser los conocimientos adquiridos sobre Direct3D y, sobretodo, las pipelines gráfica y computacional, ya que es algo más reciente y que cada vez se usa más, por lo que acabar la carrera habiendo usado éstas puede suponer una diferenciación positiva de cara al futuro. No obstante el hecho de que sean tecnologías propietarias tiene sus pegas. Aunque sean las dominantes en la situación actual del mercado y por eso es importante conocerlas y saber usarlas, la comunidad de software libre, y en concreto la de OpenGL, suele ser mucho más participativa, por lo que es más fácil encontrar documentación y ayuda ante los problemas. La parte positiva de esto es que hemos aprendido a manejarnos con la documentación de Microsoft, que, aunque sea más escueta y en ocasiones parezca escasa, es la oficial y por tanto la que se debería usar como referencia. Finalmente, ha sido interesante ver el funcionamiento de un motor de videojuegos y como sus componentes trabajan en conjunto para crear el producto final. Es una pena que los kits de desarrollo de motores conocidos y potentes que se ofrecen de manera gratuita no permitan acceder al código del motor. Aunque es algo comprensible ya que las compañías ponen muchos recursos en su desarrollo me hubiera gustado estudiar sus partes internas con más detalle. 48 4. BIBLIOGRAFÍA [1] Unity License Comparisons http://unity3d.com/unity/licenses [2] CryEngine Overview http://mycryengine.com/?conid=2 [3] Unreal Engine Features http://www.unrealengine.com/en/features/ [4]: Jason Zink, Matt Petineo, Jack Hoxley (2011). Practical Rendering & Computation with Direct3D 11. CRC Press [5] The World's First Fractal Movie - "Vol Libre" 1979 - Loren Carpenter h ttp://www.liveleak.com/view?i=b75_1371314878 [6]: Benoit B. Mandelbrot (1982). The Fractal Geometry of Nature. W. H. Freeman and Company [7] The First Completely Computer-Generated (CGI) Cinematic Image Sequence in a Feature Film http://www.historyofinformation.com/expanded.php?id=3584 [8] Making Noise http://www.noisemachine.com/talk1/ [9]: David S. Ebert, F. Kenton Musgrave, Darwyn Peachey, Ken Perlin, Steven Worley (2002). Texturing & Modelling: A Procedural Approach. Third edition. Morgan Kaufman Publishers [10] The Perlin noise math FAQ http://webstaff.itn.liu.se/~stegu/TNM022-2005/perlinnoiselinks/perlin-noise-mathfaq.html [11] Procedural Noise Categories http://physbam.stanford.edu/cs448x/old/Procedural_Noise%282f%29Categories.html [12]: Matt Pharr, Randima Fernando, Tim Sweeney (2005). GPU Gems 2: Programming Techniques for High-Performance Graphics and General-Purpose Computation. Addison-Wesley Professional [13] MSDN: Direct3D 11 Graphics http://msdn.microsoft.com/en-us/library/windows/desktop/ff476080(v=vs.85).aspx [14] MDSN: HLSL http://msdn.microsoft.com/en-us/library/windows/desktop/bb509561(v=vs.85).aspx 49 [15] Dark Secrets of Shader Development or What Your Mother Never Told You About Shaders http://developer.amd.com/wordpress/media/2012/10/Dark_Secrets_of_shader_DevMojo.pdf [16] Scape 2: Procedural Basics http://www.decarpentier.nl/scape-procedural-basics [17] DirectX 11 Skybox http://www.braynzarsoft.net/index.php?p=D3D11CUBEMAP [18] Direct3D 11: Loading Static 3D models http://www.braynzarsoft.net/index.php?p=D3D11OBJMODEL [19] DirectX 10: Blending and Alpha Blending http://takinginitiative.wordpress.com/2010/04/09/directx-10-tutorial-6-transparencyand-alpha-blending/ [20] Direct3D 11 Normal (Bump) Maps http://www.braynzarsoft.net/index.php?p=D3D11NORMMAP [21] DirectX10: Shadow Mapping http://takinginitiative.wordpress.com/2011/05/15/directx10-tutorial-10-shadowmapping/ [22]: Randima Fernando, David Kirk (2004). GPU Gems: Programming Techniques, Tips and Tricks for Real-Time Graphics. Addison-Wesley Professional [23] MSDN: XAudio2 APIs http://msdn.microsoft.com/en-us/library/hh405049(v=vs.85).aspx [24] Procedural Shape Synthesis on Subdivision Surfaces http://lvelho.impa.br/spd/spd.pdf [25] Beautiful, Yet Friendly Part 1: Stop Hitting the Bottleneck http://www.ericchadwick.com/examples/provost/byf1.html [26] MSDN: ID3DQuery interface h ttp://msdn.microsoft.com/en-us/library/windows/desktop/ff476578(v=vs.85).aspx [27] MSDN: Windows Advanced Rasterization Platform (WARP) Guide http://msdn.microsoft.com/en-us/library/windows/desktop/gg615082(v=vs.85).aspx [28] Material Editor User Guide http://udn.epicgames.com/Three/MaterialEditorUserGuide.html [29] Materials Compendium h ttp://udn.epicgames.com/Three/MaterialsCompendium.html 50 II ANEXOS ANEXO A: GESTIÓN DEL PROYECTO En este anexo se explican la metodología y planificación seguidas para el desarrollo del proyecto y las herramientas utilizadas. A. 1. Planificación El desarrollo se ha dividido en 5 fases distintas. La primera ha sido el estudio de las herramientas y técnicas que íbamos a usar. El siguiente paso ha sido implementar la creación del paisaje fractal usando C++ y el entorno de desarrollo Visual Studio 2013. Se ha seguido un desarrollo iterativo e incremental que ha permitido partir de una idea básica de lo que queríamos hacer, el paisaje, para ir refinando el objetivo conforme avanzábamos y añadíamos características. Además de esta manera podemos modificar los elementos ya existentes conforme agregamos nuevos. El límite de los detalles adicionales implementados viene marcado por el tiempo disponible, ya que también debíamos finalizar el resto del proyecto antes de la fecha límite marcada y podríamos haber seguido añadiendo más elementos a la escena. Posteriormente se ha estudiado el desempeño de la aplicación en la CPU y la GPU, analizando el rendimiento en ambos casos. A continuación se ha implementado el ruido de Perlin en el entorno UDK para poder crear un fractal. También hemos seguido un desarrollo iterativo e incremental, aunque con pocas iteraciones y más cortas que en la creación de la escena principal. Por último se ha escrito la memoria y preparado la presentación del proyecto. En la tabla 7 pueden verse las distintas tareas planificadas, su duración y el tiempo en horas dedicado para su finalización. Descripción Fecha inicio Fecha fin Dedicación (h) Estudio de técnicas y herramientas 1/09/2013 8/10/2013 100 Creación de la escena 9/10/2013 16/02/2014 195 Análisis de rendimiento 3/02/2014 10/02/2014 35 Integración en UDK 24/01/2014 5/02/2014 30 Escribir memoria y anexos 1/10/2013 19/02/2014 90 Preparar presentación 21/02/2014 10/03/2014 30 Total 1/09/2013 10/03/2014 480 Tabla 7: Dedicación 55 A. 2. Herramientas utilizadas A continuación, en la tabla 8, se indican las herramientas utilizadas en el desarrollo del proyecto junto con una breve descripción. Herramienta Descripción Sistema Operativo Windows 7 Sistema operativo utilizado en 3 de los equipos de prueba. Windows 8 Sistema operativo de un equipo de prueba. Desarrollo Visual Studio 2013 IDE para escribir el código de la aplicación principal, los shaders y su depuración. Unreal Development Kit (Julio 2013) Entorno de desarrollo del motor Unreal para integrar el ruido en una escena. DirectX SDK (Junio 2010) Bibliotecas para trabajar con DirectX. Gráficos CrazyBump 1.2 Herramienta para generar normal maps a partir de una imagen. 3DS Max 2014 Software de creación de gráficos, usado para leer la malla del paisaje y modificar su resolución. Vue xStream 9.5 Aplicación para generar las texturas del cubo del cielo. Microsoft DirectX Texture Tool Herramienta para agrupar las texturas del cielo en un solo archivo. Paint Edición de imágenes. Gestión Google Drive Copias de seguridad, control de cambios y sincronización entre ordenadores. Documentación LibreOffice 4.2.0 Writer Memoria del proyecto y anexos. LibreOffice 4.2.0 Calc Análisis de rendimiento. LibreOffice 4.2.0 Impress Presentación del PFC. LibreOffice 4.2.0 Draw Creación de imágenes DoxyGen 1.8.6 Documentación de la aplicación. Otros FRAPS 3.5.99 Captura de imágenes y video. Control de framerate. Tabla 8: Herramientas utilizadas 56 ANEXO B: COMPARACIÓN DE MOTORES En este anexo se realiza una comparación entre varios motores gráficos de videojuegos, potentes y disponibles para PC de manera gratuita. Aunque existen muchas opciones diferentes solo nos vamos a centrar en 3 de los motores más famosos y utilizados: Unreal Development Kit, CryEngine 3 y la versión gratuita de Unity. B. 1. Herramientas disponibles En este apartado se van a comparar las distintas herramientas que ofrecen los motores y sus diferentes características. Para ello éstas serán listadas y se presentará una tabla que permite hacer la comparación de manera rápida y sencilla. B. 1. 1. Animación 1. Animación basada en esqueletos. 2. Huesos que pueden influenciar cada vértice. 3. Asociación de objetos externos con alguna parte del esqueleto. 4. Transición entre animaciones distintas. 5. Cinemática inversa. 6. Animaciones grupales. 7. Animación morph-target. 8. Animación facial. Característica UDK CryEngine 3 Unity 1✔✔✔ 2444 3✔✔✔ 4✔✔✔ 5✔ ✔ Χ 6✔ ✔ Χ 7✔ ✔ Χ 8✔ ✔ Χ Tabla 9: Motores. Animación 57 B. 1. 13. Otros 1. Distribución de tareas entre múltiples núcleos. 2. Visual scripting. 3. Creación de menús e interfaces. 4. Documentación disponible. 5. Plataformas soportadas. 6. Editor de vehículos. 7. 3D estereoscópico. Característica UDK CryEngine 3 Unity 1✔✔✔ 2✔ ✔ Χ 3✔Χ✔ 4✔✔ ✔ ✔✔✔ 5 Windows y IOs Windows, Xbox 360 y PS3 Windows, Linux, Mac, Web player, Xbox 360, PS3, WiiU, IOs y Android 6 Χ ✔Χ 7✔ ✔ Χ Tabla 21: Motores. Otros B. 2. Imágenes comparativas A continuación, en las figuras 35 - 46, se presentan algunas imágenes de juegos comerciales o demostraciones técnicas creadas con los motores comparados para así poder comprobar la calidad gráfica que se puede obtener con ellos y ver en funcionamiento las características comentadas. 64 B. 2. 1. Unreal Development Kit 65 Figura 35: Muestra UDK 1 Figura 36: Muestra UDK 2 66 Figura 38: Muestra UDK 4 Figura 37: Muestra UDK 3 B. 2. 2. CryEngine 3 67 Figura 39: Muestra CryEngine 1 Figura 40: Muestra CryEngine 2 68 Figura 41: Muestra Cryengine 3 Figura 42: Muestra CryEngine 4 B. 2. 3. Unity 69 Figura 43: Muestra Unity 1 Figura 44: Muestra Unity 2 70 Figura 45: Muestra Unity 3 Figura 46: Muestra Unity 4 ANEXO C: LA PIPELINE GRÁFICA En este apartado se explica el funcionamiento de la pipeline gráfica de Directc3D 11 y las características del lenguaje que se usa para la programación de sus etapas: HLSL. C. 1. Etapas En la figura 47 podemos observar la pipeline gráfica de Direct3D 11, siendo las etapas enmarcadas en rectángulos las programables y donde, por tanto, podemos ejecutar nuestro propio código. Las otras, aunque más o menos configurables, harán siempre las mismas operaciones y por ello ofrecen una flexibilidad más limitada. Además hay que considerar que las etapas de teselado, hull shader, tesselator y domain shader, siempre las 3 juntas, son opcionales y pueden utilizarse según nos convenga en la aplicación. Lo mismo ocurre con el geometry shader y el stream output. A continuación se realiza un pequeño resumen de las etapas de la pipeline, haciendo hincapié en las programables, mostrando sobre qué trabajan y algunos ejemplos de para qué pueden utilizarse. C. 1. 1. Input Assembler La primera etapa es la encargada de construir los vértices que se utilizarán en el resto de la pipeline, así como de las uniones entre ellos. C. 1. 2. Vertex Shader (programable) Se ejecuta una vez por cada vértice de la escena y trabaja sobre cada uno de ellos de manera aislada. Entre las operaciones más típicas que realiza está el transformar el sistema de coordenadas del vértice o calcular alguna iluminación sencilla. C. 1. 3. Hull Shader (programable) Ésta es la primera de las etapas de teselado que a partir de los vértices iniciales, considerados puntos de control, consigue generar otros nuevos. Se divide en dos funciones distintas, la primera es la encargada de configurar la siguiente etapa y la otra se encarga de hacer cualquier modificación que queramos a dichos puntos. 71 Figura 47: Pipeline gráfica C. 1. 4. Tesselator Crea una serie de coordenadas en función de la configuración que hayamos indicado en el paso previo, independientemente de la posición o cualquier otro atributo de los puntos de control. C. 1. 5. Domain Shader (programable) Utilizando los puntos de control y las coordenadas generadas en la anterior etapa y con el algoritmo que el programador implemente se crearán los nuevos vértices para ser pasados al resto de la pipeline. C. 1. 6. Geometry Shader (programable) En este shader podemos modificar la geometría, vértices y sus conexiones, cambiando sus atributos o incluso añadiendo nuevos puntos. Además tiene otros usos como seleccionar qué parte de la escena no queremos que continúe a las siguientes etapas y así reducir el número de operaciones. C. 1. 7. Stream Output El único objetivo de esta etapa es guardar el resultado obtenido hasta ahora en un buffer externo para su futuro uso o inspección. Realmente la etapa no realiza ningún tipo de operación puesto que la copia se hace durante la anterior, el geometry shader o el vertex shader si el primero está desactivado. C. 1. 8. Rasterizer En esta etapa la geometría se convierte a un formato adecuado para poder ser presentado en pantalla, es decir, los triángulos se convierten en píxeles. Aquí también se elimina la parte de la escena que no debe ser mostrada en pantalla en función del tamaño de la cámara, su posición o qué parte de los objetos no se verán por no mirar a ésta. C. 1. 9. Pixel Shader (programable) El último shader programable es invocado una vez por cada pixel y se encarga de calcular su color final. Por ello es en esta etapa donde se realizan operaciones como aplicar materiales o texturas. También guarda información de la profundidad de los píxeles para poder comprobar a continuación cuales se corresponden con los objetos que están ocultos y cuales deben ser presentados. C. 1. 10. Output Merger Finalmente se combina la información de color y profundidad y se calcula el color final que tendrá cada píxel. Estos colores serán los que se guarden y los que se obtengan como salida final de la pipeline. C. 2. HLSL High Level Shading Language, HLSL, es un lenguaje derivado de C++ pero con una serie de características particulares para la programación de tarjetas gráficas, como el soporte para vectores o matrices y la inclusión de operaciones típicas con ellos, aunque carece de otras como el uso de punteros o la reserva dinámica de memoria. 72 Es utilizado en todas las etapas programables de la pipeline, permitiendo la abstracción del hardware que haya debajo. El código escrito, que implementará el algoritmo deseado, será compilado y asociado a la parte de la pipeline correspondiente. Un shader puede utilizar variables de tipo entero, real y booleano, que también pueden ser usados en vectores de hasta 4 variables, matrices de hasta 4x4 y estructuras definidas por el usuario. Además es capaz de acceder a variables de sistema, muchas generadas automáticamente, como la posición de un píxel, y que pueden ser usadas para compartir información entre etapas o simplemente para calcular algo en nuestro algoritmo. También es posible acceder a distintos buffers que contienen información intercambiada con la CPU o con otras partes de la pipeline, como podría ser una textura o un mapa de alturas. 73 ΔVij =Δt∗ Σ kl ∈nij (Qij →kl t+Qij →kl t+Δt 2) Finalmente, calcular el cambio de altura usando el área A de la columna de agua es trivial ya que Hij=Vij Aij E. 2. Diseño E. 2. 1. Datos Viendo las consideraciones anteriores llegamos a la conclusión de que para cada columna debemos mantener dos tipos de información: su altura y el flujo de las tuberías. Como cada columna tiene 8 conexiones pero hay información compartida nos basta con utilizar 4 datos, la tubería de la derecha y las de abajo, como se muestra en la figura 49. Tenemos en total 5 datos de tipo real por columna que serán guardados en un buffer constante estructurado y que contendrá el estado de la simulación. No obstante no nos basta con usar uno puesto que el estado actual debe ser mantenido hasta haber completado el cálculo del nuevo, así que usaremos 2 que irán intercambiando su papel. E. 2. 2. Threading Observando la figura 49 es fácil pensar que cada columna puede asociarse con un thread, que a su vez serán organizados en grupos cuadrados, en este caso de dimensión 16x16. Puesto que los threads vecinos comparten información es posible usar la memoria compartida de grupo para almacenar estos datos y mejorar el acceso a ellos. El problema aparece en los threads del borde del grupo, que deben comunicarse con otros que están fuera del grupo y, por tanto, fuera de la memoria compartida. Para evitar tener que escribir y leer todos los datos en la memoria de la GPU en cada pasada añadimos una fila al perímetro del grupo, quedando 18x18, que se solapa con los grupos adyacentes. Estos threads cargarán las alturas correspondientes y realizarán los cálculos de flujo, que si podrán ser guardados en memoria de grupo, pero no actualizarán el estado de la simulación, ya que de eso se encargarán los threads correspondientes de los grupos vecinos. En resumen, para evitar tener que hacer más accesos a memoria lenta repetimos algunos cálculos en distintos grupos, que suponen una penalización menor. 80 Figura 49: Conexión de columnas de agua E. 3. Aplicación Con la teoría y el diseño en mente solo queda la implementación. Para empezar, analizaremos la aplicación. La original utilizada en el ejemplo del libro usa una serie de librerías creadas por los autores que no permiten ver las llamadas a Direct3D claramente, por ello se ha escrito una nueva donde se pueden apreciar. // Incluimos los archivos necesarios #include <windows.h> #include <d3d11.h> #include <d3dcompiler.h> #include <directxmath.h> // Incluimos las librerias necesarias #pragma comment (lib, "d3d11.lib") #pragma comment (lib, "d3dcompiler.lib") using namespace DirectX; // Variables globales HWND hWnd; // Controlador de la ventana D3D11_VIEWPORT viewport; // Vista IDXGISwapChain *swapchain = NULL; // Swap chain ID3D11Device *dev = NULL; // Direct3D device interface ID3D11DeviceContext *devcon = NULL; // Direct3D device context ID3D11RenderTargetView *rtv = NULL; // Render Target View ID3D11VertexShader *pVS = NULL; // Vertex Shader ID3D11PixelShader *pPS = NULL; // Pixel Shader ID3D11ComputeShader *pCS = NULL; // Compute Shader ID3D11Buffer *pVBuffer = NULL; // Buffer de los vértices ID3D11Buffer *pIBuffer = NULL; // Buffer de índices ID3D11InputLayout *pLayout = NULL; // Formato de entrada a la pipeline ID3D11RasterizerState *pRState=NULL; // Configuración de la etapa de rasterizado ID3D11Buffer *pCBufferMat = NULL; // Constant buffer de las matrices de transformacion ID3D11Buffer *pCBufferDis = NULL; // Buffer con la info de threads utilizados ID3D11Buffer *pCBufferDisTime = NULL; // Buffer con la info de threads y de tiempo ID3D11Buffer *pWaterSim1 = NULL; // Buffer para guardar el estado actual de la simulación ID3D11Buffer *pWaterSim2 = NULL; // Buffer para guardar el próximo estado de la simulación ID3D11ShaderResourceView *pSRVWater= NULL; // Vista para leer la información de la simulación ID3D11UnorderedAccessView *pUAVWater= NULL;// Vista para leer/escribir la información de la simulación int numVertices = 0; // Número de vertices a dibujar int numIndices = 0; // Número de indices XMMATRIX myWorld; // Matriz del mundo XMMATRIX myView; // Matriz de la vista XMMATRIX myProjection; // Matriz de proyección int ThreadGroupsX = 16; // Número de thread groups, x int ThreadGroupsY = 16; // Número de thread groups, y const int DispatchSizeX = 16; // Número de threads por grupo, x const int DispatchSizeZ = 16; // Numeo de threads por grupo, Z int SizeX= ThreadGroupsX*DispatchSizeX; // Número de vértices en X 81 int SizeZ= ThreadGroupsY*DispatchSizeZ; // Número de vértices en Z LARGE_INTEGER tiempo; // Tiempo transcurrido desde la última actualización de la simulacion LARGE_INTEGER freq; // Frecuencia de la CPU, usada para calcular el tiempo // Estructuras a usar // Vértice struct VERTEX { float x, y, z; // Posición }; // Información de un punto de la simulación struct GridPoint { float height; float flow[4]; }; // Buffer constante de las matrices de transformación struct ConstantBufferMatrix { XMMATRIX World; XMMATRIX View; XMMATRIX Projection; }; // Buffer constante con la información de threads struct ConstantBufferDis { float DispatchSize[4]; }; // Buffer constante con al información de threads y tiempo struct ConstantBufferDisTime { float TimeFactor; float padding[3]; // Los buffers constantes deben estar alineados a 16bytes, así que rellenamos float DispatchSize[4]; }; // Declaración de las funciones a usar HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow ); HRESULT InitDevice(); void InitPipeline(); void InitScene(); void InitSim(); void InitCamera(); LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); void Calculate(); void Render(); void CleanD3D(); // Punto de entrada a la aplicación int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { 82 // Creación de la ventana if( FAILED (InitWindow(hInstance,nCmdShow)) ) return 0; // Creación del dispositivo y swap chain if( FAILED (InitDevice()) ) return 0; // Inicializar la pipeline InitPipeline(); // Inicializar la escena InitScene(); // Iniciar la simulación InitSim(); // Inicializar la cámara InitCamera(); // Inicio de la información del timer QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&tiempo); // Bucle principal MSG msg; // Mensaje de los eventos de Windows while(TRUE) { // Comprobar si hay mensajes esperando if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // Traducir al formato de Windows TranslateMessage(&msg); // Mandar mensaje a WindowProc para procesarlo DispatchMessage(&msg); // Comprobar si hay que salir de la aplicación if(msg.message == WM_QUIT) break; } // Calcular un paso de la simulacion Calculate(); // Renderizar la escena Render(); } // Liberar recursos CleanD3D(); // Devolver a Windows la siguiente info return msg.wParam; } // Función para iniciar la ventana HRESULT InitWindow( HINSTANCE hInstance, int nCmdShow ) { WNDCLASSEX ventana; // Clase de la ventana ZeroMemory(&ventana, sizeof(WNDCLASSEX)); // Limpiamos la variable ventana 83 // Llenamos la clase con la info correspondiente ventana.cbSize = sizeof(WNDCLASSEX); // Tamaño de la clase ventana.style = CS_HREDRAW | CS_VREDRAW; // Estilo ventana.lpfnWndProc = WindowProc; // Puntero al controlador de eventos ventana.hInstance = hInstance; // Instancia de la aplicación con la ventana ventana.hCursor = LoadCursor(NULL, IDC_ARROW); // Cursor ventana.hbrBackground = (HBRUSH)COLOR_WINDOW; // Color del fondo ventana.lpszClassName = L"ClaseVentana"; // Nombre de la clase // Registramos la clase if (! RegisterClassEx(&ventana)) return E_FAIL; // Creamos una ventana y obtenemos el controlador RECT wr = {0, 0, 800, 600}; // Indicamos el tamaño de la zona de la ventana disponible para nuestra aplicación AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); // Obtenemos el tamaño completo de la ventana hWnd = CreateWindowEx(NULL, // Estilo L"ClaseVentana", // Nombre de la clase ventana L"Agua-Columnas", // Titulo a mostrar WS_OVERLAPPEDWINDOW, // Estilo 100, // Posicion x de la ventana 100, // Posicion y de la ventana wr.right - wr.left, // Ancho wr.bottomwr.top, // Altura NULL, // Ventana padre, NULL NULL, // Uso de menus, NULL hInstance, // Instancia de la aplicación con la ventana NULL); // Multiples ventanas, NULL if (!hWnd) return E_FAIL; // Mostrar ventana por pantalla ShowWindow(hWnd, nCmdShow); return S_OK; } // Función para crear el dispositivo y la swapchain HRESULT InitDevice() { // Información de la swap chain DXGI_SWAP_CHAIN_DESC scd; ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC)); scd.BufferCount = 1; // 1 back buffer scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // Usar colores de 32 bit scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // Modo de uso: salida de la pipeline scd.OutputWindow = hWnd; // Ventana a usar scd.SampleDesc.Count = 1; // Multisamples scd.Windowed = TRUE; // Modo ventana/!pantalla completa 84 // Crear device, device context y swap chain HRESULT hr = D3D11CreateDeviceAndSwapChain(NULL, // Adaptador gráfico a usar (GPU). Se elige automáticamente D3D_DRIVER_TYPE_HARDWARE,// Tipo de driver a usar (software-hardware) NULL, // Puntero al código del driver si este es software NULL, // Flags de configuración NULL, // Feature levels. Distintas aracterísticas que pueden estar disponibles en el hardware NULL, // Número de feature levels D3D11_SDK_VERSION, // Versión de DirectX con la que vamos a desarrollar la aplicación &scd, // Info de la swap chain &swapchain, // Swap chain &dev, // Device NULL, // Máximo feature level disponible en el equipo actual &devcon); // Device context if( FAILED(hr)) return hr; // Crear render target view // Necesitamos la dirección del back buffer ID3D11Texture2D *pBackBuffer; swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer); // Usamos esta dirección para crear el render target dev->CreateRenderTargetView(pBackBuffer, NULL, &rtv); pBackBuffer->Release(); // Ya no necesitamos la textura // Configuramos la vista ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT)); viewport.TopLeftX = 0; viewport.TopLeftY = 0; viewport.Width = 800; viewport.Height = 600; return S_OK; } // Funcion para inicializar la pipeline void InitPipeline() { // Compilar y cargar los shaders ID3DBlob *VS, *PS, *CS; D3DCompileFromFile(L"Visualizacion.hlsl", NULL, NULL, "VSMAIN", "vs_5_0", D3DCOMPILE_DEBUG, 0, &VS, NULL); D3DCompileFromFile(L"Visualizacion.hlsl", NULL, NULL, "PSMAIN", "ps_5_0", D3DCOMPILE_DEBUG, 0, &PS, NULL); D3DCompileFromFile(L"AguaColumnas.hlsl", NULL, NULL, "CSMAIN", "cs_5_0", D3DCOMPILE_DEBUG, 0, &CS, NULL); // Encapsular los objetos shaders dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS); dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS); 85 dev->CreateComputeShader(CS->GetBufferPointer(), CS->GetBufferSize(), NULL, &pCS); // Definir la estructura del input layout D3D11_INPUT_ELEMENT_DESC ied[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, }; // Crear el objeto input layout dev->CreateInputLayout(ied, ARRAYSIZE(ied), VS->GetBufferPointer(), VS- >GetBufferSize(), &pLayout); // Configuramos el rasterizado D3D11_RASTERIZER_DESC rs; ZeroMemory(&rs, sizeof(rs)); rs.FillMode = D3D11_FILL_WIREFRAME; // Modo malla rs.CullMode = D3D11_CULL_BACK; // No mostrar las caras traseras dev->CreateRasterizerState( &rs, &pRState); } // Función para inicializar los objetos de la escena void InitScene() { numVertices=SizeX*SizeZ; // Número de vértices numIndices=6*(SizeX-1)*(SizeZ-1); // Número de índices // Creamos un plano de triángulos VERTEX *OurVertices; OurVertices= new VERTEX[numVertices]; WORD *indices; indices = new WORD[numIndices]; // Generar vértices for ( int y = 0; y < SizeZ; y++ ) { for ( int x = 0; x < SizeZ; x++ ) { OurVertices[y*SizeX+x].x = (float)x; OurVertices[y*SizeX+x].y = 0.0f; OurVertices[y*SizeX+x].z = (float)y; } } // Creamos el buffer de vértices D3D11_BUFFER_DESC bd; ZeroMemory(&bd, sizeof(bd)); bd.Usage = D3D11_USAGE_DEFAULT; // Modo de uso. CPU -/- y GPU Read/Write bd.ByteWidth = sizeof(VERTEX) *numVertices; // Tamaño bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // Donde puede asociarse en la pipeline bd.CPUAccessFlags = 0; // Permiso de escritura de la CPU D3D11_SUBRESOURCE_DATA InitData; // Datos iniciales ZeroMemory( &InitData, sizeof(InitData) ); InitData.pSysMem = OurVertices; dev->CreateBuffer( &bd, &InitData, &pVBuffer); // Crear el buffer 86 // Generar índices for ( int j = 0; j < SizeZ-1; j++ ) { for ( int i = 0; i < SizeX-1; i++ ) { indices[j*6*(SizeX-1)+i*6] = j*SizeX + i; indices[j*6*(SizeX-1)+i*6+1] = (j*SizeX + i) + SizeX; indices[j*6*(SizeX-1)+i*6+2] = (j*SizeX + i) + 1; indices[j*6*(SizeX-1)+i*6+3] = (j*SizeX + i) + 1; indices[j*6*(SizeX-1)+i*6+4] = (j*SizeX + i) + SizeX; indices[j*6*(SizeX-1)+i*6+5] = (j*SizeX + i) + SizeX + 1; } } // Crear el index buffer ZeroMemory(&bd, sizeof(bd)); bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof( WORD ) * numIndices; // Tamaño bd.BindFlags = D3D11_BIND_INDEX_BUFFER; bd.CPUAccessFlags = 0; InitData.pSysMem = indices; // Datos iniciales dev->CreateBuffer( &bd, &InitData, &pIBuffer ); // Crear el buffer } // Función para inicializar la simulación void InitSim() { // Creamos datos iniciales para la simulación GridPoint* pData; pData = new GridPoint[numVertices]; for (int j = 0; j < SizeZ; j++) { for (int i = 0; i < SizeX; i++) { int x = i - 32; int y = j - 96; const float fFrequency = 0.1f; if (x*x + y*y != 0.0f) { pData[SizeX*j + i].height = 40.0f * sinf(sqrt((float)(x*x + y*y)) * fFrequency) / (sqrt((float)(x*x + y*y)) * fFrequency); pData[SizeX*j + i].flow[0] = 0.0f; pData[SizeX*j + i].flow[1] = 0.0f; pData[SizeX*j + i].flow[2] = 0.0f; pData[SizeX*j + i].flow[3] = 0.0f; } else { pData[SizeX*j + i].height = 40.0f; pData[SizeX*j + i].flow[0] = 0.0f; pData[SizeX*j + i].flow[1] = 0.0f; pData[SizeX*j + i].flow[2] = 0.0f; pData[SizeX*j + i].flow[3] = 0.0f; } } 87 } // Datos iniciales D3D11_SUBRESOURCE_DATA InitialData; InitialData.pSysMem = pData; // Creamos los buffer que contendrán el estado de la simulación D3D11_BUFFER_DESC bd; ZeroMemory(&bd, sizeof(bd)); bd.ByteWidth = numVertices*sizeof(GridPoint); bd.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; bd.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; bd.StructureByteStride = sizeof(GridPoint); bd.Usage = D3D11_USAGE_DEFAULT; bd.CPUAccessFlags = 0; dev->CreateBuffer(&bd, &InitialData, &pWaterSim1); dev->CreateBuffer(&bd, &InitialData, &pWaterSim2); // Ya no necesitamos la memoria de la CPU, pues la simulación es manejada por la GPU delete[] pData; // Crear el constant buffer con la info de los threads usados ZeroMemory(&bd, sizeof(bd)); bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(ConstantBufferDis); bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; bd.CPUAccessFlags = 0; dev->CreateBuffer(&bd, NULL, &pCBufferDis); // Actualizar los datos del buffer ConstantBufferDis cd; cd.DispatchSize[0] = float(DispatchSizeX); cd.DispatchSize[1] = float(DispatchSizeZ); cd.DispatchSize[2] = float(DispatchSizeX*ThreadGroupsX); cd.DispatchSize[3] = float(DispatchSizeZ*ThreadGroupsY); devcon->UpdateSubresource(pCBufferDis, 0, NULL, &cd, 0, 0); // Crear el constant buffer para el tiempo y la información de threads ZeroMemory(&bd, sizeof(bd)); bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(ConstantBufferDisTime); bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; bd.CPUAccessFlags = 0; dev->CreateBuffer(&bd, NULL, &pCBufferDisTime); } // Función para iniciar la cámara void InitCamera() { // Inicializar la matriz del mundo myWorld = XMMatrixIdentity(); // Inicialziar la vista XMVECTOR Eye = XMVectorSet(-10.0f, 30.0f, 3.0f, 0.0f); XMVECTOR At = XMVectorSet(30.0f, 2.0f, 30.0f, 0.0f); XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); myView = XMMatrixLookAtLH(Eye, At, Up); // Inicializar la proyeccion 88 myProjection = XMMatrixPerspectiveFovLH(XM_PIDIV4, 800.0f / 600.0f, 0.01f, 100.0f); // Crear el constant buffer para las matrices D3D11_BUFFER_DESC bd; ZeroMemory(&bd, sizeof(bd)); bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(ConstantBufferMatrix); bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; bd.CPUAccessFlags = 0; dev->CreateBuffer(&bd, NULL, &pCBufferMat); // Actualizar buffer ConstantBufferMatrix cb; cb.World = XMMatrixTranspose(myWorld); cb.View = XMMatrixTranspose(myView); cb.Projection = XMMatrixTranspose(myProjection); devcon->UpdateSubresource(pCBufferMat, 0, NULL, &cb, 0, 0); } // Función para controlar eventos de windows LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // Tratamos cada tipo de evento de una manera switch (message) { // Evento cerrar ventana case WM_DESTROY: // Cerrar la aplicación PostQuitMessage(0); return 0; break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } // Funcion para calcular un paso de la simulación void Calculate() { // Información del tiempo LARGE_INTEGER nuevoT; QueryPerformanceCounter(&nuevoT); float paso = float(nuevoT.QuadPart - tiempo.QuadPart); tiempo = nuevoT; // Limpiamos el estado de la pipeline devcon->ClearState(); // Indicar los shaders a utilizar devcon->CSSetShader(pCS, 0, 0); // Buffers a usar devcon->CSSetConstantBuffers(0, 1, &pCBufferDisTime); // Creamos las vistas para acceder a la información. Como los buffers se // intercambian cada paso debemos volver a crear las vistas cada vez // Creamos la shader resource view para poder leer los gridpoints en la pipeline 89 o.color = float4( 0.0f, 0.0f, 1.0f, 1.0f ); return o; } // Pixel shader float4 PSMAIN( in VS_OUTPUT input ) : SV_Target { return input.color; } E. 6. Resultados El resultado final puede verse en la figura 50 96 Figura 50: Simulación de agua con columnas ANEXO F: RUIDO Y FRACTALES En este anexo, titulado “Ruido y fractales”, se define qué es el ruido y se distingue entre varios tipos. Además se explica el concepto de fractal y como puede construirse uno usando alguna clase de ruido. F. 1. Ruido En nuestro ámbito el ruido es una función que varía de manera aparentemente aleatoria. Decimos que es aparente porque aunque no se observe ninguna pauta la función debe ser repetible. Esta aleatoriedad se usa para evitar la aparición de patrones que, aunque sí existen, no llegan a ser apreciables. Además el rango de salida de la función debe ser conocido y su ancho de banda limitado. El ruido se usa habitualmente para generar objetos naturales como agua, montañas o nubes, ya sea para la construcción de su forma o para la de las texturas que se aplicarán a su superficie. F. 2. Tipos de ruido A continuación se hace una breve diferenciación de algunos de los tipos de ruido más usados. F. 2. 1. Ruido de rejilla Uno de los tipos de ruido más sencillo y eficiente. Se define una malla para cada punto de nuestro espacio cuyas coordenadas son enteras y se le asigna un número pseudoaleatorio, normalmente con una permutación aleatoria previamente calculada de números enteros en el rango deseado. Para obtener el valor en cualquier punto solo hay que interpolar entre los puntos de la rejilla que lo rodean. Dependiendo del método usado para interpolar y del número de vecinos utilizados se conseguirán unos resultados u otros. F. 2. 2. Ruido de valores Similar al anterior, en este caso el valor de cada punto de la malla es un número aleatorio entre -1 y 1. F. 2. 3. Ruido de gradientes En este caso tenemos también una permutación de números enteros, pero esta vez se usa para asignar a cada punto de la malla un gradiente pseudoaletorio. Estos gradientes son generados usando vectores unitarios distribuidos de manera aleatoria en la esfera unidad. Para calcular el ruido en un punto se usan los puntos de la celda en que se encuentra, obteniendo el producto escalar del gradiente por el vector que va desde cada punto de la celda de la rejilla al propio punto e interpolando. 97 F. 2. 4. Ruido de Perlin Tipo particular del ruido anterior que usa gradientes fijos ya que la permutación de enteros ya otorga suficiente aleatoriedad. En cuanto a la función usada para interpolar, esta era originalmente 3t2 – 2t3 pero la segunda derivada de ésta no es 0 ni en t=1 ni en t=0, por lo que el propio Perlin decidió más adelante cambiar y usar 6t5 – 15t4 + 10t3. F. 2. 5. Ruido de valores-gradientes El ruido de gradientes es siempre 0 para las coordenadas enteras, por lo que es posible que aparezca algún patrón. Para tratar de evitarlo el ruido de valores – gradientes hace una suma ponderada de un ruido de valores y un ruido de gradientes. F. 2. 6. Ruido de rejilla compuesto Como el primer tipo de ruido comentado pero usando algún kernel más complejo que simplemente utilizar los puntos de la celda donde nos encontremos para interpolar, como por ejemplo una esfera de cierto radio o un cono. De esta manera se evitan posibles errores alineados a los ejes. F. 2. 7. Ruido de rejilla disperso Este tipo de ruido utiliza puntos dispersos para realizar la interpolación: no se eligen los más cercanos, sino puntos aleatorios. F. 2. 8. Ruidos explícitos En los ruidos explícitos el valor del ruido se precalcula antes y se guarda, de manera que a la hora de utilizarlos solo hay que buscar el valor en una tabla, textura o cualquier lugar donde esté almacenado. Esto disminuye el tiempo necesario para obtener el ruido, pero limita la flexibilidad. F. 2. 9. Síntesis espectral de Fourier Se genera un espectro discreto de frecuencias de manera aleatoria y éste se filtra para que tenga el rango o forma deseados. Después se aplica una transformada inversa de Fourier para cambiar del domino de la frecuencia al del espacio, que será el ruido. F. 3. Fractales Un fractal es un patrón que se repite a diferentes escalas y que se utiliza para obtener la forma de un objeto irregular que no se podría conseguir con los métodos de la geometría clásica. Si a su construcción añadimos algún tipo de aleatoriedad tenemos un fractal aleatorio. En nuestro caso el patrón viene marcado por una función de ruido sumada a distintas frecuencias y con diferentes escalas. Una de las funciones fractales aleatorias más sencillas y utilizadas es la conocida como fBm, “fractal brownian motion”, definida por los siguientes parámetros: 98 Punto: lugar donde calculamos el valor del fractal. H: incremento de la amplitud en cada frecuencia. Lagunaridad: salto existente entre frecuencias sucesivas. Octavas: número de frecuencias a sumar. Se usa el mismo nombre que en música porque normalmente el salto entre frecuencias es de 2. A continuación podemos ver el código de dicha función: float fbm(float2 punto, float H, float lagunaridad, float octavas) { float value = 0.0f, sum = 0.0f, freq = 1.0f, amp = 1.0f; // Construcción del fractal for (float i = 0.0f; i < octavas; i++) { value = noise(punto * freq); sum += value * amp; freq *= lagunaridad; amp *= H; } return sum; } En este caso el patrón viene marcado directamente por el valor del ruido, pero podrían usarse funciones más complejas como senos o polinomios. En las figuras 51, 52 y 53 se presentan imágenes de MojoWorlds, mundos enteramente construidos usando fractales y ruido, para comprobar la potencia de estas técnicas. 99 Figura 51: Montañas fractales 100 Figura 52: Lago fractal Figura 53: Océano fractal ANEXO G: DOCUMENTACIÓN DE LA APLICACIÓN A continuación se presenta la documentación referente a la aplicación implementada para la generación del paisaje procedural. La documentación se ha generado de manera automática usando el programa DoxyGen. G. 1. Lista de clases AudioEngine (Motor de audio ) Camera (Una camara ) Camera::ConstantBufferCam (Buffer con la posicion de la camara ) Camera::ConstantBufferMatrix (Buffer con las matrices de la camara ) Cielo (Cielo de la escena ) D3D11Manager (Controlador de Direct3D 11 ) Datos (Clase con varios datos necesarios ) Datos::ConstantBufferDatos (Buffer con datos para los shaders ) GPUProfiler (Profiler de la ejecucion de la GPU ) Light (Luz ) Light::ConstantBufferLight (Buffer con los datos de una luz ) ObjManager (Manejo de archivos .obj ) Paisaje (Paisaje de la escena ) Pajaros (Controlador de los pajaros ) Pajaros::InfoPajaro (Informacion de un pajaro ) Perlin (Datos del ruido de Perlin ) Planeta (Planeta fractal ) SamplerManager (Clase para el manejo de samplers de texturas SceneManager (Controlador de la escena ) Shadow (Sombras ) Shadow::ConstantBufferMatrixShadow (Buffer con las matrices de la camara-luz ) TextureManager (Manejo de texturas externas ) VERTEX (Vertice ) WaveLoader (Cargador de archivos .wav ) G. 2. Documentación de clases AudioEngine Class Reference Motor de audio. Public Member Functions 1. HRESULT InitAudio () Iniciar el motor de audio. 2. void LoadSounds () Cargar los sonidos. 3. void Play () Iniciar reproduccion de sonidos. 101 4. void Clean () Liberar recursos. Public Attributes 1. IXAudio2 * audioeng Motor de audio. 2. IXAudio2MasteringVoice * audiodev Audio device. Camera Class Reference Una camara. Classes 1. struct ConstantBufferCam Buffer con la posicion de la camara. 1. struct ConstantBufferMatrix Buffer con las matrices de la camara. Public Member Functions 1. void InitCamera (D3D11Manager *D3D) Inicio de la camara. 2. void IniciarPaseoAereo () Iniciar paseo Aereo. 3. void IniciarPaseoPersona () Iniciar paseo Persona. 4. void ZoomIn () Acercar la camara. 5. void ZoomOut () Alejar la camara. 6. void MoveUp () Mover la camara hacia arriba. 7. void MoveDown () Mover la camara hacia abajo. 8. void MoveRight () Mover la camara hacia la derecha. 9. void MoveLeft () Mover la camara hacia la izquierda. 10. void LookRight () Girar la camara hacia la derecha. 11. void LookLeft () Girar la camara hacia la izquierda. 12. void LookUp () Girar la camara hacia arriba. 13. void LookDown () Girar la camara hacia abajo. 14. void Home () Posicion inicial. 15. void Aire () Posicion alejada. 102 16. void Place (XMVECTOR newPos, XMVECTOR newAt) Colocar la cámara. 17. void UpdateMatrix (XMMATRIX *newWorld) Actualizar matriz del mundo. 18. void UpdateCamera () Actualizar posicion y datos de la camara durante los recorridos. 19. void Clean () Liberar recursos. Public Attributes 1. XMVECTOR camPos Posicion de la camara. 2. bool camPaseoAereo Variable que nos indica si estamos en medio del recorrido aereo. 3. bool camPaseoPersona Variable que nos indica si estamos en medio del recorrido de la persona. 4. ID3D11Buffer * pCBufferMat Constant buffer de las matrices de mundo, vista y proyeccion. 5. ID3D11Buffer * pCBufferCam Constant buffer con la posicion de la camara. Member Function Documentation void Camera::InitCamera (D3D11Manager * D3D) Parameters: D3D IN: Controlador de Direct3D void Camera::Place (XMVECTOR newPos, XMVECTOR newAt) Parameters: newPos IN: Nueva posicion newAt IN: Nuevo punto de mira void Camera::UpdateMatrix (XMMATRIX * newWorld) Parameters: newWorld IN: Nueva matriz Camera::ConstantBufferCam Struct Reference Buffer con la posicion de la camara. Public Attributes 1. XMVECTOR CameraPos Posicion de la camara. Camera::ConstantBufferMatrix Struct Reference Buffer con las matrices de la camara. Public Attributes 1. XMMATRIX World Mundo. 103 2. XMMATRIX View Vista. 3. XMMATRIX Projection Proyeccion. Cielo Class Reference Cielo de la escena. Public Member Functions 1. void InitCielo (D3D11Manager *D3D) Inicializar el cielo. 2. void Render (Camera *Cam, TextureManager *TextureMan, SamplerManager *SamplerMan) Renderizar el cielo. 3. void UpdateCielo (Camera *Cam) Actualizar posicion del cielo. 4. void Clean () Liberar recursos. Member Function Documentation void Cielo::InitCielo (D3D11Manager * D3D) Parameters: D3D IN: Controlador de Direct3D void Cielo::Render (Camera * Cam, TextureManager * TextureMan, SamplerManager * SamplerMan) Parameters: Cam IN: Camara de la escena TextureMan IN: Texturas SamplerMan IN: Samplers void Cielo::UpdateCielo (Camera * Cam) Parameters: Cam IN: Camara en torno a la que centramos el cielo D3D11Manager Class Reference Controlador de Direct3D 11. Public Member Functions 1. void InitD3D (HWND hWnd, int windowWidth, int windowHeight) Inicio de Direct3D. 2. void Clean () Liberar recursos. Public Attributes 1. D3D11_VIEWPORT viewport Viewport. 2. IDXGISwapChain * swapchain Swap chain. 104 3. ID3D11Device * dev = NUL Direct3D device interface. 4. ID3D11DeviceContext * devcon Direct3D device context. 5. IDXGIFactory * factory Interfaz para crear objetos DXGI. 6. IDXGIAdapter * adapter Tarjeta grafica. 7. IDXGIOutput * adapterOutput Monitor. 8. ID3D11RenderTargetView * rtv Render Target View. 9. ID3D11Texture2D * pDSB Depth stencil buffer. 10. ID3D11DepthStencilView * pDSV Vista del depth buffer. 11. ID3D11InputLayout * pLayout Formato de entrada a la pipeline (vertices) 12. ID3D11RasterizerState * pRStateCull Configuracion de la etapa de rasterizado con culling. 13. ID3D11RasterizerState * pRStateNoCull Configuracion de la etapa de rasterizado sin culling. 14. ID3D11BlendState * pBSTransparent Blender state para usar transparencias. Member Function Documentation void D3D11Manager::InitD3D (HWND hWnd, int windowWidth, int windowHeight) Parameters: hWnd IN: Ventana de la aplicación windowWidth IN: Anchura de la ventana windowHeight IN: Altura de la ventana Datos Class Reference Clase con varios datos necesarios. Classes 1. struct ConstantBufferDatos Buffer con datos para los shaders. Public Member Functions 1. void InitData (D3D11Manager *D3D) Inicio de los datos. 2. void UpdateData (float d0, float d1, float d2, float d3) Actualizar buffer de datos. 3. void Clean () Liberar recursos. Public Attributes 1. int PaisajeX Dimension X del paisaje. 105 Planeta Class Reference Planeta fractal. Public Member Functions 1. void InitPlaneta (D3D11Manager *D3D) Inicio del planeta. 2. void CalcularPlaneta (Perlin *PerlinData, SamplerManager *SamplerMan, Datos *Data) Calcular fractal del planeta. 3. void Render (Camera *Cam, Perlin *PerlinData, SamplerManager *SamplerMan, Datos *Data) Renderizar el planeta. 4. void UpdatePlaneta () Actualizar planeta. 5. void Clean () Liberar recursos. Member Function Documentation void Planeta::InitPlaneta (D3D11Manager * D3D) Parameters: D3D IN: Controlador de Direct3D void Planeta::CalcularPlaneta (Perlin * PerlinData, SamplerManager * SamplerMan, Datos * Data) Parameters: PerlinData IN: Datos del ruido de Perlin SamplerMan IN: Samplers de texturas Data IN: Datos del paisaje void Planeta::Render (Camera * Cam, Perlin * PerlinData, SamplerManager * SamplerMan, Datos * Data) Parameters: Cam IN: Camara PerlinData IN: Datos del ruido de Perlin SamplerMan IN: Samplers de las texturas Data IN: Datos del planeta SamplerManager Class Reference Clase para el manejo de samplers de texturas. Public Member Functions 1. void InitSamplers (D3D11Manager *D3D) Inciar samplers de texturas. 2. void Clean () Liberar recursos. Public Attributes 1. ID3D11SamplerState * pPointMirrorSampler Sampler por puntos, mirror. 2. ID3D11SamplerState * pLineMirrorSamplerComp Sampler linear con comparacion, mirror. 3. ID3D11SamplerState * pLineWrapSampler Sampler lineal, wrap. 112 Member Function Documentation void SamplerManager::InitSamplers (D3D11Manager * D3D) Parameters: D3D IN: Controlador de Direct3D SceneManager Class Reference Controlador de la escena. Public Member Functions 1. void InitScene (D3D11Manager *D3D) Inicializar la escena. 2. void InitFractals (D3D11Manager *D3D) Iniciar los fractales. 3. void InitFrame () Marcar inicio de frame. 4. void UpdateScene (D3D11Manager *D3D) Actualizar escena. 5. void RenderScene (D3D11Manager *D3D) Renderizar escena. 6. void EndFrame () Marcar final de frame. 7. void ShowFrameInfo () Mostrar informacion del frame. 8. void Modify (WPARAM wParam) Modificar escena. 9. void Clean () Liberar recursos. Member Function Documentation void SceneManager::InitScene (D3D11Manager * D3D) Parameters: D3D IN: Controlador de Direct3D void SceneManager::InitFractals (D3D11Manager * D3D) Parameters: D3D IN: Controlador de Direct3D void SceneManager::UpdateScene (D3D11Manager * D3D) Parameters: D3D IN: Controlador de Direct3D void SceneManager::RenderScene (D3D11Manager * D3D) Parameters: D3D IN: Controlador de Direct3D void SceneManager::Modify (WPARAM wParam) Parameters: wParam IN: Tecla pulsada 113 Shadow Class Reference Sombras. Classes 1. struct ConstantBufferMatrixShadow Buffer con las matrices de la camara-luz. Public Member Functions 1. void InitShadow (D3D11Manager *D3D, Light *Luz, XMMATRIX *world) Inicializacion de las sombras. 2. void CalcularMapa (ID3D11Buffer *pVBuffer, ID3D11Buffer *pIBuffer, int numIndices) Calcular mapa de sombras. 3. void UpdateShadow (Light *Luz, XMMATRIX *world) Actualizar sombras. 4. void Clean () Librerar recursos. Public Attributes 1. ID3D11Buffer * pCBufferShadow Constant buffer de las matrices de mundo, vista y proyeccion para las sombras. 2. ID3D11ShaderResourceView * pSRVShadow Vista para leer el mapa de sombras. Member Function Documentation void Shadow::InitShadow (D3D11Manager * D3D, Light * Luz, XMMATRIX * world) Parameters: D3D IN: Controlador de Direct3D Luz IN: Luz que genera las sombras world IN: Mundo de la escena void Shadow::CalcularMapa (ID3D11Buffer * pVBuffer, ID3D11Buffer * pIBuffer, int numIndices) Parameters: pVBuffer IN: Vertices de la geometria que genera sombra pIBuffer IN: Indices de la geometria que genera sombra numIndices IN: Numero de indices de la geometria void Shadow::UpdateShadow (Light * Luz, XMMATRIX * world) Parameters: Luz IN: Luz que genera las sombras world IN: Mundo de la escena Shadow::ConstantBufferMatrixShadow Struct Reference Buffer con las matrices de la camara-luz. Public Attributes 1. XMMATRIX World Mundo. 2. XMMATRIX View Vista. 3. XMMATRIX Projection Proyeccion. 114 TextureManager Class Reference Manejo de texturas externas. Public Member Functions 1. void InitTextures (D3D11Manager *D3D) Cargar texturas. 2. void Clean () Liberar recursos. Public Attributes 1. ID3D11ShaderResourceView * pSRVTexMon Vista de la textura de la montaña 2. ID3D11ShaderResourceView * pSRVTexNormalMon Vista de la textura con las normales de la montaña 3. ID3D11ShaderResourceView * pSRVTexSand Vista de la textura de la playa. 4. ID3D11ShaderResourceView * pSRVTexNormalSand Vista de la textura con las normales de la playa. 5. ID3D11ShaderResourceView * pSRVTexWetSand Vista de la textura de la playa, zona mojada. 6. ID3D11ShaderResourceView * pSRVTexNormalWetSand Vista de la textura con las normales de la playa, zona mojada. 7. ID3D11ShaderResourceView * pSRVTexPalmera1 Vista de la textura de la palmera 1. 8. ID3D11ShaderResourceView * pSRVTexPalmera2 Vista de la textura de la palmera 2. 9. ID3D11ShaderResourceView * pSRVTexPalmera3 Vista de la textura de la palmera 3 y 4. 10. ID3D11ShaderResourceView * pSRVTexPalmera5 Vista de la textura de la palmera 5. 11. ID3D11ShaderResourceView * pSRVTexSky Vista de la textura del cielo. Member Function Documentation void TextureManager::InitTextures (D3D11Manager * D3D) Parameters: D3D IN: Controlador de Direct3D VERTEX Struct Reference Vertice. Public Attributes 1. float x Posicion.x. 2. float y Posicion.y. 3. float z Posicion.z. 4. float nx Normal.x. 115 5. float ny Normal.y. 6. float nz Normal.z. 7. float u Coordenada u de la textura. 8. float v Coordenada v de la textura. 9. float w Coordenada w de la textura. 10. int tipo Tipo de objeto al que pertenece, agua, playas, montañas o palmeras. 11. int numTriangulos Numero de triangulos a los que pertenece. 12. int triangulos [6] Triangulos a los que pertenece. WaveLoader Class Reference Cargador de archivos .wav. Public Member Functions 1. HRESULT CargarSonido (WAVEFORMATEXTENSIBLE *wfx, XAUDIO2_BUFFER *buffer, TCHAR *strFileName) Cargar fichero .wav. Member Function Documentation HRESULT WaveLoader::CargarSonido (WAVEFORMATEXTENSIBLE * wfx, XAUDIO2_BUFFER * buffer, TCHAR * strFileName) Parameters: wfx Out: Formato de audio buffer Out: Buffer de audio strFileName In: Nombre del fichero 116