tabla de contenido · página 1 de 134 tabla de contenido ... diagrama de colores de una niebla ......

134
Proyecto iL-Engine Enginyeria i Arquitectura La Salle Página 1 de 134 Tabla de contenido 1 Introducción .......................................................................................................................... 6 1.1 Marco ............................................................................................................................ 6 1.2 Estado del arte .............................................................................................................. 7 1.3 Descripción del problema ........................................................................................... 12 1.4 Solución propuesta...................................................................................................... 14 1.5 Perspectiva general del proyecto ................................................................................ 16 2 Fundamentos teóricos ........................................................................................................ 17 2.1 Librería gráfica............................................................................................................. 17 2.1.1 Introducción ........................................................................................................ 17 2.1.2 DirectX 9.0c ......................................................................................................... 20 2.1.3 DXUT .................................................................................................................... 22 2.2 Renderizado ................................................................................................................ 24 2.2.1 Iluminación .......................................................................................................... 24 2.2.2 Luces .................................................................................................................... 32 2.2.3 Texturas ............................................................................................................... 34 2.2.4 Materiales ........................................................................................................... 44 2.2.5 Render Targets .................................................................................................... 47 2.2.6 Efectos Especiales................................................................................................ 49 2.2.7 Estructura Pipe-Line ............................................................................................ 54 2.3 Tratamiento de eventos .............................................................................................. 55 2.3.1 Trama .................................................................................................................. 55 2.3.2 Eventos ................................................................................................................ 57 2.3.3 Entrada / Salida ................................................................................................... 58 2.4 Escena.......................................................................................................................... 59 2.4.1 Vértices flexibles ................................................................................................. 59 2.4.2 Vectores de índices y vértices ............................................................................. 60 2.4.3 Mallas .................................................................................................................. 62 2.4.4 Submallas ............................................................................................................ 65 2.4.5 Bounding Boxes ................................................................................................... 68 2.4.6 Cámaras ............................................................................................................... 69 2.4.7 Frustrums ............................................................................................................ 71 2.5 Sombras....................................................................................................................... 73 2.5.1 Introducción ........................................................................................................ 73 2.5.2 Cube Shadow Maps ............................................................................................. 74 2.6 Exportador................................................................................................................... 77

Upload: dothien

Post on 24-Sep-2018

219 views

Category:

Documents


0 download

TRANSCRIPT

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 1 de 134

Tabla de contenido 1 Introducción .......................................................................................................................... 6

1.1 Marco ............................................................................................................................ 6

1.2 Estado del arte .............................................................................................................. 7

1.3 Descripción del problema ........................................................................................... 12

1.4 Solución propuesta...................................................................................................... 14

1.5 Perspectiva general del proyecto ................................................................................ 16

2 Fundamentos teóricos ........................................................................................................ 17

2.1 Librería gráfica ............................................................................................................. 17

2.1.1 Introducción ........................................................................................................ 17

2.1.2 DirectX 9.0c ......................................................................................................... 20

2.1.3 DXUT .................................................................................................................... 22

2.2 Renderizado ................................................................................................................ 24

2.2.1 Iluminación .......................................................................................................... 24

2.2.2 Luces .................................................................................................................... 32

2.2.3 Texturas ............................................................................................................... 34

2.2.4 Materiales ........................................................................................................... 44

2.2.5 Render Targets .................................................................................................... 47

2.2.6 Efectos Especiales................................................................................................ 49

2.2.7 Estructura Pipe-Line ............................................................................................ 54

2.3 Tratamiento de eventos .............................................................................................. 55

2.3.1 Trama .................................................................................................................. 55

2.3.2 Eventos ................................................................................................................ 57

2.3.3 Entrada / Salida ................................................................................................... 58

2.4 Escena .......................................................................................................................... 59

2.4.1 Vértices flexibles ................................................................................................. 59

2.4.2 Vectores de índices y vértices ............................................................................. 60

2.4.3 Mallas .................................................................................................................. 62

2.4.4 Submallas ............................................................................................................ 65

2.4.5 Bounding Boxes ................................................................................................... 68

2.4.6 Cámaras ............................................................................................................... 69

2.4.7 Frustrums ............................................................................................................ 71

2.5 Sombras ....................................................................................................................... 73

2.5.1 Introducción ........................................................................................................ 73

2.5.2 Cube Shadow Maps ............................................................................................. 74

2.6 Exportador ................................................................................................................... 77

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 2 de 134

3 Parte práctica ...................................................................................................................... 80

3.1 Librería gráfica ............................................................................................................. 80

3.1.1 DirectX 9.0c ......................................................................................................... 80

3.1.2 DXUT .................................................................................................................... 82

3.2 Renderizado ................................................................................................................ 84

3.2.1 Iluminación .......................................................................................................... 84

3.2.2 Luces .................................................................................................................... 91

3.2.3 Texturas ............................................................................................................... 94

3.2.4 Materiales ........................................................................................................... 97

3.2.5 Render Targets .................................................................................................. 100

3.2.6 Efectos Especiales.............................................................................................. 102

3.2.7 Estructura Pipe-Line .......................................................................................... 107

3.3 Tratamiento de eventos ............................................................................................ 112

3.3.1 Trama ................................................................................................................ 112

3.3.2 Eventos .............................................................................................................. 113

3.3.3 Entrada / Salida ................................................................................................. 114

3.4 Escena ........................................................................................................................ 115

3.4.1 Vértices flexibles ............................................................................................... 115

3.4.2 Vectores de índices y vértices ........................................................................... 116

3.4.3 Mallas ................................................................................................................ 117

3.4.4 Submallas .......................................................................................................... 119

3.4.5 Bounding Boxes ................................................................................................. 120

3.4.6 Cámaras ............................................................................................................. 122

3.4.7 Frustrums .......................................................................................................... 123

3.5 Sombras ..................................................................................................................... 124

3.5.1 Cube Shadow Maps ........................................................................................... 124

3.6 Exportador ................................................................................................................. 126

4 Resultados ......................................................................................................................... 129

5 Conclusión ......................................................................................................................... 131

6 Líneas de futuro ................................................................................................................ 132

7 Glosario ............................................................................................................................. 133

8 Bibliografía ........................................................................................................................ 134

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 3 de 134

Listado de Ilustraciones Ilustración 1 - Capas según nivel ................................................................................................. 18 Ilustración 2 - Capas según nivel con DXUT añadido .................................................................. 22 Ilustración 3 - Componentes del Modelo Phong ......................................................................... 25 Ilustración 4 - Escena limpia ........................................................................................................ 26 Ilustración 5 - Cálculo de la componente Difusa ......................................................................... 27 Ilustración 6 - Cálculo de la componente Especular ................................................................... 28 Ilustración 7 - Diferentes valores de n para la componente especular ...................................... 29 Ilustración 8 - Diferentes tipos de modelos de iluminación ....................................................... 30 Ilustración 9 - Diferentes resultados según parámetros A i B ..................................................... 31 Ilustración 10 - Diagrama luz omnidireccional ............................................................................ 34 Ilustración 11 - Muestras de texturas con componente Difusa .................................................. 36 Ilustración 12 - Texturas de componente Especular con sus respectivas Difusas ...................... 37 Ilustración 13 - Interpolación trivial entre normales por vértice ................................................ 39 Ilustración 14 - Interpolación entre normales por vértice .......................................................... 39 Ilustración 15 - Interpolación mediante textura de normales .................................................... 39 Ilustración 16 - Componente emisiva aplicada a un objeto ........................................................ 41 Ilustración 17 - Efecto del Mapa de Luces .................................................................................. 42 Ilustración 18 - Diagrama del Manager de Texturas ................................................................... 43 Ilustración 19 - Estructura de un material .................................................................................. 44 Ilustración 20 - Diagrama del Manager de Materiales ................................................................ 46 Ilustración 21 - Diagrama de renderizado ................................................................................... 47 Ilustración 22 - Diagrama de renderizado con Render Targets .................................................. 48 Ilustración 23 - Diagrama de ejemplos de distancias de Nieblas ................................................ 50 Ilustración 24 - Diagrama de colores de una Niebla ................................................................... 50 Ilustración 25 - Diagrama de flujo del proceso Glow .................................................................. 51 Ilustración 26 - Proceso teórico de Glow a nivel de píxel ........................................................... 52 Ilustración 27 - Diagrama de funcionamiento del Glow mediante Kawase ................................ 53 Ilustración 28 - Diagrama del Pipe-Line utilizado para renderizar .............................................. 54 Ilustración 29 - Diagrama de Lectura / Escritura en una trama .................................................. 56 Ilustración 30 - Ejemplo de evento ............................................................................................. 58 Ilustración 31 - Diferentes formatos de vértice .......................................................................... 60 Ilustración 32 - Index y Vertex Buffer interrelacionados ............................................................ 61 Ilustración 33 - Diagrama lógico de una malla ............................................................................ 63 Ilustración 34 - Diagrama del Manager de Mallas ...................................................................... 64 Ilustración 35 - Especificación de la Submalla ............................................................................. 66 Ilustración 36 - Diagrama del manager de Submallas ................................................................. 67 Ilustración 37 - Ejemplos de Bounding Boxes ............................................................................. 68 Ilustración 38 - Diagrama de cámaras en primera y tercera persona ......................................... 70 Ilustración 39 - Volumen de un Frustrum ................................................................................... 71 Ilustración 40 - Estructura de un cubemap ................................................................................. 74 Ilustración 41 - Ejemplo de acceso a la información de un cubemap ......................................... 75 Ilustración 42 - Ejemplo de aplicación de sombras ..................................................................... 76 Ilustración 43 - Partes del software a unir .................................................................................. 77 Ilustración 44 - Diagrama de las fases del Constructor ............................................................... 79 Ilustración 45 - Diagrama matemático de la Luz ......................................................................... 92

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 4 de 134

Listado de Tablas Tabla 1 - Especificaciones Hardware ............................................................................................. 6 Tabla 2 - Especificaciones Software .............................................................................................. 6 Tabla 3 - Logos de las soluciones de pago ..................................................................................... 8 Tabla 4 - Funcionalidades de las soluciones de pago .................................................................... 9 Tabla 5 – Renders de soluciones de pago ..................................................................................... 9 Tabla 6 - Logos soluciones libres ................................................................................................. 10 Tabla 7 - Funcionalidades soluciones libres ................................................................................ 10 Tabla 8 - Renders soluciones libres ............................................................................................. 11 Tabla 9 - Versiones de DirectX .................................................................................................... 19 Tabla 10 - Fusiones / Sustituciones ............................................................................................. 21 Tabla 11 - Coeficientes de los planos del Frustrum..................................................................... 72 Tabla 12 - Fases del Constructor ................................................................................................. 78 Tabla 13 - Librerías necesarias para DirectX 9 ............................................................................ 80 Tabla 14 - Inicialización de DirectX 9.0c ...................................................................................... 80 Tabla 15 - Bucle de renderizado de la aplicación ........................................................................ 81 Tabla 16 - Librerías necesarias para DXUT .................................................................................. 82 Tabla 17 - Inicialización de los callbacks de DXUT ....................................................................... 82 Tabla 18 - Bucle de programa de DXUT ...................................................................................... 83 Tabla 19 - Retorno del código de la aplicación ........................................................................... 83 Tabla 20 - Variable de textura en el shader ................................................................................ 84 Tabla 21 - Variables de tipo lógico .............................................................................................. 84 Tabla 22 - Estructura de salida .................................................................................................... 85 Tabla 23 - Vertex Shader ............................................................................................................. 86 Tabla 24 - Cabecera y coordenadas de textura del PS ................................................................ 87 Tabla 25 - Sampleado de texturas ............................................................................................... 87 Tabla 26 - Tintado de la luz ......................................................................................................... 87 Tabla 27 - Distancias y sombreado en el PS ................................................................................ 88 Tabla 28 - Ecuación de iluminación de PS ................................................................................... 88 Tabla 29 - Cálculo de la niebla ..................................................................................................... 89 Tabla 30 - Función del PS sin subdivisiones ................................................................................ 90 Tabla 31 - Constructor de la Luz .................................................................................................. 91 Tabla 32 - Posición de la Luz ........................................................................................................ 91 Tabla 33 - Generación de los parámetros m y n de la recta ....................................................... 93 Tabla 34 - Actualización de los parámetros de la Luz ................................................................. 94 Tabla 35 - Encapsulado de Textura ............................................................................................. 95 Tabla 36 - Cuerpo de la clase textura .......................................................................................... 95 Tabla 37 - Manager de textura .................................................................................................... 96 Tabla 38 - Clase Material ............................................................................................................. 97 Tabla 39 - Formato de material ................................................................................................... 98 Tabla 40 - Clase del manager de materiales ............................................................................... 99 Tabla 41 - Clase Render Target .................................................................................................. 101 Tabla 42 - Inicialización de un Render Target ........................................................................... 102 Tabla 43 - Clase de la niebla ...................................................................................................... 103 Tabla 44 - Función de actualización de la niebla ....................................................................... 104 Tabla 45 - Código de renderizado de la niebla .......................................................................... 104 Tabla 46 - Metodología del Glow .............................................................................................. 105 Tabla 47 - Píxel Shader del post procesado Glow ..................................................................... 106 Tabla 48 - Inicialización de matrices ......................................................................................... 107 Tabla 49 - Inicialización de la geometría ................................................................................... 107

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 5 de 134

Tabla 50 - Inicialización del Glow .............................................................................................. 108 Tabla 51 - Activación del Back Buffer ........................................................................................ 108 Tabla 52 - Inicialización de los parámetros del shader ............................................................. 109 Tabla 53 - Parámetro para pintado en alambre ........................................................................ 109 Tabla 54 - Renderizado en el Back Buffer ................................................................................. 110 Tabla 55 - Renderizado de líneas .............................................................................................. 111 Tabla 56 - Finalización del proceso de render ........................................................................... 112 Tabla 57 - Clase Trama .............................................................................................................. 113 Tabla 58 - Clase evento ............................................................................................................. 114 Tabla 59 - Transformación de E/S en evento ............................................................................ 114 Tabla 60 - Vértices flexibles ....................................................................................................... 115 Tabla 61 - Definición de la memoria de un vértice flexible ....................................................... 116 Tabla 62 - Clase VertexArray ..................................................................................................... 116 Tabla 63 - Clase IndexArray ....................................................................................................... 117 Tabla 64 - Clase CSMesh ............................................................................................................ 118 Tabla 65 - Clase CSMeshMgr ..................................................................................................... 119 Tabla 66 - Clase CSSubMesh ...................................................................................................... 119 Tabla 67 - Clase CSSubMeshMgr ............................................................................................... 120 Tabla 68 - Clase CBoundingBox ................................................................................................. 121 Tabla 69 - Clase CCamera .......................................................................................................... 122 Tabla 70 - Clase CFrustrum ........................................................................................................ 123 Tabla 71 - Datos de la Clase CCubeMap .................................................................................... 125 Tabla 72 - Interfaz de la Clase CCubeMap ................................................................................. 125 Tabla 73 - Inicialización del constructor .................................................................................... 126 Tabla 74 - Vertex y Índex Buffers en el constructor .................................................................. 127 Tabla 75 - Cambio de formato de vectores ............................................................................... 127 Tabla 76 - Rotación de las normales ......................................................................................... 127 Tabla 77 - Guardando la nueva geometría ................................................................................ 128 Tabla 78 - Primera tabla de resultados ..................................................................................... 129 Tabla 79 - Segunda tabla de resultados .................................................................................... 130

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 6 de 134

1 Introducción

1.1 Marco Este proyecto ha sido realizado por Llorenç Martí García, estudiante de la universidad

Enginyeria i Arquitectura La Salle.

Asimismo se ha realizado este proyecto en el departamento de multimedia de dicha

universidad Enginyeria i Arquitectura La Salle.

Para realizar este proyecto, ha sido necesario un ordenador de elevadas prestaciones a nivel

de tecnología 3D. A continuación podemos observar detalladamente más información sobre

esta herramienta.

HARDWARE

Procesador Intel® Core™ 2 - 6600 @ 2.40 GHz

Memoria RAM 2.048 MB

Tarjeta Gráfica NVIDIA Gforce 8800 GTX - 768 MB

Tabla 1 - Especificaciones Hardware

SOFTWARE

Sistema Operativo Microsoft Windows XP® Profesional SP2

Librería Gráfica Microsoft DirectX 9.0c (1)

Kit de desarrollo de software gráfico Microsoft DirectX SDK (June 2007) (2)

Programa de diseño 3D Autodesk 3D Studio MAX 9 (32-bits)

Programa de edición fotográfica Adobe Photoshop CS

Tabla 2 - Especificaciones Software

Mi ponente ha sido Juan Andrés Fernández Munuera, Ingeniero en Informática de Sistemas y

actual profesor en la universidad Enginyeria i Arquitectura La Salle.

Agradecer enormemente la ayuda prestada por el profesor de la Universidad Pompeu Fabra,

Joan Abadia. Sin su ayuda sobre conocimientos en la librería grafica DirectX seguramente este

proyecto no gozaría de la calidad visual que ofrece actualmente.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 7 de 134

1.2 Estado del arte

Actualmente existen multitud de soluciones software para desarrollar aplicaciones 3D de

entretenimiento o visualización y representación. Estas soluciones vienen en forma de librerías

de código diseñadas para que nos permitan, de una manera más fácil y rápida, implementar

aplicaciones 3D.

A estas librerías o conjunto de código se les denomina en el mercado como Motores 3D o en

inglés Engines 3D. Estos motores nos encapsulan un código diseñado para gestionar y dirigir

los datos de nuestra aplicación liberándonos a nosotros de esa tarea.

Las funcionalidades que nos proveen estas librerías son variadas aunque siempre hay una en

común, y es la representación gráfica de los datos. Aparte de esta característica de

representación gráfica, muchas otras funcionalidades pueden estar presentes en dicho motor,

véase funcionalidades de sonido, control de físicas, control de inteligencia artificial, etc.

Estas soluciones pueden ser libres o de pago. Normalmente por naturaleza de contrato e

imagen corporativa, las soluciones de pago son más completas y ofrecen muchas más

funcionalidades que las soluciones de carácter libre o que no requieren de una inversión

económica. Hay que tener en cuenta que cuando una librería de representación 3D incluye

otras muchas funcionalidades como las antes mencionadas como físicas, inteligencia artificial,

etc. esta recibe el nombre de Motor de Juegos 3D o Game Engine 3D. Esto es así debido a la

especialización de dicha librería ante necesidades de entretenimiento, aunque también

pueden ser utilizadas para aplicaciones con muchos otros fines.

A continuación presentaremos unos ejemplos de Motores 3D y enmarcaremos algunas de sus

características pudiendo comprobar cómo las opciones de pago están dotadas de mayores

funcionalidades que las soluciones libres. Estas son Unreal Engine (3) y Cry Engine (4). Ello no

implica que no se puedan desarrollar soluciones profesionales con los dos tipos de soluciones.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 8 de 134

EJEMPLOS DE SOLUCIONES DE PAGO

EMPRESA MOTOR 3D

Tabla 3 - Logos de las soluciones de pago

Como podemos observar en la tabla anterior, aquí tenemos dos de las múltiples soluciones de

pago que existen actualmente en el mercado. Sin intentar incurrir en cuál de las soluciones es

mejor, hemos elegido estas dos soluciones porque las dos proporcionan funcionalidades

parecidas.

Estas funcionalidades son:

FUNCIONALIDADES

Render 3D Representación gráfica en pantalla utilizando la última tecnología

disponible en su día. Nos proporciona el uso de técnicas avanzadas de

pintado como de sombreado al igual como de iluminación.

Sonido 3D Capacidad de dotar a nuestras aplicaciones con sonido envolvente.

Inteligencia Artificial Capacidad de integrar en nuestras aplicaciones la habilidad de tratar

con entes pre-programados para que actúen con una conducta

establecida o respondan a ciertas situaciones de una manera

automática.

Físicas 3D Capacidad de dotar a nuestros objetos con propiedades físicas como

la gravedad o fuerzas varias al mismo tiempo que se gestionan

correctamente las colisiones entre diferentes tipos de objetos.

Editor 3D Aplicación que permite a un artista generar contenido especifico de

este motor 3D sin necesidad de tener conocimientos de

programación previos.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 9 de 134

Animaciones 3D Capacidad de dotar a personajes y objetos de animaciones y

movimientos de complejidad elevada.

Tabla 4 - Funcionalidades de las soluciones de pago

Una vez comentadas las funcionalidades más importantes de estas soluciones de pago,

exponemos cuatro imágenes a modo de expresar la potencia y rendimiento de estas

soluciones.

IMÁGENES EN TIEMPO REAL

UNREAL TECHNOLOGY CRY ENGINE

Tabla 5 – Renders de soluciones de pago

Como podemos observar en las imágenes anteriores, son unas imágenes de una gran calidad y

de un potencial muy elevado que nos da a entender que las aplicaciones que nosotros

podamos realizar con esa solución gozarán de un gran parecido con la realidad.

A continuación mostraremos dos de las múltiples soluciones libres que existen en la

actualidad. Debido a nuestra experiencia en ellas, hemos elegido a dos de las más populares

en la comunidad de software libre sin intentar incurrir cuál de ellas es mejor, estas son Ogre3D

(5) y Crystal Space (6).

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 10 de 134

EJEMPLOS DE SOLUCIONES LIBRES

EMPRESA MOTOR 3D

Tabla 6 - Logos soluciones libres

En la tabla anterior podemos observar dos soluciones libres y populares en la comunidad de

código libre. Estas dos soluciones ofrecen menos funcionalidades que las soluciones de pago.

Veamos y razonemos el porqué.

FUNCIONALIDADES

Render 3D Representación gráfica en pantalla utilizando tecnología no

anticuada. Nos proporciona el uso de técnicas avanzadas de pintado

como de sombreado al igual como de iluminación, pero se deja en

nuestras manos el hecho de definir sistemas de iluminación de

extrema complejidad como las soluciones de pago.

Tabla 7 - Funcionalidades soluciones libres

Podemos observar como estas soluciones no pueden ser llamadas en ningún caso Motores de

Juegos 3D pues la única, aunque no fuera de mérito, funcionalidad es la de ofrecer una

representación 3D en pantalla.

Asimismo podemos observar como el hecho de crear un Motor de Juegos 3D es una tarea

realmente complicada y compleja, que requiere de un equipo de trabajo bien coordinado y

experto. Es por ese motivo que supone un reto muy grande el hecho de desarrollar una librería

tan compleja como un Motor de Juegos 3D por una entidad sin recibir fondo alguno por parte

de sus clientes, debido a que su distribución es gratuita.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 11 de 134

Aunque podamos observar menos funcionalidades en estas soluciones libres, eso no implica

que puedan representar en pantalla escenas con gran calidad e incluso profesionalidad. Dicho

esto pasamos a ver unos ejemplos en tiempo real de estas soluciones.

IMÁGENES EN TIEMPO REAL

OGRE 3D CRYSTAL SPACE

Tabla 8 - Renders soluciones libres

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 12 de 134

1.3 Descripción del problema

El problema que se nos plantea inicialmente en este proyecto radica en la investigación y el

conocimiento necesarios como para poder llevar a cabo un estudio e implementación de lo

que anteriormente hemos comentado como motor 3D.

Cabe destacar que este proyecto no tiene como finalidad obtener un producto acabado de

nivel profesional como hemos visto en el punto anterior, ya que para eso es necesaria una

cantidad de recursos de la cual ahora mismo no disponemos.

Dicho esto, la investigación de este proyecto va íntimamente relacionada con la

implementación de un motor 3D pues de esta manera se obtiene una visión y

conocimientos privilegiados sobre su gestión y expansión interna.

Actualmente el mercado de los videojuegos está en alza y eso promociona que la

tecnología que está detrás de estos haya sufrido una inversión e investigación sin

precedentes, lo que nos permite ver como día a día salen nuevas técnicas e ideas que son

implementadas en estos motores 3D.

Debido a ello, aprovecharemos el hecho de implementar un motor 3D para poder

enriquecernos a nivel intelectual y agrandar nuestra experiencia portando estas nuevas

técnicas e ideas a nuestro proyecto.

Estas técnicas e ideas pueden oscilar entre cosas relativamente sencillas hasta conceptos

realmente complicados incluso de imaginar por nosotros mismos. En este proyecto

abordaremos esas técnicas e ideas que estén al alcance siempre teniendo en cuenta los

recursos necesarios para llevarlas a su fin.

Paralelamente a la comprensión e implementación de estas ideas, un aspecto igual de

importante es el hecho de la coexistencia de estas al mismo tiempo en el motor 3D.

Debemos darnos cuenta de que un motor 3D no es solo un conjunto de técnicas e ideas

que funcionan por separado sino que deben de ser integradas y funcionar codo con codo

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 13 de 134

unas con otras. Por eso mismo, es igualmente importante el hecho de implementar las

técnicas como el hecho de garantizar una gestión apropiada de ellas para que puedan

funcionar conjuntamente y darnos el resultado visual que esperamos.

Veremos que estas técnicas están basadas tanto en DirectX (1) como en C++ (7), algunas de

ellas serán propiamente de gestión y se basarán en una gestión a nivel de código C++ y

otras estarán basadas en mostrarnos un resultado visual utilizando toda la potencia gráfica

que tengamos a nuestro alcance mediante Shaders.

Este hecho nos garantiza que veremos en profundidad la implementación y gestión interna

de un motor 3D, que aunque no llegue a la complejidad de un producto profesional, sí que

será digno de mención a nivel de investigación y experimentación.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 14 de 134

1.4 Solución propuesta

Como ya hemos podido intuir en el punto anterior, la solución propuesta para el problema

redactado anteriormente es la implementación de un motor 3D.

Un motor 3D puede partir de múltiples ideas y metodologías de funcionamiento, tantas como

personas haya dispuestas a implementar un motor 3D. Esto es así debido a que cada

programador o equipo de programadores elige previamente cómo funcionará este motor 3D y

cómo se comportará según sus necesidades.

Gracias a que nosotros poseemos experiencia con motores 3D de software libre, esto nos

acerca mucho mas a una solución hábil y estructurada debido a que ciertas metodologías

utilizadas por estos motores son inculcadas en nosotros mientras programamos con ellos.

Debido a esto, poseemos experiencia con motores como Ogre3D (5), Crystal Space (6), etc. Los

cuales nos ofrecen ideas maduras de cómo se pueden gestionar ciertos recursos que

posteriormente nosotros mismos habremos de abordar.

El motor 3D que implementaremos nos proporcionará una solución gráfica completa a nivel de

render, esto quiere decir que el producto resultante será capaz de mostrar en pantalla efectos

de iluminación avanzados, sistemas de sobra, ambientación, etc. Que igualmente

comentaremos y detallaremos en profundidad más adelante en este documento.

Todo motor 3D acostumbra a ir acompañado de un editor gráfico el cual nos permite generar

geometría para este. Actualmente estos editores se ven ayudados por programas

profesionales de modelado 3D.

En nuestro caso concreto, somos conscientes como hemos comentado anteriormente de que

tenemos recursos limitados como puede ser el tiempo. Debido a eso, optamos por crear un

exportador de geometría compleja desde el programa de modelado 3D Studio Max (8).

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 15 de 134

Este exportador nos ayudará en el cometido de generación de geometría, siendo menos

complejo que un editor profesional pero mostrando igualmente una complejidad elevada en

su implementación.

Debido a su complejidad, este exportador será considerado como parte de la solución al igual

como el motor 3D a implementar y será documentado extensamente más adelante.

En ningún momento se pretende dotar a este motor con capacidad física. Esto es así debido a

que nos queremos centrar en la implementación y gestión de las funcionalidades gráficas y las

soluciones físicas son demasiado completas tratándose de soluciones profesionales, las cuales

ya están preparadas para ser integradas en motores 3D, dejándonos solo el trabajo de

gestionar dichas físicas impidiéndonos investigar en profundidad sobre ellas.

A nivel de programación, el motor 3D será programado bajo la librería DirectX 9.0c (1) debido

a que nuestra herramienta de trabajo detallada anteriormente trabaja con el sistema

operativo Windows XP Professional y no es posible tener acceso a la librería DirectX 10 en este

sistema operativo. Para eso sería necesario cambiar de sistema operativo a Windows Vista y es

un camino que se descartó en un principio.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 16 de 134

1.5 Perspectiva general del proyecto

Hasta aquí, lo que hemos podido ver de este escrito han sido los apartados introductorios,

tanto las listas de páginas, tablas e ilustraciones como los requisitos del mismo proyecto.

Una vez llegados a este punto, empezaremos por la parte teórica del proyecto. La parte teórica

nos ocupará gran parte de dicho escrito debido a que se explican todos los conceptos con sus

diagramas correspondientes para poder llevar a cabo el trabajo que tenemos en mente.

Finalizada la sección teórica nos adentraremos en la parte práctica, que será un camino

parecido al apartado teórico con la diferencia de que en esta sección se explicaran los detalles

técnicos de los conceptos teóricos expuestos previamente.

En el apartado de resultados y gracias a que la naturaleza de nuestro proyecto ofrece

resultados gráficos, mostraremos imágenes sacadas en tiempo real de la demostración de

nuestro proyecto.

Seguiremos con las conclusiones que hemos obtenido de la realización de nuestro proyecto

seguido de las líneas de futuro, es decir, de cómo encararíamos la continuación del proyecto

una vez terminado el actual.

Finalmente nos encontraremos con un glosario de siglas técnicas y la bibliografía consultada

para nuestro proyecto.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 17 de 134

2 Fundamentos teóricos

2.1 Librería gráfica

2.1.1 Introducción

Antes de que saliese el famoso sistema operativo Windows 95, había muchos juegos que se

ejecutaban bajo MS-DOS. Estos videojuegos tenían que ser programados por sus

desarrolladores de tal forma que ellos mismos eran los encargados de la detección y acceso a

los sistemas hardware que tenía el ordenador a través del sistema operativo.

Ese hardware que debían detectar era lo que hoy conocemos como tarjetas gráficas, tarjetas

de sonido y periféricos de entrada como el teclado, ratón y joystick.

En esa época se utilizaba el estándar de la asociación VESA (9) para las tarjetas gráficas y como

estándar de sonido se utilizaban las librerías de Sound Blaster de Creative (10).

A medida que pasaron los años, los productos relacionados con los videojuegos, ya sean

tarjetas gráficas, teclados, ratones, etc. Se vieron multiplicados en número dando un gran

abanico de posibilidades en el mercado.

Los programadores debían programar la detección y acceso a un número cada vez mayor de

productos existentes, esto derivaba en plazos de desarrollo más largos y niveles de

complejidad más elevados, los cuales podían provocar más errores en la programación de la

aplicación en curso.

En los sistemas operativos Windows, el acceso directo al hardware estaba protegido por éste,

debido a ello, el acceso directo a tarjetas gráficas y tarjetas de sonido era lento e ineficaz.

Como solución a este problema surgió la librería DirectX para Windows 95.

DirectX es una librería que está por encima del hardware y que se utiliza comúnmente para el

desarrollo de aplicaciones gráficas como por ejemplo videojuegos. El sistema operativo

permite a la librería DirectX el acceso al hardware de una manera directa y eficiente.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 18 de 134

De esta manera, los desarrolladores de una aplicación gráfica como por ejemplo un videojuego

solo programan en relación a la librería DirectX, y es la propia DirectX la que se encarga de

enviar y gestionar la información a los drivers del hardware correspondiente de una forma

rápida y eficaz.

Ilustración 1 - Capas según nivel

Tres programadores llamados Alex St. John, Eisler y Eric Engstrom se plantearon convertir a

Windows 95 en una plataforma en la que los juegos pudiesen acceder a las altas prestaciones

de las tarjetas gráficas. Los tres desarrolladores crearon en noviembre de 1994 un SDK para el

desarrollo de juegos

A continuación se muestra una tabla resumen de todas las versiones que ha sufrido DirectX a

lo largo de los años. La tabla está dividida en 3 bloques, el nombre de la versión, el sistema

operativo objetivo, y la fecha de salida.

VERSIÓN SISTEMA OPERATIVO FECHA LANZAMINENTO

DirectX 1.0 30 septiembre 1995

DirectX 2.0/2.0a Windows 95 OSR2 y NT 4.0 5 junio 1996

DirectX 3.0/3.0a Windows NT 4.0 SP3

Última versión para Windows NT 4.0

15 septiembre 1996

DirectX 4.0 Nunca se lanzó

DirectX 5.0 Disponible como beta para Windows NT

5.0 que se instala en Windows NT 4.0

16 julio 1997

DirectX 5.1 1 diciembre 1997

Aplicación 1 Aplicación 2

Aplicación 3

DirectX

Sistema Operativo

Hardware

Alto nivel

Bajo nivel

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 19 de 134

DirectX 5.2 DirectX 5.2 para Windows 95 5 mayo 1998

DirectX 5.2 Windows 98 5 mayo 1998

DirectX 6.0 Windows CE para la consola Dreamcast 7 agosto 1998

DirectX 6.1 Windows 98 SE

Última versión para Windows NT 4.0

3 febrero 1999

DirectX 7.0 Windows 2000 22 septiembre 1999

DirectX 7.0a 1999

DirectX 7.1 Windows ME 16 septiembre 1999

DirectX 8.0 (RC0) 30 septiembre 2000

DirectX 8.0 (RC14) Xbox 3 noviembre 2000

DirectX 8.0a (RC14) Última versión para Windows 95 7 noviembre 2000

DirectX 8.1 (RC7) Windows XP 12 noviembre 2001

DirectX 9.0 Windows Server 2003 19 diciembre 2002

DirectX 9.0a 26 marzo 2003

DirectX 9.0b (RC2) 13 agosto 2003

DirectX 9.0c (RC0) Windows XP SP2, Windows Server 2003

SP1, y Xbox 360.

13 diciembre 2004

DirectX 9.0c Compatible con todas las versiones de

Windows en las que DirectX 9.0C (RC0)

era compatible

Primera inclusión de las DLL’s D3DX

9 diciembre 2005

DirectX 9.0c Las versiones de diciembre de 2005 y

febrero de 2006 añaden el formato XML a

algunas clases.

Agosto 2005, con

actualizaciones

bimensuales

DirectX 10.0 Exclusivo para Windows Vista 30 noviembre 2006

Tabla 9 - Versiones de DirectX

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 20 de 134

2.1.2 DirectX 9.0c

Como hemos podido ver en la descripción de la solución al problema planteado, nuestro

desarrollo se hará encima de la librería DirectX 9.0c, ya que la librería DirectX 10 es exclusiva

para Windows Vista.

Al existir en el mercado diferentes tipos de soluciones hardware a nivel tanto de periféricos

como de tarjetas integradas, ya sean gráficas o de sonido, se desarrollaron diferentes tipos de

API’s para tratarlos. Todas en su conjunto forman lo que conocemos como librería DirectX.

A continuación podemos observar una lista de las mencionadas API’s junto a una descripción

de su uso.

DirectX Graphics: Gráficos.

o DirectDraw: Usada para gráficos 2D, aunque ya en desuso.

o Direct3D: Usada para gráficos 3D.

Direct Input: Usada para capturar entrada / salida de datos (teclados, ratones, etc.)

Direct Play: Usada para la comunicación en red, aunque en desuso.

Direct Sound: Usada para la reproducción y grabación de Sonido.

o Direct3DSound: Sonido 3D.

Direct Music: Usada para la reproducción de sonidos y música en tiempo real.

DirectX Media: Usada para diferentes tipos de objetos multimedia.

o DirectAnimation: API para animación.

o DirectShow: API para tratamiento de video.

o DirectX Video Acceleration: API para aceleración de video.

o DirectX Retained Mode: API para el tratamiento de imágenes.

o DirectX Transform for Animation: API para el tratamiento de animaciones.

DirectX Media Objects: Usada para streaming como codificadores, decodificadores,

etc

Varias de estas API’s listadas anteriormente están en desuso debido a que la empresa

Microsoft está introduciendo muchos cambios en la última librería de DirectX. Estos cambios

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 21 de 134

son debidos a una fusión / sustitución de ciertas API’s en favor de otras para poder tener una

plataforma unificada con la consola Xbox 360 propiedad de Microsoft.

Un ejemplo de estas fusiones / sustituciones las podemos observar en la siguiente tabla:

ANTIGUO NUEVO

Direct Input XInput

Direct Play Xbox Live

Direct Show MediaFundation

Direct Sound XACT

Tabla 10 - Fusiones / Sustituciones

Actualmente las API’s que están más relacionadas con el mundo de los videojuegos son:

Direct3D: Usada para la representación en 3D de geometría mediante aceleración

hardware.

Direct Input: Usada para la gestión de entrada / salida de datos por parte del usuario

mediante periféricos determinados como teclados, ratones, etc.

Direct3DSound: Usada para la reproducción de sonido 3D en videojuegos.

Direct Show: Usada para la reproducción de videos aunque con la entrada de las

nuevas API’s ésta se está viendo relegada a un segundo plano

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 22 de 134

2.1.3 DXUT

DXUT proviene de la agrupación de las siglas en ingles DirectX Utility Toolkit (2), que traducido

al castellano es una librería de utilidades para DirectX.

Esta librería fue creada encima de las API’s de DirectX 9.0 y DirectX 10 para ayudar al

programador a crear utilidades gráficas, juegos y otras aplicaciones de una forma más robusta

y con mayor facilidad de implementación.

La capa que supone DXUT (2) está diseñada para ayudar a los programadores a invertir menor

tiempo en la programación y corrección de errores en aspectos redundantes y repetitivos que

podemos encontrar en una aplicación gráfica.

A continuación observaremos una ilustración donde veremos claramente dónde está situada

esta capa y cuál es su nivel de acceso por parte de las aplicaciones.

Ilustración 2 - Capas según nivel con DXUT añadido

En la ilustración anterior podemos observar como la inclusión de la librería DXUT en nuestra

aplicación es totalmente dependiente de nuestra decisión. Podemos encontrar aplicaciones

que hagan uso de ella, como también podemos encontrar aplicaciones que no deseen

utilizarla.

DXUT nos proporciona ayuda en algunas de las tareas repetitivas que comentábamos antes.

Un ejemplo de ellas lo podemos ver a continuación:

Aplicación 1 Aplicación 2

Aplicación 3

DirectX

Sistema Operativo

Hardware

Alto nivel

Bajo nivel

DXUT

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 23 de 134

Creación de una ventana

Elegir el Device de DirectX

Crear el Device de DirectX

Manejar los eventos del Device

Manejar los eventos de la ventana

Cambios entre aplicación de ventana y aplicación a pantalla completa

Cuando hablamos de Device hemos de referirnos a ese objeto genérico al cual trataremos

mediante paso de información y parámetros para conseguir resultados gráficos.

En nuestro caso, DirectX 9.0 nos proporciona un Device por cada una de las tarjetas gráficas

que tengamos en nuestro equipo. Nosotros elegimos con qué tarjeta o Device queremos

trabajar y a partir de ese momento interactuaremos con ella / el mediante parámetros para

obtener los resultados gráficos esperados.

DXUT funciona encima de las API’s de DirectX 9.0 y DirectX 10, debido a eso, una aplicación

que haga uso de de esta librería de utilidades puede hacer un uso de forma sencilla de estas

API’s. Gracias a las utilidades de DXUT, nuestra aplicación puede detectar que API tenemos

instalada en nuestro ordenador, si éste contiene la librería DirectX 10 y puede ejecutar las dos

anteriormente mencionadas, por defecto DXUT utilizará DirectX 10 para realizar todo el

trabajo.

Al mismo tiempo, DXUT nos proporciona acceso a una serie de controles mediante los cuales

podemos crear de forma sencilla una GUI para nuestra aplicación. Esto es de gran ayuda ya

que de lo contrario, el programador tendría que destinar mucho tiempo a la implementación

de una interface gráfica que permitiese al usuario interactuar con dicha aplicación.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 24 de 134

2.2 Renderizado

2.2.1 Iluminación

2.2.1.1 Introducción

En el motor 3D que estamos implementando, un aspecto fundamental de éste es la

iluminación. La iluminación la entenderemos a partir de ahora como esa técnica o conjunto de

técnicas las cuales nos permitirán dotar a una escena con propiedades lumínicas las cuales, a

su vez, nos ayudarán a interpretar mejor la información visual que estamos viendo.

El impacto de la iluminación en una escena siempre viene acompañado por los efectos de

sombreado. Esta dualidad de luz – oscuridad permite al ojo humano interpretar de manera

notablemente superior la información geométrica que se nos muestra en pantalla.

Este efecto lo podemos describir como percepción de la profundidad de la escena. No

debemos olvidar de que toda aplicación gráfica 3D tiene como resultado final una

representación mediante píxeles en una imagen plana, en nuestro caso esa imagen plana será

lo que nos muestra el monitor.

Para crear la iluminación a nivel matemático, es necesario utilizar una ecuación matemática

que nos ayude en el cálculo. De estas ecuaciones hay muchas y de complejidad variable. En

medios de información como internet al igual como con libros, podemos encontrar que no se

habla de ecuaciones como tal sino de modelos.

Nosotros en nuestro motor hemos implementado el modelo de iluminación Phong (11),

aunque también hablaremos de otro modelo más complejo llamado Oren – Nayar (12).

La iluminación que implementaremos en nuestro motor 3D estará programada mediante

Shaders, una técnica puesta en escena para permitir una mayor libertad a los programadores y

diseñadores en su labor para crear aplicaciones gráficas.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 25 de 134

2.2.1.2 Principios teóricos de la iluminación

El modelo de Phong estima la iluminación en un punto a partir de la posición de la luz, de la

normal asociada a ese punto de la superficie y de las características de esta superficie.

Inicialmente en el sistema nosotros sabremos en todo momento la posición de nuestra luz, la

normal en el punto donde vamos a hacer los cálculos y las características de ese punto. Estas

características pueden ser muchas y muy variadas. Por ahora solo nos centraremos en la

cantidad de componente especular que posee dicho punto.

A continuación podemos ver una ilustración la cual nos muestra las componentes que tiene en

cuenta el modelo de iluminación Phong:

Ilustración 3 - Componentes del Modelo Phong

Podemos observar en la ilustración anterior 3 componentes que sumadas nos dan el resultado

final. Estas componentes son Ambiente, Difusa y Especular.

Ambiente:

o Esta componente normalmente se refiere a un color homogéneo para toda la

superficie que queremos pintar. Debemos pensar en ella como ese color que

proporciona la escena a esa superficie. En muchos casos esta componente es

representada como un valor nulo o cero debido a que se deja a la componente

Difusa todo el trabajo de la representación de color.

Difusa:

o Esta componente es la poseedora de toda la información de color base que

posee nuestra superficie. Veremos posteriormente como a la hora de calcular

Ambiente Difusa Especular Resultado + + =

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 26 de 134

esta componente, se generan áreas donde el color es más original y áreas

donde el color es prácticamente negro, cosa que indica ausencia de luz debido

a la posición de ese punto con respecto a la luz que ilumina.

Especular:

o La componente especular nos da información visual de lo brillante que resulta

nuestra superficie en reacción a nuestra luz. Podemos oscilar entre la

superficie mas mate de todas, la cual tendría una componente especular de

cero, hasta la superficie más brillante de todas, que tendría una componente

especular de uno.

Vista una definición de estas tres componentes las cuales forman el modelo de Phong, a

continuación mostraremos como se realizan los cálculos necesarios para poder llegar al

resultado final que nos plantea este modelo.

Primero de todo definiremos una escena donde tendremos un píxel a iluminar, tendremos

también un emisor de luz, y un receptor de ella. Este receptor normalmente es un ojo humano

o en nuestro caso una cámara, la cámara desde donde nosotros vemos la escena.

Ilustración 4 - Escena limpia

Podemos observar en la escena anterior como disponemos de un emisor de luz, representado

por el sol de color naranja. Un receptor que pasaremos a ser nosotros imaginariamente

representado por una cara, y el componente más importante de la escena, el píxel a iluminar

que será representado por el cuadrado verde.

Este cuadrado verde representa uno de los múltiples píxeles que forman la superficie

representada por el rectángulo de color blanco. Hay que tener en cuenta de que vamos a

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 27 de 134

tratar siempre estos ejemplos como si solo estuviéramos hablando de un píxel. En la realidad,

todos los cálculos que vamos a describir en estas escenas, son calculados para todos los píxeles

que forman la superficie.

El cálculo de la componente ambiente es el más sencillo de realizar debido a que éste es

simplemente una constante que hace referencia al color que recibe el objeto actual del

ambiente. En muchos casos nos encontraremos con que esta constante es cero.

Una vez tenemos la componente ambiente del modelo de Phong, pasamos a calcular la

componente Difusa. Esta componente ya no es tan trivial como la anterior. Concretamente

esta componente se define como la intensidad que ofrece la luz emisora multiplicado por el

coseno del ángulo que forma el vector incidente que va desde la luz al píxel con la normal de

dicho píxel.

Ilustración 5 - Cálculo de la componente Difusa

Como podemos observar en la fórmula anterior, el coseno del ángulo entre el vector L y el

vector N puede ser calculado igualmente mediante el productor escalar entre estos dos

vectores. Es importante destacar el hecho de que el vector L ha de ser invertido para que el

cálculo tenga efecto tal y como se requiere, debido a esto, en la formula siguiente el vector L

aparece en negativo para que el cálculo sea correcto.

L N

Ѳ

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 28 de 134

Una vez calculada la componente Difusa del modelo de Phong, nos disponemos a calcular la

componente Especular, que será la encargada de darnos información sobre el brillo del

modelo en la escena. Para esto, es necesario calcular dos vectores más de los que ya teníamos

anteriormente.

Primero de todo necesitamos el vector V. Este vector es el vector que va desde el píxel en

cuestión hasta el receptor, que seremos nosotros o una cámara para tal efecto.

Seguidamente necesitaremos el vector R. Este vector es el vector reflexión del vector L, es

decir, si mirásemos al vector N como una línea que actúa de espejo, el vector R es la reflexión

del vector L en ese supuesto espejo.

Por lo tanto, teniendo los vectores V y R calculados, ya podemos calcular el ángulo que hay

entre ellos dos y así poderlo aplicar a la fórmula. Seguidamente veremos una ilustración sobre

lo que hemos expuesto:

Ilustración 6 - Cálculo de la componente Especular

Partiendo de la ilustración anterior y de la fórmula que acabamos de mostrar, podemos

observar como la componente especular es calculada a partir de la intensidad de la luz y una

constante especular multiplicadas por el coseno del ángulo que forman los vectores V y R.

Hemos de destacar que el resultado del coseno del ángulo, está elevado a la enésima potencia,

esto es así ya que de esta manera se proporciona la intensidad deseada al efecto de la

componente especular.

L N

Ѳ R

V

σ

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 29 de 134

Fijémonos en que hemos cambiado el coseno del ángulo por el producto escalar entre V y R,

pues así es como el cálculo es más eficiente.

Cuando nos referimos a la variable n que está elevando al cálculo entre vectores V y R, siempre

nos hemos referido a que esta variable proporciona un valor de intensidad a la componente

especular. A continuación mostraremos un resultado visual de dicha variable para que se

puedan apreciar los cambios según los valores de n.

Ilustración 7 - Diferentes valores de n para la componente especular

Una vez calculada la componente especular, solo nos queda substituir los resultados en la

fórmula del modelo de Phong y obtener el resultado, este resultado nos vendrá dado como

color, este color será el que nosotros asignaremos al píxel representado por el cuadrado verde

que hemos estado viendo en todas las ilustraciones anteriores.

Al principio de este punto, hemos hablado sobre otro modelo, mucho más costoso de realizar

que se llama Modelo Oren – Nayar. Este modelo parte del modelo Phong pero añade unos

cálculos basados en el principio de que las superficies reales no son completamente planas.

El modelo Oren – Nayar parte de la base de que toda superficie rugosa puede ser representada

por infinidad de micro caras con diferentes orientaciones. Estas orientaciones son las que

proporcionan en un resultado final, la sensación de mayor realismo.

Este modelo parte del modelo Phong, añadiendo unos cálculos que son los que, aún siendo

teóricamente simples, a nivel de computación resultan muy costosos. Veamos un ejemplo

comparativo entre Phong y Oren – Nayar:

n = 1 n = 60

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 30 de 134

Ilustración 8 - Diferentes tipos de modelos de iluminación

Como podemos comprobar en la ilustración anterior, el modelo Oren – Nayar se acerca más al

resultado visual del objeto real, pero como contrapartida tiene mayor coste computacional

que el modelo Phong.

En la fórmula anterior, podemos observar como la fórmula derivada del modelo de Phong es

más complicada. Necesitamos calcular los parámetros A y B al igual como el cálculo de un seno

y una tangente que son los que realmente añaden complejidad computacional al modelo.

Podemos fijarnos de que si hacemos A = 1, y B = 0, entonces simplificando la ecuación

obtenemos la siguiente fórmula:

Que es precisamente el modelo de Phong. Consecuentemente, entenderemos que con valores

predeterminados como los que hemos hablado, es posible pasar del modelo Oren – Nayar al

modelo Phong sin necesidad de cambiar la ecuación.

Modelo Real Modelo Phong Modelo Oren - Nayar

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 31 de 134

A continuación podremos observar cómo puede variar el resultado visual del modelo Oren –

Nayar cambiando el parámetro σ de las componentes A i B.

Ilustración 9 - Diferentes resultados según parámetros A i B

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 32 de 134

2.2.2 Luces

2.2.2.1 Introducción Como ya hemos comentado, las luces en términos de representación 3D son emisores de luz.

Estos emisores de luz son representados mediante puntos matemáticos en el espacio. De esta

manera y dependiendo siempre de cómo se traten estos puntos, podremos tener luces en

nuestra escena.

A continuación presentamos una lista con una breve descripción de los tipos de luces que

normalmente se emplean en aplicaciones 3D. Puede haber más pues el número no está

restringido, pero con estas que se presentan a continuación se pueden hacer la gran mayoría

de las iluminaciones de una escena.

Omnidireccional

o Las luces omnidireccionales son aquellas que representadas por un punto

matemático en el espacio emiten luz en todas direcciones. El cálculo de la luz

entonces será llevado a cabo desde el centro de la luz hasta las superficies de

los objetos a iluminar. Un símil sencillo para este tipo de luces seria el astro

Sol, este astro emite luz en todas direcciones desde su centro. Y los objetos

son iluminados como tal.

Foco

o Las luces de tipo Foco son aquellas que desde su origen emiten luz en todas

direcciones pero con una restricción de ángulo. Esta restricción es la causante

de crear un efecto de iluminación en forma de cono, estando la punta de este

cono en el centro emisor de luz, y la parte más ancha de este cono en el punto

más alejado del centro de la luz. Un símil para este tipo de luz seria una

linterna, las linternas iluminan en un cono desde su origen.

Direccional

o Las luces direccionales son una aproximación matemática que se realiza para

representar luces que están a una distancia considerable del objeto a iluminar.

Esto provoca que donde antes teníamos luz emitida desde un punto y,

habiendo restricción o no de esa luz por parte del ángulo, todos los rayos

emitidos desde el origen eran radiales. En cambio, con esta aproximación

tenemos que los rayos de las luces direccionales son paralelos. Un símil para

entender estas luces seria una luz colocada muy lejos de nosotros pero que en

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 33 de 134

vez de emitir sus rayos de luz desde un punto matemático, lo hace desde un

plano perpendicular a nosotros. De esta manera, con estas luces no existen

rayos radiales, sino paralelos entre ellos.

2.2.2.2 Luces Omnidireccionales En el diseño e implementación de nuestro motor, hemos elegido este tipo de luces como la

más adecuada para poder tener tanto iluminación local como una correcta sensación de

sombreado en la escena.

Como hemos visto en la introducción, existen tres tipos de luces básicas para un motor 3D. De

estas tres, nosotros solo hemos implementado las luces Omnidireccionales. Esto es así debido

a que las luces de tipo Foco son simplemente luces Omnidireccionales a las que se les ha

introducido una restricción, y las luces direccionales serán suplidas por otro tipo de

iluminación que veremos más adelante.

Las luces omnidireccionales de nuestro motor disponen de los parámetros necesarios como

para poder cambiar su color y su atenuación.

El hecho de cambiar de color una luz es una cosa realmente trivial, pues simplemente

indicaremos que el color de la luz sea uno a nuestra elección.

Más complicación viene cuando hablamos de atenuación. La atenuación de una luz viene de

las propiedades físicas de las luces que indican que a mayor distancia, la intensidad de ésta es

menor sobre los objetos. Es necesario comentar que no es la finalidad de este proyecto

representar fielmente los efectos de la luz, pues estos podrían llevar más cálculos de los

necesarios para una aplicación en tiempo real.

La atenuación de nuestra luz omnidireccional vendrá dada por dos parámetros, estos

parámetros indicaran la distancia desde el origen hasta donde empieza la atenuación y la

distancia hasta que la atenuación es total, es decir, donde ya no llega la luz.

En la ilustración siguiente veremos un diagrama gráfico de cómo se comportan estos

parámetros dada una luz omnidireccional cualquiera.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 34 de 134

Ilustración 10 - Diagrama luz omnidireccional

En la ilustración anterior podemos observar de manera clara y concisa cuales son los dos

parámetros de la luz (flecha verde y flecha roja) que conforman los dos radios de la luz.

Asimismo podemos observar la extensión del diagrama a una representación en dos

dimensiones sobre cómo se comporta la luz a media que ésta se aleja de su centro emisor.

Podemos observar como desde el origen hasta el primero radio, la luz es máxima en todo su

recorrido, y como desde el primer radio hasta el segundo, la luz pasa de intensidad máxima a

intensidad mínima linealmente.

2.2.3 Texturas

2.2.3.1 Introducción

Un textura se define como un mapa de bits con información de color aplicada a una geometría

en una escena 2D / 3D. Por lo tanto, entenderemos como textura a partir de ahora ese fichero

Luz Omni

Radio exterior

Radio interior

Pendiente de atenuación

Área máxima iluminación

Área atenuada

Y+

X+

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 35 de 134

especial que nos contiene un mapa de colores específicos que nosotros aplicaremos de la

forma que mejor nos convenga en los modelos que utilizaremos en nuestro motor 3D.

Es importante introducir al lector de que esta información de color, no tiene porqué ser

interpretada siempre como un color, puede ser interpretada esa información como valores

numéricos para ciertos cálculos que veremos con posterioridad.

Una vez comentado que es una textura como tal y si recordamos las variables en el modelo de

Phong, no es de extrañar que en nuestro motor 3D utilicemos como fuente para esas variables

texturas destinadas a esa función.

Si recordamos el modelo de Phong, este nos decía que:

En el modelo que nosotros usaremos, cuando hablemos de Difusa, tendremos una textura que

será la encargada de proporcionarnos por cada píxel la información numérica o de color

relacionada con la componente Difusa. Lo mismo pasará con otros tipos de texturas.

En nuestro modelo de iluminación, tendremos cinco variables. A continuación mostramos una

lista con una pequeña introducción sobre cada una de ellas:

Componente Difusa

o La textura que nos dará la componente Difusa es una textura de color de 24

bits repartidos entre 3 grupos de 8 bits de Rojo, Verde y Azul más un cuarto

grupo de 8 bits para la componente transparente. Hemos de imaginarnos esta

textura como una fotografía con color real para colorear nuestros modelos en

escena.

Componente Especular

o La textura que nos proporciona la componente Especular es una textura como

la anterior, pero con la diferencia de que el color que nos muestra ésta es un

valor número entre cero (0) y uno (1) que nos indicará la cantidad de

componente Especular que se puede aplicar en ese punto en concreto.

Componente Normal

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 36 de 134

o La textura de componente Normal es una textura que no nos proporciona un

color sino un valor. Ese valor será interpretado como si de un vector se tratase

y así poder extraer una normal en el punto solicitado.

Componente Emisiva

o La textura de componente Emisiva es esa textura que nos dará información

sobre en qué puntos del modelo a tratar se está emitiendo luz desde su

superficie. Más adelante se especificará mejor la información relativa a esta

textura.

Componente Mapa de Luces

o La textura de Mapa de Luces es una textura que contiene información en

forma de color sobre la iluminación estática ambiente y que será aplicada al

objeto en cuestión.

2.2.3.2 Componente Difusa

Como ya hemos comentado, la componente Difusa es posiblemente la más sencilla de

entender, pues la información nos llegará directamente de su información de color.

La componente Difusa es la encargada de darle color a los objetos que queremos tener en

nuestra escena 3D. Por ello, el color base que tengan nuestros objetos vendrán dados de base

por esta texturas.

Ilustración 11 - Muestras de texturas con componente Difusa

Como podemos observar en la ilustración anterior, mostramos tres texturas al azar utilizadas

en nuestro motor para colorear los objetos requeridos en la escena.

Concrete1_d.tga Stones1_d.tga Lava1_d.tga

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 37 de 134

Es importante destacar también que el formato de estas texturas en concreto es de 24 bits por

píxel, 8 para el canal Rojo, 8 para el canal Verde, y 8 para el canal Azul. No tienen canal Alpha

para poder hacer transparencias porque no es requerido en esta fase del motor 3D.

Fijémonos también en el formato del nombre. El nombre de toda textura Difusa contiene una

cola característica e igual para todas ellas. Esta cola son los caracteres ‘_d’, estos caracteres

determinan de forma rápida el que esa textura en concreto sea de tipo Difusa.

2.2.3.3 Componente Especular

La componente Especular nos vendrá dada por un tipo de textura igual al de la componente

Difusa, es decir, la misma estructura en tipo de bits para el color, 24 bits divididos entre tres

pares de 8 bits para los tres canales de color.

El significado de la componente Especular es el de dejar o no dejar brillar la luz dependiendo

de la componente Especular en la superficie del objeto que estemos tratando. Para ello

primero veremos un conjunto de texturas de componente Difusa con sus respectivas texturas

de componente Especular para que podamos tener en mente su utilidad en el futuro.

Ilustración 12 - Texturas de componente Especular con sus respectivas Difusas

Panel1_d.tga

Panel2_d.tga

Panel1_s.tga

Pane2_s.tga

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 38 de 134

En la ilustración anterior podemos ver las texturas de componente Especular como van junto a

sus homónimas en cuanto a componente Difusa se refiere. Fijémonos en que solo se podrá

representar brillo allí donde la componente Especular sea más clara y no se podrá representar

brillo allí donde sea más oscura.

Este aspecto nos proporciona un cierto control sobre qué áreas pueden brillar y qué áreas no

en un objeto, aparte del poder decir si hay brillo o no, con la tonalidad de la componente

Especular. También podemos controlar la cantidad de brillo, dando mucha más libertad al

diseñador para poder mostrar objetos más naturales en escena.

2.2.3.4 Componente Normal

La componente Normal nos viene dada por una textura de 24 bits en la cual no codificaremos

color sino valores numéricos que se van a interpretar como componentes de un vector.

Cada píxel de la textura que representa la componente Normal se interpretará como un vector

que indicará la dirección de la Normal que se utilizará para hacer los cálculos de la iluminación.

Esto es así debido a que si se define una normal por vértice en los triángulos de un modelo,

cuando este modelo es rasterizado y convertido a imagen compuesta por píxeles, esta Normal

es interpolada de vértice a vértice. Esto nos ofrece una interactuación muy vaga a la hora de

calcular la iluminación en esos píxeles.

Debido a ese límite en la iluminación, se optó por dar una normal píxel a píxel en la superficie

de un modelo para así dotar a dicho modelo de una iluminación mucho más precisa y con

resultados ampliamente superiores en cuanto a calidad visual.

A continuación mostraremos una ilustración que comparará el hecho de dar normales a los

vértices y dejar que éstas se interpolen o proporcionar una textura para que ésta dé las

normales píxel a píxel y podremos comprobar cómo el control otorgado por esta última

técnica es mucho más potente.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 39 de 134

Ilustración 13 - Interpolación trivial entre normales por vértice

En la ilustración anterior podemos observar una interpolación trivial entre las normales de dos

vértices, si entendemos los dos vértices como los dos cuadrados lilas con sus respectivas

normales lilas. Entonces veremos cómo una vez rasterizado el triángulo y convertido en

píxeles, estos píxeles interpolados reciben a su vez una interpolación entre las dos normales

anteriores, quedando en este ejemplo una interpolación trivial debido a que todas ellas son la

misma.

Ilustración 14 - Interpolación entre normales por vértice

En la ilustración anterior, vemos un ejemplo de interpolación que ya no es trivial. Básicamente

es un ejemplo para mostrar cómo se comporta el sistema cuando las dos normales no son la

misma normal.

Igualmente podemos observar como los píxeles resultantes obtienen una normal interpolada

entre las dos normales que provienen de los dos vértices.

Ilustración 15 - Interpolación mediante textura de normales

Normal1_n.tga

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 40 de 134

En la ilustración anterior, la interpolación ya no viene dada por las normales entre vértices,

sino que es rescatada píxel a píxel de la textura de normales. Esto nos da un control a nivel de

píxel de cómo queremos las normales en cada momento. Mediante esta técnica es posible

dotar de gran realismo a la escena sin comprometer el rendimiento de ésta por la necesidad

de añadir más triángulos a los objetos que vemos en dicha escena.

2.2.3.5 Componente Emisiva

La componente emisiva la debemos entender como si saliese luz de la propia superficie del

objeto al que le estamos aplicando la textura emisiva.

Recordemos que las luces dinámicas que hemos implementado son puntos matemáticos en el

espacio que emite rayos de luz. Con estas luces nos es imposible representar posibles

emisiones de luz a lo largo de superficies.

Con esta información que tenemos ya podemos deducir dos cosas. La primera es que al

tratarse de una emisión que no forma parte de las luces dinámicas, este tipo de iluminación no

es dinámico y por lo tanto solo servirá para dotar a la escena de un efecto parecido a algo que

podamos ver en la realidad. La segunda es que esta iluminación solo la veremos en la fuente,

es decir, en esa superficie en la cual apliquemos la textura emisiva, no se representará ni se

proyectará encima de demás superficies cercanas.

Para continuar con la explicación, mostraremos una ilustración donde veremos este tipo de

componente aplicada a un objeto y cómo parece que emita luz siendo un efecto visual y no

una emisión de luz dinámica.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 41 de 134

Ilustración 16 - Componente emisiva aplicada a un objeto

Como podemos ver en la ilustración anterior, el efecto emisivo está marcado en el objeto

mediante flechas de color naranja. Este efecto nos proporciona una percepción la cual nos

hace creer que se está emitiendo luz desde esas partes del objeto. Simplemente es un efecto

óptico que en ningún caso llegara a ningún tipo de iluminación dinámica conocida.

2.2.3.6 Componente Mapa de Luces

La componente de Mapa de Luces viene a representar en una textura la luz que recibe ese

objeto del ambiente. Concretamente este efecto es muy costoso de calcular en tiempo real y

por eso, se considera como un efecto off-line o pre-calculado. Debido a esta limitación, los

mapas de luces se acostumbran a utilizar en objetos estáticos y que su posición no cambiará

con el tiempo.

Para entender este efecto, es necesario imaginarse como el color base del objeto es

multiplicado por el color del Mapa de Luces, aplicando de esta manera una iluminación global

pero estática al objeto en cuestión.

Para verlo más claro mostraremos a continuación una ilustración que nos servirá para ver el

efecto en funcionamiento.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 42 de 134

Ilustración 17 - Efecto del Mapa de Luces

En la ilustración anterior podemos observar como el color base, o lo que en su defecto para

nosotros será el color proporcionado por la componente Difusa, es multiplicado por el Mapa

de Luces. Este mapa nos indica donde hay luz y donde no la hay, asimismo también nos indica

de qué colores es la luz.

Hemos de tener en cuenta de que el hecho de aplicar este efecto nos condiciona a tener en el

objeto en concreto como mínimo un set de coordenadas de textura para el Mapa de Luces,

que será independiente de cualquier otro set que utilicemos en ese objeto.

2.2.3.7 Manager de texturas

El manager de texturas es una estructura de datos encargada de almacenar y gestionar las

texturas que nosotros necesitaremos durante nuestra aplicación.

Esta estructura nos permite almacenar todas las texturas que vayamos a utilizar con la

característica de mantener únicas las texturas almacenadas. Esto quiere decir que de cada una

de las texturas, solo tendremos una en memoria y solo una, no pudiendo estar repetida de

ningún modo.

Gracias a esta característica, aseguramos un consumo de memoria responsable a lo largo de la

vida de la aplicación a nivel de gestión de texturas. Por otro lado, el gestor de texturas nos da

acceso directo a estas texturas siempre que necesitemos de ellas.

Las texturas almacenadas en el gestor de memoria pueden ser de los formatos conocidos

habituales, esto implica a texturas de tipo BMP, JPG, TGA, PNG, DDS, etc. Para el programador

Difusa Mapa de Luces Resultado

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 43 de 134

que utilice este gestor, será completamente transparente cualquiera de los formatos sea

utilizado.

Ilustración 18 - Diagrama del Manager de Texturas

Como podemos observar en la ilustración anterior, solo existe una copia por cada textura en

memoria, al mismo tiempo existe un sistema de relación entre nombre de la textura y su Id

para que resulte mucho más fácil buscar la textura solicitada.

Asimismo podemos observar como a nivel de aplicación, ésta interactúa con el manager

siéndole transparente el funcionamiento interno de éste.

Textura 1 Nombre 1

Texturas en memoria

Relación Nombre - Id

Nombre 2

Nombre 3

Nombre 4

Textura 2

Textura 3

Textura 4

id

id

id

id

Manager de Texturas

Ap

licac

ión

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 44 de 134

2.2.4 Materiales

2.2.4.1 Introducción

Podríamos definir un material como una agrupación de texturas para intentar recrear un

efecto visual de la realidad.

Concretamente para nosotros, esta agrupación de texturas se compone de una textura de

cada una de las que anteriormente hemos hablado en el apartado de texturas. Esto supone

que cada uno de nuestros materiales tendrá una textura para ofrecer color de base al objeto,

una textura que nos ofrecerá información de las normales a nivel de píxel, una tercera textura

que nos ofrecerá información de brillos o componente especular a nivel de píxel, una textura

que nos ofrecerá una componente emisiva y para finalizar una quinta textura que nos dará

información de luz global estática aplicada a nuestro objeto.

Si observamos la ilustración siguiente, podremos comprobar una idea básica de lo que es en

nuestro motor 3D un material para un objeto.

Ilustración 19 - Estructura de un material

Como podemos observar en el diagrama anterior, un material está formado por parámetros de

dicho material y cinco texturas asociadas a ese material que lo definirán como tal en nuestro

motor.

Parámetros

Texturas

Material

Difusa Especular

Normal Emisiva

Mapa de Luces

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 45 de 134

Cada material tiene sus propios parámetros, estos parámetros le diferencian de los demás

dotándolo de características como escalado, desplazamiento y shader a utilizar.

2.2.4.2 Componentes de un material

Las componentes de una material son aquellas texturas que lo componen. Concretamente en

nuestros materiales tenemos cinco componentes, estas son: la componente Difusa, la

componente Especular, la componente Normal, la componente Emisiva y la componente de

Mapa de Luces.

Todas ellas en un material definen una textura a modo de poder ser utilizada en tiempo real

dentro del motor 3D. Si bien es necesario poner las cinco componentes para cada uno de los

materiales, cabe la posibilidad de que no necesitemos en todo momento alguna de las

componentes.

Por ejemplo se nos podría plantear el caso en que no necesitásemos la componente Emisiva a

la hora de representar en pantalla un objeto. Para paliar esta falta de necesidad, los materiales

utilizan unas texturas por defecto para no interferir en el modo de gestión de los shaders.

Estas texturas por defecto son texturas generadas para tal efecto las cuales son de diminutas

dimensiones, concretamente de 2 x 2 píxeles, para no sobrecargar el sistema con memoria

inútil.

2.2.4.3 Parámetros de un material

Los parámetros de un material definen como se comporta este en el motor 3D. Para ello aquí

tenemos un esquema de cómo se comportan los tres parámetros que podemos definir en un

material.

Escalado

o Este parámetro nos permite repetir las texturas de un material a lo largo de

una superficie tantas veces como queramos. Es útil para dotar de mayor

definición a superficies sin necesidad de cargar una textura muy grande en

memoria.

Desplazamiento

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 46 de 134

o Extremadamente útil para mover las texturas de un material a lo largo de una

superficie en la dirección que nosotros queramos. Hay que comentar que, al

igual como en el escalado, todas las texturas que conforman el resultado final

de la superficie se mueven a la misma velocidad.

Shader

o Con este parámetro le indicamos al material qué shader ha de utilizar para

renderizar dicho material.

2.2.4.4 Manager de materiales

El manager de materiales es aquel gestor encargado de gestionar los materiales que vamos a

tener en pantalla en todo momento.

Un material, cuando es definido por el artista o diseñador, toma un carácter de unicidad. Esto

quiere decir que no habrá más de un material igual dentro del manager de materiales al igual

como pasaba con las texturas.

Ilustración 20 - Diagrama del Manager de Materiales

Material 1

Materiales en memoria

Identificación

id

id

id

Manager de Materiales

Ap

licac

ión

Manager de Texturas

id Material 2

Material 3

Material 4

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 47 de 134

En la ilustración anterior podemos observar como el manager de materiales interactúa con el

manager de texturas para poder dar accesos a estas en todo momento. Paralelamente a todo

esto, la aplicación puede acceder a la interfaz tanto del manager de texturas como al de

materiales.

Esto nos proporciona un control global de los recursos a nivel de la aplicación, pues esta puede

decidir si acceder al manager para que este gestione materiales y texturas al mismo tiempo, o

por lo contrario, acceder al manager de texturas para obtener acceso a texturas individuales.

2.2.5 Render Targets

Normalmente cuando de aplicaciones 3D hablamos, siempre es necesaria una área de

memoria donde nuestra aplicación generara el resultado visual final y lo mostrará para que los

usuarios lo podamos ver.

A este proceso se le llama renderizar una escena y su finalidad es la de mostrar el resultado

por pantalla. El renderizado de la escena nos deja el resultado visual en una porción de

memoria del sistema llamada Back buffer. Este Back buffer es como una área de trabajo para

nuestra aplicación la cual monta la escena y nos muestra un resultado final

Fijémonos en el diagrama siguiente:

Ilustración 21 - Diagrama de renderizado

Renderizado

Back buffer

Front buffer

El renderizado utiliza el área de Back buffer como área de trabajo

Del Back buffer se pasa al Front buffer y este es mostrado al usuario

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 48 de 134

En la ilustración anterior podemos observar como el renderizado utiliza el Back buffer como

area de trabajo para que posteriormente sea mostrado al usuario.

A partir de aquí podemos definir como Render Target ese área de trabajo temporal que

nosotros le indicaremos a la librería gráfica para poder trabajar con más áreas de memoria.

Esto nos servirá para poder guardar o mantener información valiosa en forma de imagen para

posteriormente ser utilizada en procesos que lo requieran.

Ilustración 22 - Diagrama de renderizado con Render Targets

Fijémonos en la ilustración anterior como el renderizado puede utilizar el área del Back buffer

o por el contrario puede utilizar un Render Target definido por nosotros como área de trabajo.

Independientemente de cuál sea el área de trabajo, ésta podrá ser presentada ante el usuario

final como resultado del render.

Debe mencionarse que es muy recomendable renderizar la geometría de la escena junto a su

color original en el Back buffer y dejar a los otros Render Targets para procesado de otro tipo.

Esto es así debido a que el Back buffer tiene un sistema nativo de antialiasing que no es

soportado en los otros Render Targets y por tanto no es calculado en estos últimos.

Los Render Targets en una aplicación pueden ser configurados de muchas maneras. Se les

puede definir las dimensiones de ancho y alto, el tipo de píxel que será utilizado, si tienen o no

tienen buffer de profundidad, etc.

Renderizado

Back buffer

Front buffer

El renderizado utiliza el área de Back buffer como área de trabajo

Del Back buffer se pasa al Front buffer y este es mostrado al usuario

Render Target 1

Render Target 2

Render Target N

Los Render Targets pueden usarse para operaciones o pueden copiarse en el Back buffer para ser mostrados en pantalla

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 49 de 134

2.2.6 Efectos Especiales

2.2.6.1 Introducción

Los efectos especiales son esos efectos que se añaden a la escena aparte del color de base que

estamos dando a los objetos.

Efectos de este tipo podrían ser el efecto de la niebla y el efecto Glow. Estos efectos se añaden

a la escena para dotar a esta de una mayor integridad y realidad, aunque en muchas

ocasiones, estos efectos no son muy reales pero empujan las sensaciones de los usuarios hacia

estados donde nosotros queremos llegar.

Es de esperar entonces que gracias a estos efectos podamos transmitir sensaciones de calor,

frio, iluminación intensa, etc. Aunque éstas no sean completamente reales

2.2.6.2 Niebla

La niebla es un efecto visual recreado en las aplicaciones 3D que intenta igualar lo que nos

muestra en la vida real el efecto meteorológico de la niebla, es decir, un desvanecimiento de

los objetos a través de la distancia que nos separa de ellos.

Normalmente y debido a los factores reales que tiene la niebla en la vida real, ésta siempre

obtiene unos colores grisáceos tirando a blancos mediante los cuales se hace el

desvanecimiento de los objetos.

En nuestro motor 3D, hemos experimentado con este efecto meteorológico dotando a la

niebla de unos parámetros variables como color y distancias.

Las distancias en nuestra niebla nos indican dos valores muy importantes, el inicio del

desvanecimiento de los objetos en la niebla, y el desvanecimiento completo de estos.

Fijémonos a continuación en la ilustración siguiente como dependiendo de estos parámetros

se puede llegar a crear diferentes densidades de nieblas en una escena.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 50 de 134

Ilustración 23 - Diagrama de ejemplos de distancias de Nieblas

Como podemos observar en el diagrama anterior, existen dos distancias, la distancia en el que

la niebla empieza a ser presente, y la distancia en el que la niebla esta 100% presente y no se

puede distinguir nada.

Aparte de las distancias en la niebla, también tenemos otro parámetro con el que hemos

experimentado que ha sido el color de la niebla.

En un ambiente normal, la niebla tiene un solo color. En nuestro motor, la niebla tiene dos

colores, uno de inicio y otro final. Estos dos colores se aplican y son interpolados a través de la

niebla desde la distancia inicial y final de ésta.

Ilustración 24 - Diagrama de colores de una Niebla

Inicio Niebla Final Niebla

Color cercano Color lejano

Inicio Niebla Final Niebla

Near Clip Far Clip

Inicio Niebla Final Niebla

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 51 de 134

En la ilustración anterior podemos observar el experimento que llevamos a cabo en nuestro

motor 3D, es decir, el hecho de implementar una niebla que se basa en dos colores y no en

uno solo. Esto nos da más parámetros con los que jugar en una escena y nos permite

ambientar ésta de forma más flexible.

Si por el contrario deseamos una niebla como la de toda la vida, siempre podemos optar por

definir los dos colores de la niebla como el mismo color.

2.2.6.3 Glow

El efecto conocido como Glow o halos de luz son presentes en muchos sitios de la vida real y

proporcionan una visión enriquecida de las emanaciones de luz que existen en una escena

concreta.

Cuando hablamos de gráficos generados por ordenador, el brillo proporcionado por las luces

de éste son limitadas y no ofrecen los rangos que se pueden obtener en el mundo real. Es por

eso que los efectos de Glow son la única manera de entender una emanación de luz en un

mundo 3D como una fuente lo suficientemente potente de luz.

Concretamente el Glow de nuestro motor 3D proviene de dos fuentes paralelas las cuales lo

provocan. Estas fuentes son, los píxeles de mayor intensidad de un umbral y de la componente

emisiva que tienen todos los objetos que hemos descrito en apartados anteriores.

Ilustración 25 - Diagrama de flujo del proceso Glow

Como podemos observar en la ilustración anterior, existen las dos fuentes citadas para poder

realizar el Glow. Estas dos fuentes son combinadas para podérseles aplicar el proceso de Glow.

Píxeles con una intensidad superior a un

umbral

Componente

Emisiva

Proceso de Glow

Componente de Glow

resultante lista para aplicar

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 52 de 134

Finalmente obtenemos el Glow resultante que aplicaremos como post proceso en el render

final.

A continuación mostraremos de forma teórica cómo es generado el Glow a nivel de píxel.

Hemos de tener presente que el Glow se realiza normalmente en dos pasadas, una vertical y

otra horizontal. Veamos el diagrama siguiente:

Ilustración 26 - Proceso teórico de Glow a nivel de píxel

Como podemos observar en el diagrama anterior, el proceso de Glow teórico es realizado

mediante dos pasos, uno horizontal y otro vertical. Este proceso es así debido al mejor

entendimiento teórico de este mismo, pues en la realidad, no puede llevarse a cabo tal cual

porque en las tarjetas gráficas es imposible escribir en un píxel vecino mientras se está

tratando el píxel actual.

El primer paso es hacer la pasada horizontal, la cual extiende el color a los píxeles vecinos con menor intensidad según distancia.

El segundo paso es hacer lo mismo pero en vertical, es necesario partir del resultado anterior para un efecto correcto.

El efecto final es el color del píxel tratado esparcido por los píxeles vecinos, dando el efecto correcto de Glow o halo de luz.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 53 de 134

Debido a esto último, debemos entender como que un píxel no esparce su color a sus vecinos,

sino que este píxel, acumula una cantidad de color en sí mismo de sus vecinos. De esta manera

el píxel que estamos tratando lo que hace es acceder a sus vecinos y leer, que no escribir, su

valor de color y aplica un factor de multiplicación a éste para poder generar una media.

En nuestro motor 3D, hemos implementado un proceso de Glow que desarrolló en el año 2003

un señor que se llamaba Masaki Kawase (13). Este algoritmo parte de la base de intentar hacer

las dos pasadas en una sola, apoyándose en la capacidad que tienen las tarjetas gráficas hoy en

día de interpolar colores entre píxeles a la hora de leerlos de una texturas.

Lo que hace el algoritmo es recuperar el valor de un pixel ficticio que resulta estar entre medio

de dos píxeles de la textura. Como resultado obtenemos un valor interpolado entre píxeles

colindantes y obtenemos un efecto Glow que aunque no sea tan perfecto como en el caso

anterior, si que este último algoritmo hace que obtenemos mayor rendimiento.

Ilustración 27 - Diagrama de funcionamiento del Glow mediante Kawase

La lectura de píxeles se hace en el primer paso en la intersección de los píxeles colindantes. Esto nos da un color interpolado entre estos píxeles.

En el segundo paso, la lectura de píxeles se traslada a la siguiente intersección de píxeles, la cual nos da un color interpolado entre estos últimos.

En el tercer paso, ocurre exactamente lo mismo que en los anteriores dos, a excepción de que en cada paso sucesivo, el factor multiplicativo del color es menor.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 54 de 134

2.2.7 Estructura Pipe-Line

Ilustración 28 - Diagrama del Pipe-Line utilizado para renderizar

Occlusion Query

Frustrum Cube

ShadowMap

Sort Geometry

Render Cube

ShadowMap

Render Self

Ilumination

Render Skybox

Render Main

Geometry

Render Bright

Render Self + Bright

Glow Loop 1

Glow Loop 2

Render Main + Glow

Aplicamos una occlusion query a la geometría para solo tratar aquellos objetos que se ven.

Mediante frustrum culling elegimos y descartamos geometría para las 6 caras del Cube ShadowMap.

Ordenamos la geometría mediante materiales para que el impacto de rendimiento sea mínimo.

Renderizamos las 6 caras del Cube ShadowMap aplicando a cada pixel profundidad en vez de color.

Renderizamos la geometría de la escena mediante la componente emisiva de estas.

Renderizamos el SkyBox y nos aseguramos de que no se limpie el BackBuffer después.

Renderizamos la geometría de la escena principal en el BackBuffer sin haber sido limpiado antes.

Extraemos del render anterior mediante un umbral el brillo de los píxeles.

Sumamos los resultados del render Self más el render de Bright.

Aplicamos el algoritmo de Glow - Kawase con sus respectivas vueltas.

Juntamos todos los resultados anteriores para formar la imagen resultante.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 55 de 134

Como podemos observar en el diagrama anterior, la estructura pipe-line del renderizado en

nuestro motor sigue los pasos previamente mostrados.

Entre estos pasos podemos observar diversos grupos de acciones y procesos determinados por

diferentes colores, a continuación detallaremos sus funciones:

2.3 Tratamiento de eventos

2.3.1 Trama

Podemos definir una trama como un objeto contenedor de información la cual somos nosotros

quien decidimos su formato para su correcta interpretación.

Preparación de la geometría

o En estas etapas podemos observar como mediante algoritmos de

descarte y algoritmos de oclusión de geometría podemos determinar que

objetos estamos viendo y cuáles no. Gracias a este hecho, estamos

descargando al renderizado posterior de trabajo innecesario o

redundante.

Calculo de sombras

o Esta fase se caracteriza por el cálculo de sombras, este cálculo se hace

mediante la técnica Shadow Mapping, la cual hemos modificado e

implementado para que sea capaz de generar una sombra a partir de una

luz omnidireccional con degradado de intensidad según la distancia.

Renderizado principal

o En esta fase simplemente renderizamos la geometría mediante los

algoritmos de iluminación que hemos diseñado para nuestro motor 3D.

Post producción de efectos

o En esta fase se calculan los efectos de post producción como el Glow. Es

importante destacar que antes de esta etapa seria poco fructífero calcular

estos efectos.

Resultado final

o Es la suma de todos los procesos que hemos llevado a cabo.

Verde

Naranja

Azul

Lila

Rojo

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 56 de 134

En nuestro motor 3D, utilizamos las tramas como objetos para poder transportar información

allí donde nosotros la necesitamos. Estos objetos están diseñados para almacenar cualquier

tipo de dato y cualquier combinación de estos de una manera lineal y estructurada.

Una de las limitaciones en la naturaleza de este tipo de objetos es que debido a la infinidad de

posibles órdenes y tipos de datos distintos, seremos nosotros los programadores los

encargados de asegurar un protocolo de lectura coherente al orden de escritura de

información en dicha trama.

Si no se conserva este protocolo para cada tipo de caso que tengamos en un futuro, puede

derivar en la lectura de información corrupta debido a que no se ha respetado la estructura

interna de la trama que se ha marcado en su inicial escritura.

Veamos un ejemplo de cómo se respeta el orden en una trama:

Ilustración 29 - Diagrama de Lectura / Escritura en una trama

1002 1002

Escritura de un primer valor en la trama, este ocupa las primeras posiciones dentro de la trama.

Escritura de un segundo carácter dentro de la trama. Es un tipo de dato diferente pero se almacena de la misma manera.

‘A’

Escritura de un tercer miembro en coma flotante, que será introducido en la parte posterior de la trama.

Lectura del primer miembro, este miembro es un número y como tal se lee de la trama esperando ese tipo de dato.

Lectura del segundo miembro, pero en este caso, se lee un valor esperando que sea de otro tipo lo cual es un error.

4.352

1002

1002

1002

1002

‘A’

‘A’

‘A’

‘A’

4.352

4.352

4.352

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 57 de 134

Como podemos observar en el diagrama anterior, nosotros podemos insertar cualquier tipo de

dato en una trama, de hecho, es nuestro deber mantener el orden de lectura cuando

necesitemos recuperar algún dato.

Fijémonos en la primera lectura que hacemos, podemos observar como estamos leyendo un

tipo de dato que encaja perfectamente con el que hemos escrito en su inicio. Esto nos

devuelve el dato deseado de forma íntegra y sin errores.

Por el contrario, si realizamos una consulta como la segunda lectura del ejemplo, nos podemos

fijar que estamos intentando leer un tipo de dato que no concuerda con lo que se ha escrito

anteriormente en la trama. Esto desemboca en una lectura incoherente y corrupta del

segmento de datos leídos, pues estamos leyendo en este caso en particular un dato y parte del

siguiente.

2.3.2 Eventos

Los eventos los podemos definir como objetos con información que se generan cuando hay un

cambio de estado en algún sub sistema del motor 3D y que nosotros queramos tener

consciencia de ello.

En nuestro motor 3D, los eventos toman información de muchas partes y son encargados de

mantenerla hasta que es tratada debidamente. Un ejemplo de esto podrían ser las entradas de

teclado y ratón, la entrada de la red, un trigger, etc.

Un evento está formado por dos partes características: la primera es un identificador de

evento, y la segunda es una trama.

Identificador

o El identificador del evento es lo que nos permite saber de qué evento estamos

hablando. Estos identificadores tienen que ser definidos previamente por los

desarrolladores y llegar a un acuerdo sobre qué tipo de identificadores se

asignan a cada evento.

Trama

o Anteriormente hemos definido a la trama como ese contendor de información

ordenada por el desarrollador. En un evento, siempre tenemos

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 58 de 134

inherentemente una trama para almacenar la información que lleva ese

evento. Esto nos permite alimentar de información a un evento como por

ejemplo un evento de ratón, o por lo contrario tener eventos predefinidos en

el interior de un trigger y que este los haga activos cuando sea disparado.

A continuación mostramos un diagrama en forma de ejemplo de cómo podría ser un evento:

Ilustración 30 - Ejemplo de evento

Fijémonos de que el evento encapsula tanto el identificador como la trama que contiene la

información. Estos eventos son almacenados en una estructura de datos llamada Event

Manager, que al igual como anteriores managers se encarga de mantener un control y un

orden en los eventos que nos llegan y hemos de tratar.

2.3.3 Entrada / Salida

2.3.3.1 Teclado

Cuando hablamos de Entrada y Salida a nivel de teclado, nos referimos a los eventos que en

este caso entran generados por el teclado con la interactuación del usuario.

Para gestionar los eventos que se pueden hacer mediante un teclado, en nuestro motor hemos

implementado un gestor de eventos, que particularmente se le llama internamente tracker.

Este tracker es el encargado de gestionar cada uno de los eventos del teclado y de generar una

imagen virtual de cómo está el teclado en todo momento.

1002 ‘A’ 4.352 “Cadena”

Identificador

Evento

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 59 de 134

Gracias a este seguimiento, nosotros podemos comprobar en todo momento cuál es el estado

de cualquiera de las teclas que nosotros necesitamos. Esto ayuda a la hora de mantener una

idea de programación limpia y estructurada.

De igual modo que el tracker puede hacer un seguimiento exhaustivo del estado del teclado,

también se puede deshabilitar su función como tal. Esto es beneficioso en el caso en el que se

quiera bloquear toda entrada referente a teclado, es decir, dejaríamos al motor 3D sin entrada

posible mediante teclado.

2.3.3.2 Ratón

A nivel de Entrada y Salida, también tenemos un gestor encargado de mirar en todo momento

el estado del ratón.

El tracker del ratón, al igual como el del teclado, mira en todo momento el estado de éste para

poder ofrecer en tiempo de aplicación la información necesaria para un correcto

funcionamiento.

Este tracker a diferencia del de teclado, no solo ofrece el estado actual de los botones del

ratón sino que también ofrece información de las coordenadas actuales del puntero del ratón

como de sus incrementos diferenciales a través del tiempo.

Los incrementos diferenciales son necesarios para poder calcular bien los movimientos de la

cámara dentro del motor 3D. Así el cálculo diferencia ya viene hecho por el tracker de ratón y

no es necesario hacerlo en la cámara.

2.4 Escena

2.4.1 Vértices flexibles

Como ya podemos intuir, los objetos 3D representados por nuestro motor están hechos a

partir de triángulos. Cada triángulo por consiguiente tiene tres vértices. Los datos contenidos

en estos tres vértices son los encargados de decir cómo se renderizará este objeto en nuestro

motor 3D.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 60 de 134

La librería DirectX utiliza el formato FVF o Flexible Vertex Format que es un formato flexible

que nos permite definir la estructura de los vértices utilizados como nosotros necesitemos.

Esta definición flexible nos permite utilizar en cada caso una declaración de vértice acorde con

nuestras necesidades en ese momento. Podemos definir desde el vértice más simple con una

posición 3D o todo lo complicado que se requiera añadiendo datos e informando a la librería

de su estructura.

Ilustración 31 - Diferentes formatos de vértice

En el diagrama anterior podemos observar diferentes ejemplos del formato flexible del que

hablamos. Este formato nos proporciona la libertad necesaria para poder definir toda la

información que necesitemos para un vértice de nuestra geometría.

2.4.2 Vectores de índices y vértices

Cuando nosotros queremos representar un objeto 3D en pantalla, lo haremos mediante un

conjunto de triángulos que definirán su forma visual. Estos triángulos deberán estar

contenidos en una estructura de datos apta para ello.

Esta estructura de datos que hemos utilizado nosotros en nuestro motor 3D son los vectores

de índices y vértices.

Vértice

Posición

Vértice

Posición Color

Vértice

Posición Color

Normal

Vértice

Posición Color

Normal Tangente

Textura 1 Textura 2

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 61 de 134

Por cada objeto será necesaria la creación de dos estructuras de datos que irán

interrelacionadas, estas dos son el vector de índices y el vector de vértices. Gracias a esta

definición conjunta se nos asegura que los vértices contenidos en el vector de vértices serán

únicos para un objeto y que no estarán repetidos. Esto es debido a que el vector de índices es

el encargado de decir en cada momento que vértices forman cada triángulo.

Esta definición conjunta nos salvaguarda de problemas de optimización de memoria que hacen

referencia a la repetición de vértices debido a triángulos contiguos. Sí es cierto que habrá

índices repetidos a lo largo del vector de índices, pero de esta manera solo se repiten números

naturales e individuales y no estructuras de vértices flexibles que pueden llegar a representar

una carga de memoria importante.

Ilustración 32 - Index y Vertex Buffer interrelacionados

En la ilustración anterior podemos observar como un vector de índices se relaciona con un

vector de vértices. Podemos observar que existen índices repetidos para ciertos triángulos,

pero los datos representativos de un índice son significantemente inferiores en cantidad de

memoria con respecto a un vértice.

Vértice 1

Vértice 2

Vértice 3

Vértice 4

Vértice 5

Vértice 6

1 2 3 4 5 6 7 8 9

Triángulo 1 Triángulo 2 Triángulo 3

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 62 de 134

2.4.3 Mallas

2.4.3.1 Introducción

A partir del punto anterior, hemos entendido que los triángulos como figura geométrica son

almacenados en estructuras lógicas llamadas Vertex Buffers.

Gracias a estas estructuras lógicas, nosotros podremos agruparlas para poder llegar a formar lo

que nombraremos una malla. Las mallas son utilizadas en nuestro motor 3D para poder

representar gráficamente a los objetos en pantalla. Estas mallas son el objeto mínimo que el

motor 3D puede dar acceso a la aplicación en cuanto a representación gráfica se refiere.

2.4.3.2 Partes de una malla

Como hemos comentado anteriormente, una malla está compuesta por vectores de vértices y

vectores de índices. Las partes de una malla precisamente son la agrupación de estos vectores

por duetos que son formados por un vector de vértices y otro de índices.

Aparte de los vectores de vértices y índices que formarán la malla gráfica, también existe otro

tipo de malla que coexiste con la malla gráfica, ésta es la malla física.

La malla física es una malla de mucha más simplicidad a nivel de forma gráfica, pues ésta

contiene muchos menos triángulos sacrificando detalles geométricos. Esta malla física es

utilizada mediante un algoritmo antes de hacer nada en el render gráfico. Esto nos permite

descartar geometría mediante sus oclusiones visuales.

Hemos de remarcar que en cada malla solo puede haber una malla física, pues es totalmente

lógico que una malla sea representada mediante una sola malla física. Por el contrario, si

representásemos una malla mediante varias mallas físicas, lo único que conseguiríamos sería

perder rendimiento a la hora de calcular las oclusiones.

A continuación podemos observar como una malla es representada a nivel lógico dentro del

motor 3D.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 63 de 134

Ilustración 33 - Diagrama lógico de una malla

En el diagrama anterior podemos ver los dos grupos predominantes, es decir, la malla gráfica

de la que hemos hablado con anterioridad, y la malla física, que solo hay una para cada malla

que tengamos en nuestro motor 3D.

Podemos observar que en la malla gráfica existen varios duetos de vectores de índices y

vértices. Estos duetos forman un conjunto de mallas que en su conjunto formaran el resultado

final de la malla. Esto quiere decir que una malla puede ser representada en pantalla mediante

la representación de muchas mallas.

2.4.3.3 Manager de mallas

El manager de mallas, al igual como otros managers que ya hemos comentado anteriormente,

se encarga de gestionar las mallas que podemos tener en nuestro motor 3D. De este modo, la

aplicación interactúa con el manager para poder cargar en tiempo real las mallas y así poder

ser mostradas en pantalla.

Un aspecto importante del gestor de mallas es que tiene acceso directo a otros managers que

ya hemos descrito como el manager de texturas y el manager de materiales. Gracias a este

acceso directo, el manager de mallas puede cargar y descargar toda la información necesaria

Malla

Vector de Índices Vector de Vértices

Vector de Índices Vector de Vértices

Vector de Índices Vector de Vértices

Malla Gráfica

Malla Física

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 64 de 134

tanto a nivel de texturas como de materiales siéndole este proceso transparente al

desarrollador.

Cuando un manager de mallas recibe la orden de cargar una malla en memoria, éste lee el

archivo para rescatar la información de la malla, interactúa con el manager de materiales para

poder cargar los materiales para esa malla, y al mismo tiempo, el manager de materiales

interactúa con el manager de texturas para poder cargar las texturas necesarias.

Asimismo, cuando el manager de mallas decide descargar de memoria una malla, este

manager, aparte de descargar la información lógica de la malla de memoria, también accede al

manager de materiales para indicarle que materiales debe descargar, y este último hace lo

mismo con el manager de texturas, el cual descarga de memoria esas texturas necesarias.

Ilustración 34 - Diagrama del Manager de Mallas

Malla 1

Mallas en memoria

Identificación

id

id

id

Manager de Mallas

Ap

licac

ión

Manager de Materiales

id Malla 2

Malla 3

Malla 4

Manager de Texturas

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 65 de 134

2.4.4 Submallas

2.4.4.1 Introducción

En un diagrama anterior hemos podido ver que una malla se dividía entre duetos de vectores

de índices y vectores de vértices y una malla física necesaria para poder calcular oclusiones

visuales.

Una submalla en nuestro motor 3D se define como un dueto entre un vector de índices y un

vector de vértices. Este dueto, que a partir de ahora llamaremos submalla, es el que mediante

su presencia, y la de otras submallas más, nos permite crear una malla completa y visual.

2.4.4.2 Partes de una submalla

Como podemos prever, las partes de una submalla vienen dadas por un primer vector de

índices seguido de un vector de vértices que serán indexados por estos índices.

Estos dos vectores conformaran la parte geométrica y la que mostrara en pantalla la forma 3D

de la malla.

Por otro lado tenemos que para estos dos vectores, siempre tenemos asociado un material.

Esto quiere decir que una malla está formada por varias submallas, y cada una de esta está

asociada a un material en concreto.

Por lo tanto podemos tener en pantalla objetos complejos que a lo largo de su superficie el

color y material de ésta cambie según las zonas.

Con esta información, podemos actualizar el diagrama que hemos visto con anterioridad y

remarcar lo que es una submalla dentro de una malla

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 66 de 134

Ilustración 35 - Especificación de la Submalla

Una vez tenemos descrito cuales son las partes de una submalla, solo nos queda remarcar

cómo se comporta ésta en el proceso de renderizado.

Una submalla es el elemento atómico más pequeño que el renderizado acepta para poder ser

tratado. Esto es así ya que cada submalla tiene asociado un material, y el proceso de

renderizado trata los objetos según el material.

Gracias a esta información podemos comprobar como una malla es descompuesta en

submallas, y estas tratadas y renderizadas independientemente.

Este proceso se lleva a cabo mediante esta idea ya que es beneficioso a nivel de renderizado

que todas las mallas que comparten un mismo material sean renderizadas una detrás de otra y

no alternando con otros materiales, pues una de las cosas que penaliza más en la librería

gráfica es la de hacer cambios en los materiales.

2.4.4.3 Manager de submallas

El manager de submallas lo podríamos definir como ese manager intermedio que es accedido

por el manager de mallas y al mismo tiempo accede a otros managers como el de texturas o el

de materiales.

Malla

Vector de Índices Vector de Vértices

Vector de Índices Vector de Vértices

Malla Gráfica

Malla Física

Material

Vector de Índices Vector de Vértices Material

Submalla

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 67 de 134

Este manager es el encargado de almacenar las submallas, que serán el elemento más

accedido por el bucle de render. Aparte de esto, también es necesario comentar que este

manager es totalmente consistente con la metodología de los managers pues cuando el

manager de mallas indica que una malla debe ser borrada, el manager de submallas borra

todas las submallas asociadas a esa malla.

Ilustración 36 - Diagrama del manager de Submallas

En el diagrama anterior podemos observar como el manager de mallas accede al manager de

submallas donde están todas estas almacenadas, al mismo tiempo y como ya es normal, estos

managers tienen acceso a otros managers para tener acceso a la información necesaria.

Submalla 1

Submallas en memoria

Identificación

id

id

id

Manager de Submallas

Ap

licac

ión

Manager de Materiales

id Submalla 2

Submalla 3

Submalla 4

Manager de Texturas

Manager de Mallas

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 68 de 134

2.4.5 Bounding Boxes

Una Bounding Box es la mínima caja geométrica en la que puede llegar a caber un modelo

determinador.

Partiendo de esta definición, una Bounding Box, mediante unas coordenadas, crea una caja

imaginaria que engloba la totalidad de un modelo de nuestro motor 3D.

Gracias a este hecho, es posible simplificar un objeto mediante su Bounding Box para así

aligerar la carga en procesos lógicos de exclusión o procesado de objetos en tiempo real.

La orientación de las Bounding Boxes de nuestro motor 3D está alineada con los ejes del

mundo, es decir, en ningún caso vamos a tener una caja rotada de este estilo. Con esta

premisa de orientación alineada, es mucho más fácil presuponer información a la hora de

hacer los cálculos asociados con colisiones.

A continuación veremos una representación visual de lo que es una Bounding Box:

Ilustración 37 - Ejemplos de Bounding Boxes

En la ilustración anterior podemos observar como los objetos están contenidos dentro de unas

cajas representadas mediante líneas. Estas cajas nos ayudan a poder representar los objetos

en los algoritmos mediante una representación más sencilla y más fácil de calcular.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 69 de 134

2.4.6 Cámaras

Cuando nosotros montamos una escena y la renderizamos para que sea mostrada por pantalla

como resultado final, aparte de las luces, los modelos y efectos especiales, necesitamos unos

parámetros de vital importancia.

Estos parámetros son las matrices de Mundo, Visión y Proyección. Estas matrices nos definen

en todo momento lo siguiente:

Mundo (World)

o Esta matriz nos multiplica y suma las coordenadas de nuestros modelos para

así poder escalar, rotar o trasladar el mundo al completo en el que estamos. Se

utiliza muy poco debido a que muy pocas veces es necesario modificar el

mundo en su globalidad como lo conocemos nosotros.

Visión (View)

o Esta matriz nos ayuda a poder crear unos valores que multiplicados por las

demás matrices nos dará un punto de visión, una dirección en la cual miramos

y una orientación de nuestro ojo de visión.

Proyección (Projection)

o Esta matriz al igual que la de visión, nos crea unos valores que multiplicados

por las otras dos matrices, nos definirá un volumen de visión partiendo del

ángulo de apertura de la cámara y de la distancia de visión mínima y máxima.

El conjunto de estas matrices, como decíamos anteriormente, nos permite generar una escena

desde el punto de visión que nosotros escojamos, con el ángulo que nosotros queramos y con

la profundidad y escalado deseado.

Cada vez que queramos renderizar la escena desde un punto de visión diferente, será

necesario cambiar los valores de estas tres matrices a los deseados. Es por ese motivo por el

cual se crea un objeto controlador al cargo de estas tres matrices que a partir de ahora

llamaremos cámara.

Esta cámara es la encargada de decirnos en todo momento cuáles son esas tres matrices de las

que hemos hablado anteriormente y, al mismo tiempo, nos permite mover, girar, abrir o cerrar

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 70 de 134

en ángulo, etc. de una forma más cómoda que si se tratara de modificar las matrices

directamente.

Una vez hemos hablado sobre qué es lo que engloba a nivel teórico una cámara, hablaremos

de los dos tipos de cámaras más comunes y de cuál de ellas hacemos uso en nuestro motor.

Primera persona (1P)

o Una cámara en primera persona es esa cámara que emula nuestra visión real

del día a día. Esto se refiere a que esta cámara tiene el punto de visión fijo, y el

punto hacia donde mira es el móvil. Por lo tanto, es la cámara que según su

naturaleza, más se asemejaría a la visión de una persona humana.

Tercera persona (3P)

o Las cámaras de tercera persona reciben su nombre debido a que su forma de

ver a través de ellas es como la de una persona que está mirando fijamente al

punto que es protagonista en este momento. Este aspecto desplaza a la

cámara y la deja en un segundo plano, es decir, esta cámara siempre estará

observando a un cierto punto sin elección de ningún otro.

A continuación veremos un diagrama en el cual podremos observar los dos tipos de cámaras y

como estas son representadas en 3D.

Ilustración 38 - Diagrama de cámaras en primera y tercera persona

En la ilustración anterior podemos observar las diferencias entre la primera y la tercera

persona. Mientras la primera persona, goza de un movimiento esférico para poder mirar

Primera persona Tercera persona

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 71 de 134

cualquier punto desde él, la tercera persona se mueve ella misma a lo largo de la esfera para

estar siempre mirando al punto central de dicha esfera.

2.4.7 Frustrums

Por Frustrum podemos entender el volumen que se encierra dentro de una figura,

normalmente una pirámide o cilindro delimitada por dos planos paralelos, denominados Near

Plane y Far Plane.

De esta definición podemos ver que en nuestras aplicaciones 3D, la forma natural más apta

para nosotros es la de una pirámide, normalmente llamada pirámide de visión. Esta pirámide

está formada por seis planos, estos planos nos determinaran un volumen interior donde la

geometría que esté en ese volumen se renderizará, en cambio, la geometría que quede fuera

de ese volumen no se renderizará.

A continuación podemos observar una ilustración con este concepto para un entendimiento

más fácil.

Ilustración 39 - Volumen de un Frustrum

Como se puede observar en la ilustración anterior, los cilindros verdes (C, D, E, F) están dentro

del volumen mientras que los cilindros naranjas (A, B, J, I, G, H) no lo están.

J

A

B

C D E

G H

F I

Far Plane

Near Plane

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 72 de 134

Los seis planos que conforman la pirámide de visión, pueden extraerse de las matrices que

hemos visto anteriormente para generar la escena, es decir, de las matrices Mundo, Visión y

Proyección.

Concretamente en nuestro motor 3D obtenemos los planos de la matriz resultante de la

multiplicación de la matriz de Visión y la matriz de Proyección. Esto nos dará los planos en el

espacio Mundo y los coeficientes de los planos se extraerán de la siguiente forma:

Plano Coeficientes de Plano

Izquierda

Derecha

Arriba

Abajo

Cercano

Lejano

Tabla 11 - Coeficientes de los planos del Frustrum

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 73 de 134

Una vez nosotros tenemos los coeficientes de los seis planos que conformarán nuestro

volumen de visión es relativamente fácil determinar si un punto 3D está por delante o por

detrás de dichos planos, pues solo hay que substituir las coordenadas del punto (X, Y, Z) en las

ecuaciones de los planos. Si el valor resultante es positivo, está delante del plano, de lo

contrario está por detrás.

Gracias a esta técnica, si un punto está por detrás de los 6 planos, implica que no está dentro

del volumen de visión, por lo tanto, no es necesario que lo pintemos ni lo tratemos.

Concretamente aquí entendemos el porqué es recomendable asociar una geometría más

simple que la del propio objeto, es decir, una Bounding Box, ya que de este modo nos será más

fácil y más rápido descartar si un modelo se está viendo o no.

2.5 Sombras

2.5.1 Introducción

Cuando hablamos sobre sombras en una escena 3D no nos referimos a la falta de luz en una

superficie debido al ángulo de ésta con el del punto que emana luz. Nos referimos a la oclusión

de luz que un objeto realiza a otro objeto en tiempo real.

De tipos de sombras se han diseñado muchas y muy variadas según lo requiera la escena.

Tenemos desde la sombra más fácil, que es colocar a los pies del objeto un círculo a forma de

sombra, hasta los métodos más complicados de hoy en día que generan sombras con perfil

difuminado y con calidades visuales cada vez más elevadas.

La evolución de las sombras en los juegos ha pasado por sombras de carácter simple donde la

sombra tenía forma de círculo o de perímetro que era estático en el suelo y se mantenía

centrado en el objeto.

Años después surgiría un método para poder generar sombras proyectando los contornos de

los objetos desde el origen de la luz hasta el infinito, llamándose este método sombras

volumétricas. Estas últimas gozan de un error natural debido a que los contornos de la sombra

son extremadamente definidos y chocan con las sombras reales dando un aspecto más alejado

de la realidad de lo que se pretende.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 74 de 134

Poco a poco el mundo de las sombras evolucionó y muchos programadores se han movido a

un tipo de sombras llamado shadow mapping, donde las sombras son realizadas gracias a una

textura generada intencionadamente de distancias entre el origen de la luz y la geometría a

tratar.

El hecho de que se tenga una textura gracias a la cual se puede generar la sombra, da la

capacidad al programador de desarrollar algoritmos basados en texturas y por consiguiente en

espacios 2D los cuales nos permiten aplicar degradados y difuminados a lo largo de los

contornos deseados.

Concretamente en nuestro motor 3D hemos implementado una técnica de sombras como la

que hemos descrito anteriormente como shadow mapping, pero a diferencia de que en luces

como las spot light que solo necesitan una textura para generar la sombra, nosotros hemos

utilizado un cubemap para poder generar sombras con una luz omnidireccional.

2.5.2 Cube Shadow Maps

Como ya hemos dicho, nuestro algoritmo utiliza un cubemap en el cual almacenaremos

distancias en lugar de información de color en los píxeles de éste. A continuación veremos la

estructura de un cubemap para tener en mente como está representado en memoria.

Ilustración 40 - Estructura de un cubemap

X

-Z

Cara Y

X

Y

Cara Z

X

Z

Cara -Y

-Z

Y

Cara X

-X

Y

Cara -Z

Z

Y

Cara -X

U

V

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 75 de 134

Como podemos observar en la ilustración anterior, el cubemap está compuesto de seis caras,

estas caras son cada una de ellas texturas del tipo y formato que nosotros queramos, al igual

como las dimensiones de estas.

Una vez hemos visto como un cubemap está estructurado internamente, hablaremos de cómo

accederemos a la información de un cubemap.

En un cubemap, a diferencia de los accesos 2D que se hacen en texturas normales, aquí es

necesario acceder a través de un acceso 3D, o lo que es lo mismo un vector 3D. Gracias a estos

vectores seremos capaces de recuperar el color o el dato deseado almacenado en ese punto

por el cubemap.

A continuación presentamos una ilustración de cómo los accesos desde un cubemap son

realizados y cómo los cubemap devuelven el color o dato asociado al punto donde accedemos.

Ilustración 41 - Ejemplo de acceso a la información de un cubemap

Xres

Xres

Xre

s

Xre

s

Xres

Xres

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 76 de 134

En la ilustración anterior vemos como se recupera la información de un cubemap para poder

operar con ella.

Habiendo visto esto, solo nos queda explicar cómo se calculan las sombras. Para hacer dichas

sombras, es necesario renderizar la escena desde la posición de la luz omnidireccional seis

veces, una en cada una de las direcciones que marca el cubemap.

Estos múltiplos renders de la escena tienen la peculiaridad de que no sacan un valor

interpretado como color, sino que nosotros en cada uno de los píxeles de las caras del

cubemap almacenamos la distancia desde la posición de la luz hasta dicho píxel.

Una vez tenemos el cubemap con las distancias, solo tenemos que hacer una comparación

cuando estamos renderizando desde la cámara desde donde miramos.

Es decir, si el pixel que está iluminado desde la luz omnidireccional tiene una distancia mayor a

la que se puede ver desde el cubemap en esa dirección, entonces el píxel que estamos

tratando está en oclusión de esa luz, y eso implica que está en sombra, si no el píxel se

encuentra iluminado normalmente.

Ilustración 42 - Ejemplo de aplicación de sombras

La luz no obtiene ninguna obstrucción en éste píxel, aunque no se vea desde la cámara. Este píxel está iluminado.

La distancia de este píxel es más grande que la distancia del píxel que se ve desde la luz. Este píxel esta en sombra.

La distancia de éste píxel es la misma que la distancia desde la luz. No hay obstrucciones por lo tanto está iluminado.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 77 de 134

2.6 Exportador

El exportador, llamado Constructor en el proyecto, es una de las aplicaciones mas criticas que

tiene este proyecto. Esto se debe a que sin él, nosotros no seriamos capaces de obtener los

modelos formateados de una forma específica para poderlos representar por pantalla.

Es por eso que el exportador obtiene una relevancia muy elevada en este proyecto, a la altura

de factores como la ecuación de iluminación o el diseño final de la demo.

Para poder entender el trabajo del exportador, es necesario entender cuáles son las dos partes

que queremos interconectar, pues el exportador será un software que obtendrá datos de una

parte y los transformará para la otra parte.

Por un lado tenemos nuestro software de diseño 3D, con el que somos capaces de hacer

nuestros propios modelos. Por otro lado tenemos nuestro motor 3D que estamos

desarrollando, y que como tal, necesita los modelos 3D formateados de una manera específica

para que éste pueda entenderlos.

Ilustración 43 - Partes del software a unir

Como podemos observar en la ilustración anterior, tenemos dos aplicaciones importantes que

necesitan ser unidas por un exportador para que las dos puedan coexistir y operar de manera

coordinada.

De no ser así, nos veríamos envueltos en una tarea de proporciones titánicas, ya que el motor

3D necesita mucha información para cada objeto, y dichos objetos no salen con esa

información directamente desde el software de edición 3D Studio MAX.

A continuación detallaremos todas las fases que conforman el exportador y por las cuales pasa

la información del 3D Studio MAX hasta convertirse en información útil para el motor 3D.

3D Studio MAX

iL Engine

?

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 78 de 134

Fases del Constructor

Fase 1

En la primera fase, la información del 3D Studio es exportada al exterior de

este programa mediante un formato abierto y en forma de texto plano

llamado ASE. Este formato aunque tenga muchísimas carencias nos servirá

para poder leerlo de manera cómoda por nuestro exportador.

Fase 2

En la segunda fase, el fichero ASE es parseado y recolocado en el interior de

las estructuras de datos del exportador para poder tener la información de

este archivo en un valor lógico con el cual se pueda operar de manera sencilla

y eficiente.

Fase 3

En la tercera fase de este exportador, la información lógica ASE es

transformada en información donde su estructura concuerde con las

definiciones de Vertex Buffer y Index Buffer. Por otro lado también se

reconfiguran las normales a dichos vértices ya que el formato ASE no lo hace

por sí solo.

Fase 4 En la cuarta fase, estos Vertex e Index Buffers son optimizados y purgados de

información innecesaria que pueda proceder de la fase anterior.

Fase 5

En la quinta fase, se procede al fusionado de dos objetos, esto es debido a que

desde el 3D Studio MAX no nos es posible exportar directamente un objeto

con doble set de texturas. Gracias a esta fase, podemos juntar dos objetos que

coincidan en estructura para dar como resultado un objeto con estructura

idéntica pero que conserve los sets de texturas de los dos objetos.

Fase 6

En la sexta fase, cada uno de los objetos resultantes es analizado y se procede

al cálculo de sus tangentes por vértice. Gracias a que podemos calcular este

dato en modo off-line, nos ahorramos tener que hacerlo en la carga de

modelo.

Fase 7

En esta séptima fase, el objeto resultante es fusionado otra vez con otro

objeto, pero este ahora es el objeto que concuerda con la estructura física que

tendrá dicho objeto para usarla dentro del motor 3D.

Fase 8 En esta última fase, escribimos a disco toda la información que hemos llevado

a cabo con las fases anteriores.

Fase 9 Esta fase es totalmente opcional, y existe para poder exportar no geometría

como tal sino líneas 3D que nos serán útiles para hacer caminos u otras cosas.

Tabla 12 - Fases del Constructor

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 79 de 134

Ilustración 44 - Diagrama de las fases del Constructor

Como podemos observar en la ilustración anterior, las fases del Constructor son secuenciales

aunque existen algunas de ellas que son totalmente opcionales, y dependen exclusivamente

de qué tipo de datos ha exportado el documento ASE.

Fase 1 Exportar la

geometría a formato ASE

Fase 2 Parseado del fichero ASE

Fase 3 Cálculo de

ASE a Vertex y Index Buffer

Fase 9 Exportación de líneas 3D

Fase 9.1 Escritura en disco de las

líneas 3D

Fase 4 Optimización de Vertex e

Index Buffers

Fase 5 Fusionado

para dos sets de texturas

Fase 6 Cálculo de tangentes

Fase 7 Fusionado con objeto

físico

Fase 8 Escritura en

disco

3D Studio MAX

iL Engine

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 80 de 134

3 Parte práctica

3.1 Librería gráfica

3.1.1 DirectX 9.0c

Lo primero que tenemos que tener en cuenta para la inclusión de la librería gráfica son las

instrucciones para poder añadirla al proyecto. Estas instrucciones engloban tanto a la propia

librería como a librerías de carácter útil a lo largo de nuestro desarrollo.

Librerías de DirectX 9.0c

#include <Windows.h>

#include <mmsystem.h>

#include <d3dx9.h>

Tabla 13 - Librerías necesarias para DirectX 9

Una vez tenemos incluidas las librerías necesarias para invocar a los objetos de DirectX al igual

como para poder hacer cálculos de matrices mediante optimizaciones de sistema y también

poder hacer referencias a los objetos de Windows para así poder crear ventanas y controles,

es necesario empezar con la inicialización de los datos necesarios para la aplicación en sí.

Código par inicializar DirectX 9.0c

// Creamos el objeto D3D.

if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )

return E_FAIL;

// Inicializamos la estructura para crear el objeto D3DDevice.

D3DPRESENT_PARAMETERS d3dpp;

ZeroMemory( &d3dpp, sizeof(d3dpp) );

d3dpp.Windowed = TRUE;

d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

d3dpp.EnableAutoDepthStencil = TRUE;

d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

// Creacion del D3DDevice

if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,

hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice ) ) )

{

return E_FAIL;

}

Tabla 14 - Inicialización de DirectX 9.0c

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 81 de 134

Una vez hemos inicializado el objeto que nos contendrá los datos de DirectX y hemos puesto

los parámetros adecuados para nuestra aplicación, solo nos queda el hecho de implementar el

bucle de programa para que nuestra aplicación pueda ejecutarse.

Código del bucle de renderizado

// Registramos la clase de ventana.

WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,

GetModuleHandle(NULL), NULL, NULL, NULL, NULL,

L"iL engine", NULL };

RegisterClassEx( &wc );

// Create mos la ventana de aplicacion.

HWND hWnd = CreateWindow( L"iL engine", L"iL engine : Demo",

WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,

NULL, NULL, wc.hInstance, NULL );

// Inicializamos Direct3D

if( SUCCEEDED( InitD3D( hWnd ) ) )

{

// Creamos la geometria

if( SUCCEEDED( InitGeometry() ) )

{

// Mostramos la ventana

ShowWindow( hWnd, SW_SHOWDEFAULT );

UpdateWindow( hWnd );

// Entramos en el bucle de mensajes

MSG msg;

ZeroMemory( &msg, sizeof(msg) );

while( msg.message!=WM_QUIT )

{

if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )

{

TranslateMessage( &msg );

DispatchMessage( &msg );

}

else

Render();

}

}

}

UnregisterClass( L"iL engine", wc.hInstance );

return 0;

Tabla 15 - Bucle de renderizado de la aplicación

Como podemos comprobar en la última tabla, el bucle de renderizado está gobernado por los

mensajes que el sistema operativo Windows nos manda. Este bucle solo parará en este caso

cuando el mensaje de salida de una ventana sea mandado.

Igualmente también podemos observar como el renderizado de la aplicación sólo se realiza

cuando el bucle no ha recibido ningún mensaje, de lo contrario, se destina tiempo a tratar e

interpretar el mensaje y en la siguiente vuelta se sigue renderizando.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 82 de 134

3.1.2 DXUT DXUT, como ya hemos comentado en la parte teórica, es una capa de software que nos ayuda

a la hora de gestionar nuestra aplicación. Esta ayuda nos viene dada mediante el control del

bucle de programa y de todos los mensajes que vienen dados por Windows y que modifican a

nuestra aplicación.

Librerías de DXUT

#include "DXUT.h"

#include "DXUTcamera.h"

#include "DXUTsettingsdlg.h"

#include "SDKmesh.h"

#include "SDKmisc.h"

#include "resource.h"

Tabla 16 - Librerías necesarias para DXUT

En la anterior tabla podemos observar cuales son las librerías necesarias que hemos de incluir

en nuestro proyecto para que este pueda gozar de la ayuda que ofrece DXUT en gestión de la

aplicación.

Callbacks de DXUT

// Iniciamos los callbacks de DXUT

DXUTSetCallbackD3D9DeviceAcceptable( IsDeviceAcceptable );

DXUTSetCallbackD3D9DeviceCreated( OnCreateDevice );

DXUTSetCallbackD3D9DeviceReset( OnResetDevice );

DXUTSetCallbackD3D9FrameRender( OnFrameRender );

DXUTSetCallbackD3D9DeviceLost( OnLostDevice );

DXUTSetCallbackD3D9DeviceDestroyed( OnDestroyDevice );

DXUTSetCallbackMsgProc( MsgProc );

DXUTSetCallbackFrameMove( OnFrameMove );

DXUTSetCallbackDeviceChanging( ModifyDeviceSettings );

Tabla 17 - Inicialización de los callbacks de DXUT

Una vez introducidas las librerías, a continuación y cómo podemos observar en la tabla

anterior, necesitamos informarle a la librería DXUT de donde están las funciones que ésta

necesita para poder operar como es debido. La naturaleza de los callbacks es que nosotros

definiremos unas funciones de control y DXUT las llamará cuando sea necesario.

De esta manera podemos observar como la ayuda de DXUT se hace patente pues el bucle de

renderizado queda algo más oculto en el código y nosotros solo tenemos que centrarnos en

dichas funciones de control.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 83 de 134

Una vez inicializadas todas las funciones que estarán ligadas a los callbacks de DXUT, es

necesario invocar el bucle de programa desde DXUT. Esto se hace de la siguiente forma:

Bucle de programa

//Inicializacion de la interfaz visual de la aplicacion

InitApp();

//Inicializacion de nuestro motor 3D

m_Engine.InitializeEngine();

//Invocamos el bucle de programa desde DXUT

DXUTMainLoop(); Tabla 18 - Bucle de programa de DXUT

Cuando queremos cerrar nuestra aplicación y ya hemos salido del bucle de programa, es

necesario hacer una última cosa, y es invocar a una función para que nos devuelva el código de

salida de nuestra aplicación. A continuación indicamos como.

Código de salida

//Devolvemos el codigo de salida de nuestra aplicacion

return DXUTGetExitCode(); Tabla 19 - Retorno del código de la aplicación

Una vez hemos visto las partes que necesitamos de DXUT para tener lista nuestra aplicación,

hemos de comentar que de todas las ayudas que nos ofrece DXUT, nosotros solo hemos hecho

uso de dos de ellas.

Callbacks

o Como ya hemos comentado, los callbacks nos han ayudado para esconder el

bucle de programa y así poder centrarnos en nuestro motor y no tanto en la

gestión de dicho bucle.

Interfaz Gráfica de Usuario (GUI)

o Hemos usado la interfaz gráfica que nos proporciona DXUT, de este modo no

tenemos que implementarla nosotros ya que cumple con creces su función en

nuestra aplicación. Asimismo también cabe comentar que no está en los

planes de este proyecto implementar una interfaz gráfica para usuario.

Con estas dos ayudas que hemos usado de DXUT ya podemos cerrar la lista. DXUT ofrece

infinidad de ayudas mas como por ejemplo implementaciones de clases útiles, formatos de

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 84 de 134

fichero, etc. Aunque hemos de destacar que este proyecto parte de la idea del aprendizaje y

por eso hemos optado por implementar todo lo posible desde sus inicios.

3.2 Renderizado

3.2.1 Iluminación Para poder comprender la iluminación que se realiza en este motor 3D, hemos de

remontarnos al apartado de iluminación de la parte teórica. Una vez recordada la educación

de iluminación solo nos falta comentar que esta ecuación ha sido implementada en los shaders

de este motor 3D. A continuación explicaremos paso a paso esta ecuación y cómo se lleva a

cabo.

Definición de una textura

// -------------------------------------------------------------------

// Texture Samplers

// -------------------------------------------------------------------

texture DiffuseTexture;

sampler DiffuseSampler = sampler_state

{

Texture = ( DiffuseTexture );

//Filtrados

MIPFILTER = LINEAR;

MAGFILTER = LINEAR;

MINFILTER = LINEAR;

}; Tabla 20 - Variable de textura en el shader

Para empezar con la explicación de la iluminación, debemos comentar cómo se definen las

variables que se necesitaran durante todo el proceso, aquí podemos ver un ejemplo de

definición de una variable de tipo textura. En los shaders son muy habituales estos tipos de

variables y gracias a ellos podemos recuperar colores de dicha textura.

Definición de variables lógicas

// -------------------------------------------------------------------

// Structs

// -------------------------------------------------------------------

uniform float4x4 world_view_proj;

uniform float3 light_pos;

uniform float4 light_color;

uniform float light_pendent;

uniform float light_offset;

//... Tabla 21 - Variables de tipo lógico

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 85 de 134

Como podemos observar en la tabla anterior, la definición de las variables lógicas de tipo

valores reales es tan necesaria como cualquier otra cosa, pues en éstas vienen definidos

parámetros tan importantes como la posición de las luces, el color de estas, o la matriz que

agrupa la matriz de mundo, la de visión y la de proyección.

Estructura de salida del Vertex Shader

struct VS_OUTPUT

{

float4 pos : POSITION;

float2 tex0 : TEXCOORD0;

float2 tex1 : TEXCOORD1;

float3 pos_world : TEXCOORD2;

float3 light_pos_to_px : TEXCOORD3;

float3 h : TEXCOORD4;

float3 norm : TEXCOORD5;

}; Tabla 22 - Estructura de salida

En este caso podemos observar la definición de la estructura de salida que usaremos en el

Vertex Shader. Esta estructura agrupa valores que serán necesarios en el Píxel Shader y que

saldrán interpolados para cada píxel.

Podemos observar como pasamos al Píxel Shader los valores de la posición en el mundo, al

igual como las coordenadas de textura y los valores de ‘h’ y ‘norm’ resultantes del cálculo

tangencial para posteriormente aplicar las texturas de Normal mapping.

Función del Vertex Shader

// -------------------------------------------------------------------

// Vertex Shader

// -------------------------------------------------------------------

VS_OUTPUT RenderSceneVS (

float4 inPos : POSITION,

float3 inNormal : NORMAL,

float2 inTex0 : TEXCOORD0,

float2 inTex1 : TEXCOORD1,

float3 inTangent : TEXCOORD2

)

{

VS_OUTPUT output;

output.pos = mul( float4(inPos.xyz,1.0f) , world_view_proj );

output.norm = float4(inNormal.xyz,1.0f);

output.tex0 = inTex0;

output.tex1 = inTex1;

float3 binormal = normalize(cross( inNormal , inTangent ));

float3x3 TBNMatrix = float3x3 ( normalize(inTangent) ,binormal,

normalize(inNormal));

output.norm = inPos;

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 86 de 134

float3 light_pos_to_px = light_pos - inPos;

float light_dist = length(light_pos_to_px);

float3 eye_pos_to_px = eye_pos - inPos;

float eye_dist = length(eye_pos_to_px);

float3 h = light_pos_to_px + (eye_pos_to_px * (light_dist /

eye_dist));

output.light_pos_to_px = mul( TBNMatrix ,light_pos_to_px);

output.h = mul( TBNMatrix ,h);

// Ponemos la pos local para que llegue al PS

output.pos_world = inPos;

return output;

}

Tabla 23 - Vertex Shader

En la tabla anterior podemos observar la función de Vertex Shader que tendrá nuestra

ecuación de iluminación. Esta función será ejecutada para cada uno de los vértices de la

escena y como tal, operará cada uno de estos vértices preparándolos para la función de Pixel

Shader.

Podemos observar como las coordenadas del vértice son multiplicadas por las matrices de

Mundo, Visión y Proyección combinadas entre ellas. Esto otorga al vértice la perspectiva

necesaria para ser representado en pantalla.

A continuación se realiza una de las operaciones más importantes en esta función. Y es el

cálculo de la matriz tangencial que nos permitirá más adelante poder calcular correctamente

las normales en los píxeles.

Esta matriz tangencial es calculada mediante tres vectores perpendiculares entre sí. Estos

vectores están compuestos por el vector de la tangente, dado por el vértice, el vector de la

normal, dado por el vértice, y el vector binormal, calculado a partir de los otros dos.

Una vez calculada esta matriz, solo nos queda hacer que los vectores que utilizaremos para la

iluminación sean locales al mundo descrito por la matriz tangencial.

A continuación veremos la función que sigue al Vertex Shader, esta función recibe el nombre

de Píxel Shader, y está encargada de calcular el color de cada uno de los píxeles que formaran

la imagen final.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 87 de 134

Función del Píxel Shader

// -------------------------------------------------------------------

// Pixel Shader

// -------------------------------------------------------------------

float4 RenderScenePS(

float2 inTex0 : TEXCOORD0,

float2 inTex1 : TEXCOORD1,

float3 pos_world : TEXCOORD2,

float3 light_pos_to_px : TEXCOORD3,

float3 h : TEXCOORD4,

float3 norm : TEXCOORD5

) : COLOR

{

inTex0.x = (inTex0.x * scalex) + scrollx;

inTex0.y = (inTex0.y * scaley) + scrolly;

//... Tabla 24 - Cabecera y coordenadas de textura del PS

En esta parte inicial de la función del Píxel Shader, podemos observar como entran los datos

en la función de igual forma que lo pueden hacer en el Vertex Shader. A continuación

observamos como las coordenadas de textura para este píxel son calculadas mediante las

coordenadas entrantes en la función modificadas por los parámetros que nos indica el material

desde fuera del shader.

Función del Píxel Shader

//...

float4 diffuse_texture = tex2D( DiffuseSampler , inTex0 );

float4 selfilu_texture = tex2D( SelfIlumSampler, inTex0 );

float4 lightmap_texture = tex2D( LightMapSampler , inTex1 );

float4 specular_texture = tex2D( SpecularSampler , inTex0 );

//... Tabla 25 - Sampleado de texturas

Podemos observar en la tabla anterior como las texturas son sampleadas. Esto quiere decir que a partir de las coordenadas de textura del píxel actual, podemos obtener el color que le corresponde según sus texturas asociadas. Este proceso lo realizamos para cada una de las componentes que formarán nuestra ecuación de iluminación.

Función del Píxel Shader

//...

light_pos_to_px = normalize( light_pos_to_px );

h = normalize( h );

//Obtenemos el color de la tinta de la luz

float4 cubemapInk_texture = texCUBE( CubeMapInkSampler, float4

(pos_world.xyz - light_pos.xyz,0.0f));

float shadow_dist = texCUBE( CubeMapSampler, float4

(pos_world.xyz - light_pos.xyz,1.0f)).x;

light_color = light_color * cubemapInk_texture;

//... Tabla 26 - Tintado de la luz

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 88 de 134

En esta última tabla, podemos observar el código con el que tintamos la luz. Un tinte de luz se

realiza mediante la multiplicación del color de la luz por el color obtenido en una textura

cubemap centrada en esta luz.

Función del Píxel Shader

//...

//Calculamos la distancia entre el pixel y el ojo de la camara

float dis_eye_pixel = distance(eye_pos,pos_world);

//Calculamos la distancia entre la luz y el pixel

float dis_light_pixel = distance(light_pos.xyz,pos_world.xyz);

float shadow_factor = (shadow_dist / dis_light_pixel);

if (dis_light_pixel < shadow_dist)

{

shadow_factor = 1.0f;

}

//... Tabla 27 - Distancias y sombreado en el PS

En la tabla anterior podemos observar cómo se calculan las distancias entre el ojo de la cámara

y el píxel, al mismo tiempo también podemos ver el cálculo de la distancia entre el píxel y la

luz.

Después de estos cálculos y accediendo a una textura de shadow cubemap, hacemos el cálculo

de la luz tal y como hemos descrito en el apartado teórico de las sombras.

Función del Píxel Shader

//...

float light_atenuation = saturate((light_pendent *

dis_light_pixel) + light_offset);

float4 light_factor = lerp(lightmap_texture, light_color,

light_atenuation);

float3 normal = tex2D( NormalSampler , inTex0 );

normal = 2 * normal - 1; //Pasamos de 0 a 1 -> -1 a 1

float diffuse = saturate( dot( normal , light_pos_to_px ) );

float cos_beta = saturate( dot( normal , h ) );

float specular_factor = saturate( pow( cos_beta , 30 ));

float4 specular = specular_factor * specular_texture *

light_atenuation;

float4 final = diffuse * ( (diffuse_texture + specular) *

light_factor * shadow_factor ) + ( selfilu_texture * glowintensity);

//... Tabla 28 - Ecuación de iluminación de PS

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 89 de 134

En la tabla anterior podemos observar el cálculo de la ecuación de iluminación propiamente

dicho. Este cálculo se realiza mediante una interpolación entre la luz dinámica y el color que

nos ofrece la textura de lightmap. Una vez calculadas las dos partes de la luz que hemos

comentado, estos valores son interpolados según las distancias de la luz.

Función del Píxel Shader

//...

//Calculo de la niebla

float f = saturate((fog_end - dis_eye_pixel)/(fog_end -

fog_start));

float4 color_fog = lerp(fog_color_far,fog_color_near,f);

float4 final_plus_fog = lerp(color_fog,final,f);

return ( final_plus_fog );

}

Tabla 29 - Cálculo de la niebla

Podemos observar en la tabla anterior, como una vez obtenido el color del píxel que

pondremos en pantalla, calculamos el color de la niebla para este píxel. Como ya hemos

comentado, esta niebla tiene dos colores, y el color interpolado de la niebla se vuelve a

interpolar con el color final del píxel.

Para terminar, mostramos la función del píxel shader sin subdivisiones de tablas para poder

tener una visión global de esta función.

Función del Píxel Shader

// -------------------------------------------------------------------

// Pixel Shader

// -------------------------------------------------------------------

float4 RenderScenePS(

float2 inTex0 : TEXCOORD0,

float2 inTex1 : TEXCOORD1,

float3 pos_world : TEXCOORD2,

float3 light_pos_to_px : TEXCOORD3,

float3 h : TEXCOORD4,

float3 norm : TEXCOORD5

) : COLOR

{

inTex0.x = (inTex0.x * scalex) + scrollx;

inTex0.y = (inTex0.y * scaley) + scrolly;

float4 diffuse_texture = tex2D( DiffuseSampler , inTex0 );

float4 selfilu_texture = tex2D( SelfIlumSampler, inTex0 );

float4 lightmap_texture = tex2D( LightMapSampler , inTex1 );

float4 specular_texture = tex2D( SpecularSampler , inTex0 );

light_pos_to_px = normalize( light_pos_to_px );

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 90 de 134

h = normalize( h );

//Obtenemos el color de la tinta de la luz

float4 cubemapInk_texture = texCUBE( CubeMapInkSampler, float4

(pos_world.xyz - light_pos.xyz,0.0f));

float shadow_dist = texCUBE( CubeMapSampler, float4

(pos_world.xyz - light_pos.xyz,1.0f)).x;

light_color = light_color * cubemapInk_texture;

//Calculamos la distancia entre el pixel y el ojo de la camara

float dis_eye_pixel = distance(eye_pos,pos_world);

//Calculamos la distancia entre la luz y el pixel

float dis_light_pixel = distance(light_pos.xyz,pos_world.xyz);

float shadow_factor = (shadow_dist / dis_light_pixel);

if (dis_light_pixel < shadow_dist)

{

shadow_factor = 1.0f;

}

float light_atenuation = saturate((light_pendent *

dis_light_pixel) + light_offset);

float4 light_factor = lerp(lightmap_texture, light_color,

light_atenuation);

float3 normal = tex2D( NormalSampler , inTex0 );

normal = 2 * normal - 1; //Pasamos de 0 a 1 -> -1 a 1

float diffuse = saturate( dot( normal , light_pos_to_px ) );

float cos_beta = saturate( dot( normal , h ) );

float specular_factor = saturate( pow( cos_beta , 30 ));

float4 specular = specular_factor * specular_texture *

light_atenuation;

float4 final = diffuse * ( (diffuse_texture + specular) *

light_factor * shadow_factor ) + ( selfilu_texture * glowintensity);

//Calculo de la niebla

float f = saturate((fog_end - dis_eye_pixel)/(fog_end -

fog_start));

float4 color_fog = lerp(fog_color_far,fog_color_near,f);

float4 final_plus_fog = lerp(color_fog,final,f);

return ( final_plus_fog );

} Tabla 30 - Función del PS sin subdivisiones

En esta última tabla observamos el código responsable de la ecuación de iluminación de

nuestro motor 3D. Hemos de tener en cuenta que este código se ejecuta por cada píxel de un

frame, o lo que es lo mismo, estamos viendo indirectamente la potencia que tienen las tarjetas

gráficas de hoy en día para poder hacer todos estos cálculos en el tiempo requerido.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 91 de 134

3.2.2 Luces

Como hemos comentado en el apartado teórico, las luces de este motor tienen diferentes parámetros. Parámetros que nos indican hasta donde llega la luz, el color de ésta, etc. A continuación veremos como estas luces han sido implementadas en nuestro motor.

Inicialización de la luz

//--------------------------------------------------------------------

// USE: Constructor de la clase

// IN: Void

// OUT: Void

//--------------------------------------------------------------------

CLight::CLight()

{

m_cLightType = IL_LIGHT_OMNI;

m_fAngle = 90.0f;

m_fPendent = -1.0f;

m_fOffset = 1.0f;

m_vPosition.x = 0.0f; m_vPosition.y = 0.0f;

m_vPosition.z = 0.0f; m_vPosition.w = 1.0f;

m_vDirection.x = 0.0f; m_vDirection.y = 0.0f;

m_vDirection.z = 0.0f; m_vDirection.w = 1.0f;

}

Tabla 31 - Constructor de la Luz

Empezamos con el constructor de la luz, que nos indica cómo serán inicializadas las luces en su

inicio. Como podemos observar, todas las luces en un inicio serán de tipo omnidireccional con

un ángulo de 90 grados que no será usado, y con un pendiente y offset en las distancias de la

luz de -1 y 1.

Al mismo tiempo también podemos observar como los valores de posición de la luz y dirección

de ésta están expresados mediante un vector de cuatro componentes. Tenemos que tener en

cuenta de que esto es así debido a que los shaders acostumbran a recibir vectores de cuatro

componentes, aunque nosotros de esas cuatro componentes solo usaremos tres.

Entrada de la posición de la luz

//--------------------------------------------------------------------

// USE: Introduce la posicion de la luz

// IN: D3DXVECTOR3& - Posicion de la luz (x,y,z,w)

// OUT: Void

//--------------------------------------------------------------------

void CLight::SetPosition(D3DXVECTOR4& inVec)

{

m_vPosition = inVec;

}

Tabla 32 - Posición de la Luz

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 92 de 134

En la tabla anterior podemos observar lo que estábamos comentando de las cuatro

componentes de los vectores. Podemos observar como la posición 3D de la luz esta descrita

por un vector de cuatro componentes, de las cuales, la última (w) será ignorada.

Ilustración 45 - Diagrama matemático de la Luz

Hemos recuperado el diagrama de la luz que teníamos en el apartado teórico. En este

diagrama hemos substituido y añadido valores matemáticos los cuales ahora podemos

entender mejor.

La atenuación de la luz se genera mediante una ecuación de una recta, nosotros le pasamos al

motor los radios (interior y exterior) y este genera los valores m y n de nuestra ecuación.

En la tabla siguiente podremos observar cómo se realiza ese proceso a nivel de código.

Luz Omni

Radio exterior

Radio interior

Y = -mX + n

Y+

X+

+1

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 93 de 134

Entrada de los Radios de la Luz

//--------------------------------------------------------------------

// USE: Introduce los radios de la uz

// IN: float,float - Radio interior, Radio exterior

// OUT: Void

//--------------------------------------------------------------------

void CLight::SetLightRadis(float inRadiNear, float inRadiFar, float

inTime)

{

//Comprobamos que los valores de los radios no sean negativos

if(inRadiNear < 0.0f) inRadiNear = 0.0f;

if(inRadiFar < 0.0f) inRadiFar = 0.0f;

//Si el radio externo es menor que el interno, pasan a valer lo

//mismo

if(inRadiFar <= inRadiNear)

{

inRadiFar = inRadiNear;

}

//Si los radios son iguales, el radio exterior valdra una unidad

//mas

if(inRadiNear == inRadiFar)

{

inRadiFar = inRadiNear + 1.0f;

}

m_Radis.SetNewData(D3DXVECTOR4(inRadiNear,inRadiFar,0.0f,1.0f),i

nTime);

if(inTime > 0.0f)

{

}

else

{

m_fPendent = -1.0f / (inRadiFar - inRadiNear);

m_fOffset = m_fPendent * inRadiFar * -1.0f;

}

}

Tabla 33 - Generación de los parámetros m y n de la recta

En la tabla anterior podemos observar como a partir de dos radios, uno interno y otro externo,

generamos una serie de comparaciones para que estos radios sean correctos. Que un radio sea

correcto indica entre otras cosas que el radio externo no sea menor que el radio interno, o que

los dos radios no tengan valor cero a la vez.

Finalmente podemos observar como a partir de esos radios, se generan los parámetros de

Pendiente y Offset. Estos valores corresponden a los valores de m y n en la ecuación de la

recta.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 94 de 134

A continuación comentaremos como las luces cambian de parámetros de forma fluida y no

bruscamente.

Actualización de la Luz

//--------------------------------------------------------------------

// USE: Actualiza el color y los radios de la luz segun el tiempo

// IN: float - Elapsed Time

// OUT: Void

//--------------------------------------------------------------------

void CLight::Update(float inElapsedTime)

{

m_Color.Update(inElapsedTime);

m_Radis.Update(inElapsedTime);

if(m_Radis.GetTime() > 0.0f)

{

m_fPendent = -1.0f / (m_Radis.GetOutData().y -

m_Radis.GetOutData().x);

m_fOffset = m_fPendent * m_Radis.GetOutData().y * -1.0f;

}

}

Tabla 34 - Actualización de los parámetros de la Luz

En la tabla anterior podemos observar como los parámetros de la luz son actualizados. Esta

actualización se lleva a cabo mediante un objeto interno del motor 3D creado para tal efecto y

denominado ‘Interpolator’.

El ‘Interpolator’ es un vector de cuatro componentes que interpola entre dos valores gracias a

una diferencia de tiempo que nosotros le proporcionamos.

Podemos observar como en la tabla son actualizados el color y los radios de la luz, y si la

diferencia de tiempo para los radios aún está activa, entonces actualizamos los parámetros de

Pendiente y Offset de la ecuación de la recta de la luz.

3.2.3 Texturas

El objeto de textura ha sido creado para encapsular el tipo básico de textura relacionado

directamente con la librería gráfica DirectX.

De este modo aseguramos una mayor portabilidad a la hora de cambiar de librería pues este

objeto es el único que cambiará y creará una frontera para que los restantes objetos que

llamen a éste no tengan que ser cambiados.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 95 de 134

Como podemos observar a continuación, esta es la clase que engloba una textura de DirectX

en nuestro motor 3D.

Declaración de la textura

#include "DXUT.h"

class CTexture

{

private:

unsigned int m_iId;

unsigned int m_iInstances;

LPDIRECT3DTEXTURE9 m_tTexture;

//...

}

Tabla 35 - Encapsulado de Textura

Como vemos en la tabla anterior, el tipo de texturas en DirectX es LPDIRECT3DTEXTURE9 y

queda encapsulado en esta clase, creando la frontera que hemos comentado antes para que

no haya más cambios por encima de este objeto.

Para realizar este encapsulado, aparte de declarar un objeto de tipo textura, también

necesitamos unas funciones miembro que nos aseguren una correcta gestión de las texturas

en nuestro motor 3D.

A continuación veremos estas funciones.

Funciones miembro de la textura

public:

CTexture ();

CTexture (const CTexture& inObj);

~CTexture ();

void SetId (unsigned int inId);

void SetInstances (unsigned int inInst);

void SetTexturePtr (LPDIRECT3DTEXTURE9 inPtr);

void IncInstances ();

unsigned int GetId () const;

unsigned int GetInstances () const;

LPDIRECT3DTEXTURE9 GetTexturePtr () const;

void Release ();

CTexture& operator= (const CTexture& inObj);

bool operator== (const CTexture& inObj);

bool operator< (const CTexture& inObj);

Tabla 36 - Cuerpo de la clase textura

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 96 de 134

En la tabla anterior podemos observar el cuerpo de la clase textura, la cual nos ofrece

sistemas de gestión como las funciones para incrementar instáncias o decrementar, para

poder asegurar una copia única de texturas en memoria y así ser más eficientes con la

memoria de la tarjeta gráfica.

A nivel del manager de texturas, este nos ofrece unas funciones de gestión un poco diferentes

a las vistas en la clase textura.

Funciones miembro de manager de textura

class CTextureMgr

{

private:

vector<CTexture> m_vTextures;

vector< pair< wstring, unsigned int> > m_vNamesId;

wstring m_sRelativePath;

LPDIRECT3DDEVICE9 m_pDevice;

public:

CTextureMgr ();

~CTextureMgr ();

void SetRelativePath (const char* inPath);

void SetDevicePtr (LPDIRECT3DDEVICE9 inPtr);

unsigned int LoadTexture (const char* inName);

const CTexture& GetTexture (unsigned int inIndex)const;

bool ReleaseTexture (unsigned int inId, unsigned

int *outId);

void Release ();

};

Tabla 37 - Manager de textura

Como podemos observar en la tabla anterior, el manager de textura usa un camino de archivo

(path) relativo para así poder acceder a todas las texturas únicamente por su nombre y no

tener que poner todo el camino completo cada vez.

De igual forma, nos ofrece funciones para cargar y descargar las texturas a voluntad y en

tiempo real, esto nos asegura que las texturas cargadas en memoria son las óptimas si

optamos por cargarlas a medida que las necesitemos.

Por último comentar que el manager de texturas necesita inequívocamente el puntero al

objeto device de la librería DirectX para poder trabajar correctamente.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 97 de 134

3.2.4 Materiales

Como hemos visto en la sección teórica, los materiales nos sirven para poder dotar a los

objetos de propiedades visuales complejas sin necesidad de mucho esfuerzo.

Cada uno de los materiales está considerado como un objeto en memoria al igual como una

textura. Para un material, tenemos definido una clase que actuará como objeto contenedor de

la información de un material individual, también tenemos definido un formato XML para

poder representar ese material en disco.

A continuación veremos cómo ha estado definida la clase contenedora de un material y cuáles

son sus funciones asociadas.

Funciones de gestión de un Material

class CMaterial

{

private:

unsigned int m_iId;

unsigned int m_iDiffuseId;

//...

float m_fGlowIntensity;

string m_sTechName;

public:

CMaterial ();

CMaterial (const CMaterial& inObj);

~CMaterial ();

void SetId (unsigned int inId);

void SetEffectId (unsigned int inEId);

void SetInstances (unsigned int inInst);

void SetFlags (unsigned char inFlags);

void SetDiffuseId (unsigned int inId);

void SetNormalId (unsigned int inId);

void SetSpecularId (unsigned int inId);

//...

unsigned int GetEffectId () const;

unsigned int GetInstances () const;

unsigned char GetFlags () const;

unsigned int GetDiffuseId () const;

unsigned int GetNormalId () const;

unsigned int GetSpecularId () const;

CMaterial& operator= (const CMaterial& inObj);

bool operator== (const CMaterial& inObj);

bool operator< (const CMaterial& inObj);

};

Tabla 38 - Clase Material

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 98 de 134

Como podemos observar en la tabla anterior, el material como objeto está dotado de

múltiples funciones de gestión que no han sido representadas en su totalidad. Podemos

observar funciones para la inserción de identificadores de textura, necesarios para poder

acceder a las texturas que necesitemos.

Por otro lado también podemos ver cómo están definidos los operadores de igualdad,

comparación de igualdad y comparación de más grande que. Estos operadores nos ayudan a

que el código escrito sea más portable y rápido para el desarrollador.

A continuación mostramos el formato de un material escrito en disco, para así poder dar más

flexibilidad al motor 3D y no necesitar recompilarlo cada vez que haya un cambio.

Definición de un material

<Material name="Tronco">

<Properties>

<Scroll x="0.0" y="0.0"/>

<Scale x="3.0" y="5.0"/>

<Shader file="planar.fx" relname="planar.fx" technique="Basic"/>

</Properties>

<Textures>

<Diffuse name="Tronco1_d.tga"/>

<Normal name="Tronco1_n.tga"/>

<Specular name="default_g.tga"/>

<SelfIlum name="default_g.tga" intensity="0.2"/>

<LightMap name="default_l.tga"/>

</Textures>

</Material>

Tabla 39 - Formato de material

En la tabla anterior vemos el formato, que nos indica en cada momento cuales son las texturas

asociadas a cada uno de los tipos de texturas. Podemos contar cinco tipos diferentes como

comentamos en la sección teórica.

Fijémonos en el hecho de que cuando no queremos una textura en concreto para un canal,

esta debe ser definida como una textura por defecto. Esto nos asegura que no habrá ninguna

ambigüedad en el material y este se comportara de la manera deseada.

Al mismo tiempo también vemos como los parámetros de escalado y deslizamiento están

definidos. Estos parámetros pueden tener sus valores por defecto, o por el contrario, pueden

tener valores para que el material en pantalla sufra cambios de aspecto.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 99 de 134

Cada uno de estos materiales, es almacenado en un manager de materiales, el cual gestionara

cada uno de ellos y nos asegurará entre otras cosas que no haya materiales repetidos.

A continuación mostraremos las funciones de un manager de materiales para observarlas con

mayor precisión.

Definición del manager de materiales

class CMaterialMgr

{

private:

vector<CMaterial> m_vMaterials;

vector<unsigned int> m_vDeleted;

vector<unsigned int> m_vRelations;

vector< pair< wstring, unsigned int> > m_vNamesId;

CTextureMgr *m_pTexMgr;

CEffectMgr *m_pEffMgr;

string m_sRelativePath;

protected:

bool IsMatDeleted (unsigned int inIndex);

void UndeleteMat (unsigned int inIndex);

void ChangeTextIds (unsigned int inId1, unsigned int

inId2);

public:

CMaterialMgr ();

CMaterialMgr (const CMaterial& inObj);

~CMaterialMgr ();

void SetRelativePath (const char* inPath);

void SetTexMgrPtr (CTextureMgr *inPtr);

void SetEffMgrPtr (CEffectMgr *inPtr);

unsigned int SetMaterial (CMaterial& inMat);

unsigned int LoadMaterial (const char* inName);

CMaterial& GetMaterial (unsigned int inIndex);

void Release ();

void ReleaseMaterial (unsigned int inIndex);

};

Tabla 40 - Clase del manager de materiales

Como podemos observar en la tabla anterior, el manager de materiales está formado

internamente por unas estructuras de vectores mediante las que se relacionan los nombres de

los materiales con estos mismos.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 100 de 134

Por otro lado tenemos que el manager de materiales funciona prácticamente igual que la

metodología que hemos visto en el manager de texturas, cargando y descargando un material

cuando nosotros necesitemos.

3.2.5 Render Targets

Los Render Targets nos sirven para crear nuevas superficies de renderizado tal y como lo

explicamos en el apartado teórico correspondiente.

Cada uno de estos Render Targets será creado como objeto en nuestro motor 3D. Para cada

uno de ellos se definirá sus dimensiones y tipo de píxel. Gracias a esto podremos usar cada uno

de los Render Targets con el fin que queramos.

A continuación mostramos la clase que engloba los Render Targets y así tenemos una mejor

idea de cómo son accedidos por el motor 3D.

Definición del Render Target

class CRenderTarget

{

private:

LPD3DXRENDERTOSURFACE m_RenderToSurface;

LPDIRECT3DSURFACE9 m_Surface;

D3DFORMAT m_ColorFormat;

D3DFORMAT m_StencilZBufferFormat;

LPDIRECT3DTEXTURE9 m_tTexture;

LPDIRECT3DDEVICE9 m_Device;

bool m_bUseDepthBuffer;

public:

CRenderTarget ();

~CRenderTarget ();

bool CreateTexture ( LPDIRECT3DDEVICE9

inDev, unsigned int inWidth, unsigned int inHeight, D3DFORMAT

inColorFormat = D3DFMT_A8R8G8B8, D3DFORMAT inStencilFormat =

D3DFMT_UNKNOWN);

void BeginScene ();

void EndScene ();

void SaveToFile (const char* inName);

void DumpSurface (unsigned int inPosX1,

unsigned int inPosY1,unsigned int inOffX2,unsigned int inOffY2);

void LoadFromBackBuffer (unsigned int inPosX1,

unsigned int inPosY1,unsigned int inOffX2,unsigned int inOffY2);

void Release ();

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 101 de 134

LPDIRECT3DTEXTURE9 GetTexture ();

LPDIRECT3DSURFACE9 GetSurface ();

};

Tabla 41 - Clase Render Target

Fijémonos en que los datos internos del Render Target incluyen tanto una

LPDIRECT3DSURFACE9 como un LPDIRECT3DTEXTURE9. Esto es así debido a que el proceso

de render necesita una superficie para renderizar, pero el motor 3D necesita una textura para

poder operar con ella. De este modo tenemos una textura relacionada con su superficie y todo

encapsulado en una misma clase.

En la interfaz de la clase, podemos observar como la función para crear un Render Target

recibe numerosos parámetros que hemos comentado anteriormente. También podemos

observar que existen funciones para hacer volcados de memoria gráfica, tanto de Render

Target al Back Buffer como a la inversa.

Por último comentar que también existen las funciones para acceder desde fuera a la textura y

superficie que comentábamos antes.

A continuación veremos cómo se inicializa un Render Target.

Cómo se inicializa un Render Target

//--------------------------------------------------------------------

// USE: Crea la textura en memoria donde sera renderizada la escena

// deseada

// IN: Deivce, ancho y alto, y formatos tanto de color como de

// Stencil buffer

// OUT: bool - Cierto si ha ido bien, falso si hay algun error

//--------------------------------------------------------------------

bool CRenderTarget::CreateTexture( LPDIRECT3DDEVICE9 inDev, unsigned

int inWidth, unsigned int inHeight, D3DFORMAT inColorFormat, D3DFORMAT

inStencilFormat)

{

// Salvamos el device y demas para posibles usos

m_Device = inDev; //d3d_device = ad3d_device;

m_ColorFormat = inColorFormat; //color_format = acolor_format;

m_StencilZBufferFormat = inStencilFormat;

HRESULT hr;

hr = D3DXCreateTexture( m_Device,

inWidth, //Hanchura

inHeight, //Altura

1, //mip levels

D3DUSAGE_RENDERTARGET,

m_ColorFormat,

D3DPOOL_DEFAULT,

&m_tTexture);

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 102 de 134

if(hr != D3D_OK)

{

return (false);

}

hr = m_tTexture->GetSurfaceLevel(0, &m_Surface);

assert (hr == D3D_OK);

D3DSURFACE_DESC desc;

hr = m_Surface->GetDesc(&desc);

assert (hr == D3D_OK);

m_bUseDepthBuffer =( m_StencilZBufferFormat != D3DFMT_UNKNOWN );

hr = D3DXCreateRenderToSurface( m_Device,

desc.Width,

desc.Height,

desc.Format,

m_bUseDepthBuffer,

m_StencilZBufferFormat,

&m_RenderToSurface

);

return (hr == D3D_OK);

}

Tabla 42 - Inicialización de un Render Target

En esta tabla podemos observar como la textura y la superficie asociada al Render Target están

relacionadas entre sí, para que tanto el motor 3D como el proceso de render se puedan

beneficiar de ello.

3.2.6 Efectos Especiales

En lo que a efectos especiales se refiere, veremos conjuntamente como se hace la niebla y

como se hace el efecto Glow.

En la sección teórica vimos sus principios teóricos y como deberían ser encarados. Ahora,

veremos más en detalle como son realizados estos efectos especiales.

Hay que comentar primero que estos dos efectos especiales son realizados mediante las

instrucciones de píxel shader, y que por eso los trataremos juntos ya que están relacionados

por el proceso que los trata a la hora de hacer el Render.

Como primer efecto especial, veremos la niebla. Esta niebla está gobernada por un objeto que

controla en todo momento sus parámetros para pasárselos al shader correspondiente que nos

hará el render final.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 103 de 134

Definición de la niebla

class CFogMgr

{

private:

unsigned int m_iFramesRemainDis;

unsigned int m_iFramesRemainCol;

float m_fStart;

float m_fEnd;

float m_fIncStart;

float m_fIncEnd;

D3DXVECTOR4 m_FogColorNear;

D3DXVECTOR4 m_FogColorFar;

D3DXVECTOR4 m_IncColorNear;

D3DXVECTOR4 m_IncColorFar;

CInterpolator m_ColorNear;

CInterpolator m_ColorFar;

CInterpolator m_FogDistances;

public:

CFogMgr ();

~CFogMgr ();

void SetFogDistance (float inStart, float inEnd,

float inTime);

void SetFogColor (D3DXVECTOR4& inColNear,

D3DXVECTOR4& inColFar, float inTime);

void Update (float inElapsedTime);

float GetStart () const;

float GetEnd () const;

const D3DXVECTOR4& GetFogColorNear () const;

const D3DXVECTOR4& GetFogColorFar () const;

};

Tabla 43 - Clase de la niebla

Como podemos observar en la tabla anterior, la niebla está formada por 3 objetos de tipo

‘interpolator’. Estos objetos como ya hemos hablado anteriormente de ellos, son capaces de

darnos un valor interpolado entre dos valores predefinidos por el programador.

Al igual que necesitamos informar a la niebla de los colores que debe tener, también tenemos

que hacer una cosa muy importante y es actualizarla a través del tiempo. Gracias a la función

de actualización presente en esta clase, la niebla puede, mediante un tiempo determinado,

hacer los cambios pertinentes entre un color actual, y otro color futuro.

Fijémonos a continuación en el interior de la función de actualización de la niebla y podremos

observar como esta procede a través del tiempo.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 104 de 134

Actualización de la niebla

//--------------------------------------------------------------------

// USE: Actualiza segun los frames el color de la niebla

// IN: void

// OUT: Void

//--------------------------------------------------------------------

void CFogMgr::Update(float inElapsedTime)

{

m_ColorNear.Update(inElapsedTime);

m_ColorFar.Update(inElapsedTime);

m_FogDistances.Update(inElapsedTime);

}

Tabla 44 - Función de actualización de la niebla

Como podemos observar en la tabla anterior, los objetos ‘interpolator’ de la niebla son

actualizados mediante una porción de tiempo. Estos objetos en su interior se actualizan para

que siempre que nosotros pidamos sus valores, estos nos los devuelvan correctamente.

Una vez tenemos los valores que nos devuelve la niebla según sus parámetros actualizados a

través del tiempo, solo nos falta introducir dichos datos en el shader que renderiza la escena.

A continuación veremos las instrucciones que nos crean la niebla en pantalla.

Instrucciones de la niebla en el Shader

float f = saturate((fog_end - dis_eye_pixel)/(fog_end - fog_start));

float4 color_fog = lerp(fog_color_far,fog_color_near,f);

float4 final_plus_fog = lerp(color_fog,final,f);

Tabla 45 - Código de renderizado de la niebla

En la tabla anterior podemos observar como el factor de la niebla es calculado a partir de las

distancias de ésta y como acto seguido, son interpolados los colores de la niebla con el color

real del píxel de la escena.

Como ya se comentó en el apartado teórico, el hecho de hacer la niebla a partir de dos colores

es un aspecto experimental que ha dado unos buenos resultados a la hora de la verdad

añadiendo solo una instrucción más al píxel shader.

A continuación, trataremos el siguiente efecto especial que denominamos Glow. Si

recordamos lo que explicamos en el apartado teórico, el Glow es una representación de un

material que por sí sólo pretender emanar luz a la escena.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 105 de 134

Este efecto se consigue mediante una textura donde se especifica el lugar y el color del efecto

seguido por un proceso de post procesado que nos crea el efecto deseado.

La metodología para aplicar el Glow consiste en un efecto de post procesado de emborronar la

información de color que nos da la textura de Glow.

A continuación podemos ver el código que nos permite hacer este efecto.

Post procesado del Glow

for(unsigned int i = 0; i < m_iGlowLoops; i++)

{

m_RT2.BeginScene();

// Limpiamos el RenderTarget para pintar las texturas de

SelfIlumination

V( m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET |

D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB(0, 0, 0, 0), 1.0f, 0) );

unsigned int cPasses;

m_EffectMgr.GetEffect(IL_EFFECT_GLOW)->Begin( &cPasses, 0 );

//...

for( unsigned int p = 0; p < cPasses; ++p )

{

m_EffectMgr.GetEffect(IL_EFFECT_GLOW)->BeginPass( p );

m_pD3DDevice->SetFVF( D3DFVF_TEXTUREDVERTEX );

m_pD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2,

m_vRenderQuad, sizeof( TEXTUREDVERTEX ) );

m_EffectMgr.GetEffect(IL_EFFECT_GLOW)->EndPass();

}

m_EffectMgr.GetEffect(IL_EFFECT_GLOW)->End();

m_RT2.EndScene();

m_RT3.BeginScene();

// Limpiamos el RenderTarget para pintar las texturas de

SelfIlumination

V( m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET |

D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB(0, 0, 0, 0), 1.0f, 0) );

//...

for( unsigned int p = 0; p < cPasses; ++p )

{

m_EffectMgr.GetEffect(IL_EFFECT_GLOW)->BeginPass( p );

m_pD3DDevice->SetFVF( D3DFVF_TEXTUREDVERTEX );

m_pD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2,

m_vRenderQuad, sizeof( TEXTUREDVERTEX ) );

m_EffectMgr.GetEffect(IL_EFFECT_GLOW)->EndPass();

}

m_EffectMgr.GetEffect(IL_EFFECT_GLOW)->End();

m_RT3.EndScene();

}

Tabla 46 - Metodología del Glow

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 106 de 134

En la tabla anterior podemos observar que la metodología del Glow se basa en una serie de

bucles que emborronan los píxeles de la imagen para poder formar el efecto deseado. El

número de vueltas que se aplican a este efecto es un parámetro proporcionado por el

desarrollador

Post procesado de Glow en el Píxel Shader

float4 RenderGlowPS(

float2 inTex0 : TEXCOORD0

) : COLOR

{

float2 pixelSize = float2 (dx, dy);

float2 texCoordSample = 0;

float2 halfPixelSize = pixelSize / 2.0f;

float2 dUV = (pixelSize.xy*float(iteration))+ halfPixelSize.xy;

float3 cOut;

//Recuperamos el pixel de arriba-izquierda

texCoordSample.x = inTex0.x - dUV.x ;

texCoordSample.y = inTex0.y + dUV.y;

cOut = tex2D(GlowSampler, texCoordSample);

//Recuperamos el pixel de arriba-derecha

texCoordSample.x = inTex0.x + dUV.x;

texCoordSample.y = inTex0.y + dUV.y;

cOut += tex2D(GlowSampler, texCoordSample);

//Recuperamos el pixel de abajo-derecha

texCoordSample.x = inTex0.x + dUV.x;

texCoordSample.y = inTex0.y - dUV.y;

cOut += tex2D(GlowSampler, texCoordSample);

//Recuperamos el pixel de abajo-izquierda

texCoordSample.x = inTex0.x - dUV.x;

texCoordSample.y = inTex0.y - dUV.y;

cOut += tex2D(GlowSampler, texCoordSample);

cOut*= 0.25f;

return (float4(cOut,1.0f));

}

Tabla 47 - Píxel Shader del post procesado Glow

En la tabla anterior podemos observar como el código del píxel shader encargado de hacer el

efecto Glow accede a los píxeles vecinos de estos y hace una media para así poder devolver

ese valor como resultado.

Este proceso nos da como resultado un emborronamiento de la imagen que será necesario

debido a la naturaleza del efecto Glow.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 107 de 134

3.2.7 Estructura Pipe-Line

En el apartado teórico hablamos sobre la estructura pipeline que tiene nuestro motor 3D para

generar cada una de las imágenes que nos muestra por pantalla.

La estructura pipeline no es más que la secuencia de procesos que debemos realizar para

poder construir cada unos de los fotogramas que mostraremos en pantalla.

Inicializamos las matrices

//Recuperamos las matrices necesarias

mWorld = mIdentity = *D3DXMatrixIdentity(&mWorld);

mProj = m_Camera.GetProjMatrix();

mView = m_Camera.GetViewMatrix();

mWorldViewProjection = mWorld * mView * mProj;

//Actualizamos la posicion de la luz

vLightPos.x = m_Camera.GetTarget().x;

vLightPos.y = m_Camera.GetTarget().y;

vLightPos.z = m_Camera.GetTarget().z;

vLightPos.w = 1.0f;

m_vLights[0].SetPosition(vLightPos);

Tabla 48 - Inicialización de matrices

Como podemos observar en la última tabla, el primer proceso que debemos realizar es la

actualización de las matrices involucradas en la generación del fotograma actual.

Por ello será necesario recuperar estas matrices de la cámara activa que tengamos en estos

momentos.

Inicializamos la geometría

// Cargamos solo aquella geometria que sea visible por la camara

LoadVisibleMesh(mWorldViewProjection);

//Cargamos la geometria visible para cada una de las caras del

//cubemap

LoadVisibleMeshCubeMap();

//Ordenamos la geometria segun el material que tienen.

SortVisibleMesh();

//Renderizamos las caras del cubemap

RenderShadowCubeMap();

Tabla 49 - Inicialización de la geometría

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 108 de 134

Junto a esta última tabla y si recordamos el diagrama de la estructura pipeline en el apartado

teórico, podremos relacionar los procesos de la inicialización de la geometría, al igual como la

ordenación de ésta y la generación de los datos necesarios para hacer las sombras.

Podemos observar como la inicialización de la geometría para utilizar solo aquella que estamos

viendo en estos momentos se hace a partir de la matriz resultante de la cámara activa en estos

instantes.

Momentos después se procede a ordenar esta geometría y a generar los shadowmap para su

utilización en el sombreado.

Primeros pasos para hacer el Glow

//Renderizamos los objetos que tienene SelfIlum en un

//rendertarget

if(m_bDebugGlow)

{

SetQuadUVtoRT(m_iScreenWidth,m_iScreenHeight);

RenderSelfIlum(&mWorldViewProjection);

}

else

{

fGlowActive = 0.0f;

}

RenderSkyBox(&mWorldViewProjection);

Tabla 50 - Inicialización del Glow

En esta última tabla, podemos observar cómo se hacen los preparativos para hacer el Glow en

la escena.

Podemos ver cómo si el Glow está activo, se renderiza la geometría con el material específico

de Glow activado.

Poco después de hacer este proceso, procedemos a renderizar lo que en la escena será el cielo

o cubo envolvente.

Renderizamos en el Back Buffer

if( SUCCEEDED( m_pD3DDevice->BeginScene() ) )

{

Tabla 51 - Activación del Back Buffer

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 109 de 134

A continuación observamos una de las partes importantes de nuestro proceso de renderizado

y es el hecho de renderizar la geometría principal en el Back Buffer y no en un Render Target.

Esto es así debido a que si renderizamos en un Render Target, perderemos la habilidad que

nos ofrece la librería gráfica para aplicar vía hardware un filtrado de Anti-Aliasing en dicha

geometría.

Paso de parámetros al shader

V(m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetMatrix("world_view_proj",

&mWorldViewProjection ) );

V(m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetVector("light_pos",

&vLightPos ) );

V(m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetVector("light_color",

&m_vLights[0].GetColor() ) );

V(m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetFloat("light_pendent",

m_vLights[0].GetPendent() ) );

V(m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetFloat("light_offset",

m_vLights[0].GetOffset() ) );

V(m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetVector("eye_pos",

&vEyePos ) );

V(m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetFloat("fog_start",

m_FogMgr.GetStart() ) );

V(m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetFloat("fog_end",

m_FogMgr.GetEnd() ) );

V(m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetVector("fog_color_near",

&m_FogMgr.GetFogColorNear() ) );

V(m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetVector("fog_color_far",

&m_FogMgr.GetFogColorFar() ) );

Tabla 52 - Inicialización de los parámetros del shader

En la tabla anterior podemos observar cómo pasamos los parámetros necesarios al shader

para que éste pueda renderizar adecuadamente la geometría que nosotros le indiquemos.

Uno de los parámetros más importantes que debemos pasarle al shader es la matriz resultante

de la cámara para que la perspectiva pueda ser creada acorde con nuestras necesidades.

Activación del modo alambre

//Si esta activado el bool de renderizado en alambre, entonces

//activamos el flag interno del pipeline para poder pintar de esta

//forma

if(m_bWireMode)

{

m_pD3DDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);

}

Tabla 53 - Parámetro para pintado en alambre

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 110 de 134

Como podemos observar en esta última tabla, con ese parámetro podemos activar la función

de pintado mediante alambre de la geometría. Esta función es puramente un método de

depuración o metodología usada mientras se desarrolla la aplicación, ya que en pleno uso, la

aplicación no mostrará este tipo de pintado.

Renderizado de la geometría principal

for(unsigned int j = 0; j < (unsigned int)m_vSubMeshes.size(); j++)

{

unsigned int VIndex;

unsigned int IIndex;

iMaterial=m_SSubMeshMgr.GetSSubMesh(m_vSubMeshes[j]).GetMatId();

m_MaterialMgr.GetMaterial(iMaterial).Update(inElapsedTime);

if(m_MaterialMgr.GetMaterial(iMaterial).GetGlowIntensity()>0.5f)

{

int a = 1;

}

m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetTechnique(

m_MaterialMgr.GetMaterial(iMaterial).GetTechName() );

V( m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->SetFloat( "scalex",

m_MaterialMgr.GetMaterial(iMaterial).GetScalex() ) );

//...

unsigned int cPasses;

m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->Begin( &cPasses, 0 );

for( unsigned int p = 0; p < cPasses; ++p )

{

m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->BeginPass( p );

//Para cada sub-mesh, ponemos su material en posicion para

ser utilizado

handle_param = m_EffectMgr.GetEffect(IL_EFFECT_BASIC)-

>GetParameterByName( NULL, "DiffuseTexture" );

//...

m_pD3DDevice->SetFVF(FVF_Format);

m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->CommitChanges();

VIndex = m_SSubMeshMgr.GetSSubMesh(m_vSubMeshes[j]).GetVBId();

IIndex = m_SSubMeshMgr.GetSSubMesh(m_vSubMeshes[j]).GetIBId();

m_pD3DDevice->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST,

0,

m_VertexArrayMgr.GetVertexArray(VIndex).GetNumVertex(),

m_IndexArrayMgr.GetIndexArray(IIndex).GetNumIndex(),

m_IndexArrayMgr.GetIndexArray(IIndex).GetIndexPtr(),

D3DFMT_INDEX16,

m_VertexArrayMgr.GetVertexArray(VIndex).GetVertexPtr(),

sizeof(FVFVertex)

);

m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->EndPass();

}

m_EffectMgr.GetEffect(IL_EFFECT_BASIC)->End();

}

Tabla 54 - Renderizado en el Back Buffer

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 111 de 134

En la tabla anterior podemos observar el proceso de renderizado de la geometría principal en

el Back Buffer.

Este proceso pasa por etapas como inicialización de parámetros del shader específicos para el

material de la geometría que se está pintando en ese momento. Luego entramos en el bucle

para pintar todas las submallas que intervienen en una malla normal.

En todo este proceso podemos observar cómo se introducen datos tanto en forma de

parámetros lógicos como en forma de texturas. Una vez todos estos parámetros han sido

iniciados para un objeto en concreto, llamamos a la función de renderizado diciéndole cuantos

triángulos tiene el objeto, longitud en memoria de estos, etc.

Pintado de líneas en el motor 3D

m_pD3DDevice->SetTransform( D3DTS_WORLD, &mWorld );

m_pD3DDevice->SetTransform( D3DTS_VIEW, &mView );

m_pD3DDevice->SetTransform( D3DTS_PROJECTION, &mProj );

DebDrawBoundingBoxes();

//Dibujamos los ejes centrados en el (0,0,0)

DebDrawAxis(D3DXVECTOR3(0.0f,0.0f,0.0f),10.0f);

//Dibujamos ejes dobles en el target de la camara

DebDrawAxis(m_Camera.GetTarget(),1.0f,true);

DebDrawLights(0.5f);

DebDrawNormals(5.0f);

DebDrawTangents(5.0f);

DebDrawTriggers();

DebDrawPath();

m_pD3DDevice->SetTransform( D3DTS_WORLD, &mIdentity );

m_pD3DDevice->SetTransform( D3DTS_VIEW, &mIdentity );

m_pD3DDevice->SetTransform( D3DTS_PROJECTION, &mIdentity

Tabla 55 - Renderizado de líneas

En la tabla anterior podemos observa la parte del proceso de renderizado que hace referencia

al pintado de líneas. El pintado de líneas nos es útil para representar en pantalla objetos de

carácter informativo. Un ejemplo de estos objetos pueden ser las Bounding Boxes o las

normales de una malla cualquiera.

A continuación veremos la última parte del proceso de renderizado. Esta parte hacer

referencia al proceso de integración entre todos los renders que hemos hecho anteriormente,

ya sean de geometría principal como de información de carácter de Glow u otros tipos de

información.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 112 de 134

Integración de todos los datos

m_RTmain.LoadFromBackBuffer(0,0,m_iScreenWidth,m_iScreenHeight);

//Configuramos las UV's del Quad que servira de bolcado en

//pantalla de los RT's

SetQuadUVtoRT((m_iScreenWidth >> m_iGlowDownSamples),

(m_iScreenHeight >> m_iGlowDownSamples));

//Extraemos el brillo de la escena con una limite inferior

RenderBright();

//Fusionamos la textura de Self-Ilumination con la de extraccion

//de brillo

RenderSelfPlusBright();

//Aplicamos glow al rendertarget de SelfIlum + el brillo

//extraido de la escena final

RenderGlow();

//Configuramos las UV's del Quad que servira de bolcado en

//pantalla de los RT's

SetQuadUVtoRT(m_iScreenWidth, m_iScreenHeight);

//Fusionamos el render final con la textura resultante de glow

RenderMainPlusGlow();

m_RT1.DumpSurface(0,0,m_iScreenWidth,m_iScreenHeight);

Tabla 56 - Finalización del proceso de render

En esta última tabla podemos observar como guardamos en un Render Target la información

que teníamos en el Back Buffer para poder trabajar con ella posteriormente.

Seguidamente renderizamos el brillo de la escena para que forme parte del Glow, seguido del

render del propio Glow.

Finalizamos con la integración de toda la información y volcamos los gráficos otra vez al Back

Buffer para ser mostrados por pantalla.

3.3 Tratamiento de eventos

3.3.1 Trama

Cuando hablábamos en la sección teórica sobre las tramas, las describimos como

contenedores de información en el que nosotros indicábamos en cada momento que tipo de

dato insertábamos.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 113 de 134

Definición de una trama

class CFrame

{

private:

unsigned int m_iLength;

unsigned int m_iIndex;

char m_sTrama[IL_FRAME_LENGTH];

public:

CFrame ();

~CFrame ();

void AddChar (char inChar);

void AddUChar (unsigned char inChar);

void AddShort (short inShort);

void AddUShort (unsigned short inShort);

void AddInt (int inInt);

//...

};

Tabla 57 - Clase Trama

En la tabla anterior, podemos observar como un objeto trama es definido según su clase.

Podemos observar también cómo existen todas las funciones necesarias para poder introducir

y al mismo tiempo recuperar los datos que necesitemos y del tipo que requiramos.

Por otro lado también podemos observar como todos los datos almacenados son en forma de

cadena de caracteres, fácil de copiar y de transportar.

3.3.2 Eventos

Un evento es un tipo de objeto que en su interior contiene un objeto contenedor de datos en

forma de trama.

Estos objetos de tipo evento son utilizados en el motor para poder transportar eventos de un

lado a otro donde estos sean necesarios, como la entrada de teclado, de ratón, etc.

Cada uno de estos eventos posee, como ya hemos dicho, una trama en su interior, pero al

mismo tiempo posee un identificador único de evento, el cual le permite ser identificado por el

motor 3D y tratado como tal. Recordemos que si la trama interna de un evento no es tratada

como es debido, nos da como resultado información corrupta.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 114 de 134

Definición de un evento

class CEvent

{

private:

unsigned int m_iEventType;

CFrame m_Frame;

public:

CEvent ();

~CEvent ();

void SetType (unsigned int inType);

void SetFrame (CFrame& inFrame);

unsigned int GetType () const;

CFrame& GetFrame ();

const CFrame& GetCFrame () const;

CEvent& operator = (const CEvent& inEvent);

bool operator == (const CEvent& inEvent);

bool operator < (const CEvent& inEvent);

};

Tabla 58 - Clase evento

En la tabla anterior podemos observar el identificador del que hablábamos. Junto a estos

datos, podemos observar todas las funciones de acceso que un evento nos proporciona, desde

insertar una trama dentro del evento, hasta operaciones de igualdad para facilitar su uso de

cara al desarrollador.

3.3.3 Entrada / Salida

Cuando hablamos de entrada y salida de datos a la aplicación, nos referimos a la entrada que

un periférico puede hacer en nuestro motor 3D.

Es por este motivo por el cual mostraremos como tanto el teclado como el ratón generan su

propia información y esta es transformada en eventos hábiles para el motor 3D.

Fragmento de la entrada / salida

case WM_KEYDOWN:

mFrame.AddUInt((unsigned int) wParam);

mFrame.AddUInt((unsigned int) lParam);

mEvent.SetType(IL_KEYDOWN);

mEvent.SetFrame(mFrame);

g_pEngine->SetEvent(mEvent);

gKeybEntrys ++;

break;

Tabla 59 - Transformación de E/S en evento

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 115 de 134

En la tabla anterior podemos observar como el mensaje que nos proporciona el sistema

operativo es transformado en un evento que nuestro motor 3D es capaz de interpretar y

tratar.

3.4 Escena

3.4.1 Vértices flexibles

Cuando hablamos de los vértices flexibles en el apartado teórico, veíamos como estos pueden

cambiar en forma según nuestras necesidades.

Seguidamente observaremos como son definidos y como informamos al motor 3D de su

estructura para que éste pueda interpretarlos.

Definición de un vértice flexible

//--------------------------------------------------------------------

// FVF Format - Static Mesh

//--------------------------------------------------------------------

struct FVFVertex

{

float x, y, z; // The untransformed, 3D position for the vertex

float nx, ny, nz; // The untransformed, 3D normal for the vertex

float tu1,tv1;

float tu2,tv2;

D3DXVECTOR4 Tangent;

};

#define FVF_Format

(D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX3|D3DFVF_TEXCOORDSIZE2(0)|D3DFVF_T

EXCOORDSIZE2(1)|D3DFVF_TEXCOORDSIZE4(2))

Tabla 60 - Vértices flexibles

En la tabla anterior podemos observar como nuestro vértice flexible es definido con una serie

de atributos. De igual forma, una vez definido este vértice flexible, nos definimos una etiqueta

que usaremos a lo largo de las instrucciones del motor 3D para informarle de qué contiene

este vértice flexible a estas instrucciones.

Una vez hemos definido el vértice flexible en nuestro motor, solo nos queda informar a

nuestro motor 3D de qué tamaños tienen nuestros atributos en memoria. Esto es sumamente

importante ya que gracias a esto, el motor 3D sabrá moverse por la memoria sin generar

accesos indebidos de ésta.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 116 de 134

Memoria usada por el vértice flexible

const D3DVERTEXELEMENT9 g_VBDecl_Geometry[] =

{ {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},

{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},

{0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},

{0, 32, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},

{0, 40, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},

D3DDECL_END()

};

Tabla 61 - Definición de la memoria de un vértice flexible

En la tabla anterior podemos observar cómo definimos la memoria asociada a cada uno de los

vértices flexibles.

Como ya comentábamos, esta definición es esencial para que los accesos a memoria sean

correctos en todo momento.

3.4.2 Vectores de índices y vértices

Una vez tenemos definidos todos los tipos de vértices que vamos a utilizar, es momento de

definir las estructuras que los agruparán para formar tanto los vectores de vértices como los

vectores de índices.

Definición de un vector de vértices

class CVertexArray

{

private:

FVFVertex *m_pVertexArray;

unsigned int m_iNumVertex;

unsigned int m_iId;

public:

CVertexArray ();

CVertexArray (const CVertexArray& inObj);

~CVertexArray ();

void SetId (unsigned int inId);

void SetVertexArray (FVFVertex *inPtr, unsigned

int inNum);

unsigned int GetId () const;

FVFVertex* GetVertexPtr () const;

unsigned int GetNumVertex () const;

void Release ();

CVertexArray& operator= (const CVertexArray& inObj);

bool operator== (const CVertexArray& inObj);

bool operator< (const CVertexArray& inObj);

};

Tabla 62 - Clase VertexArray

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 117 de 134

En la tabla anterior podemos observar la definición de un vector de vértices. Es importante

destacar que este vector es del tipo de vértices que hemos definido previamente y que

contiene todas las operaciones necesarias para poder acceder a la información de dicho

vector.

Definición de un vector de índices

class CIndexArray

{

private:

unsigned short *m_pIndexArray;

unsigned short m_iNumIndex;

unsigned int m_iId;

public:

CIndexArray ();

CIndexArray (const CIndexArray& inObj);

~CIndexArray ();

void SetId (unsigned int inId);

void SetIndexArray (unsigned short *inPtr,

unsigned int inNum);

unsigned int GetId () const;

unsigned short* GetIndexPtr () const;

unsigned short GetNumIndex () const;

void Release ();

CIndexArray& operator= (const CIndexArray& inObj);

bool operator== (const CIndexArray& inObj);

bool operator< (const CIndexArray& inObj);

};

Tabla 63 - Clase IndexArray

Por el contrario en la tabla anterior podemos observar la definición del vector de índices, este

vector de índices irá acompañado indiscutiblemente de un vector de vértices para poder tratar

correctamente su información.

3.4.3 Mallas

Como comentábamos en el apartado teórico, las mallas son un conjunto de submallas que se

unen para formar un modelo al completo.

Cada una de estas submallas tiene su propio material asociado, con lo que nos puede dar

como resultado una malla con diferentes materiales debido a su estructura de submallas.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 118 de 134

Definición de una malla

class CSMesh

{

private:

unsigned int m_iId;

bool m_bVisible;

vector <unsigned int> m_vSubMeshes;

unsigned int m_iPhysicVBId;

unsigned int m_iPhysicIBId;

CBoundingBox m_BoundingBox;

public:

CSMesh ();

CSMesh (const CSMesh& inObj);

~CSMesh ();

void SetId (unsigned int inId);

void SetVisible (bool inVis);

void SetSubMeshId (unsigned int inId);

void SetPhysicVBId (unsigned int inId);

void SetPhysicIBId (unsigned int inId);

void SetBoundingBox (CBoundingBox& inBox);

unsigned int GetId () const;

bool GetVisible () const;

unsigned int GetNumSMeshes () const;

unsigned int GetSubMeshId (unsigned int inIndex)

const;

unsigned int GetPhysicVBId () const;

unsigned int GetPhysicIBId () const;

CBoundingBox& GetBoundingBox ();

const CBoundingBox& GetCBoundingBox () const;

CSMesh& operator= (const CSMesh& inObj);

bool operator== (const CSMesh& inObj);

bool operator< (const CSMesh& inObj);

};

Tabla 64 - Clase CSMesh

En la tabla anterior podemos observar cómo una malla es definida, fijémonos en que contiene

una lista de identificadores a submallas. Esto es así porque es la manera de trabajar en los

managers, pues todo esta indexado para su rápido acceso.

Definición de un manager de mallas

class CSMeshMgr

{

private:

vector<CSMesh> m_vSMeshMgr;

CMaterialMgr *m_pMatMgr;

CSSubMeshMgr *m_pSSubMMgr;

public:

CSMeshMgr ();

~CSMeshMgr ();

unsigned int SetSMesh (CSMesh &inObj);

void SetMatMgrPtr (CMaterialMgr *inPtr);

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 119 de 134

void SetSSubMMgrPtr (CSSubMeshMgr *inPtr);

CSMesh& GetSMesh (unsigned int inIndex);

unsigned int GetNumSMesh () const;

void Release ();

};

Tabla 65 - Clase CSMeshMgr

Aquí podemos ver el manager de mallas, que continúa con la misma metodología y encapsula

objetos malla al mismo tiempo que estos serán accedidos según su identificador.

3.4.4 Submallas

Las submallas son un conjunto atómico de un vector de vértices y un vector de índices. Los dos

vectores dan la suficiente información para que podamos interpretar los triángulos de un

objeto en concreto.

Normalmente al tratarse de una submalla, esta será considerada como una parte de ese

objeto, aunque podría tratarse de un objeto por sí misma.

Definición de una submalla

class CSSubMesh

{

private:

unsigned int m_iVertexBuffer;

unsigned int m_iIndexBuffer;

unsigned int m_iMaterialId;

unsigned int m_iNumVertex;

unsigned int m_iNumFaces;

unsigned int m_iId;

CBoundingBox m_BoundingBox;

public:

CSSubMesh ();

CSSubMesh (const CSSubMesh& inObj);

~CSSubMesh ();

void SetId (unsigned int inId);

void SetVBId (unsigned int inId);

void SetIBId (unsigned int inId);

void SetMatId (unsigned int inMId);

void SetNumVertex (unsigned int inNum);

void SetNumFaces (unsigned int inNum);

void SetBoundingBox (CBoundingBox& inBox);

//...

};

Tabla 66 - Clase CSSubMesh

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 120 de 134

En la anterior tabla podemos observar como una submalla tiene inequívocamente un

identificador de vector de vértices y un identificador de vector de índices.

Por otro lado también podemos observar como la submalla tiene un identificador a un

material al igual como contiene un objeto de Bounding Box para su uso en representaciones

visuales.

Definición del manager de submallas

class CSSubMeshMgr

{

private:

vector<CSSubMesh> m_vSubMeshMgr;

public:

CSSubMeshMgr ();

~CSSubMeshMgr ();

unsigned int SetSSubMesh (CSSubMesh &inObj);

CSSubMesh& GetSSubMesh (unsigned int inIndex);

unsigned int GetNumSSubMesh () const;

void Release ();

};

Tabla 67 - Clase CSSubMeshMgr

En la tabla anterior podemos observar como la definición del manager de submallas se ha

llevado a cabo. Podemos ver como el manager de submallas contiene en su interior un vector

de submallas que será accedido mediante índices para insertar o devolver los objetos

requeridos.

3.4.5 Bounding Boxes

Las Bounding Boxes nos sirven a lo largo de la aplicación para simplificar la forma de los

objetos que nosotros trataremos en nuestro motor 3D. Para eso, es necesario crear una caja

3D con las dimensiones mínimas en las que pueda caber el objeto 3D en concreto.

Para llevar a cabo esta metodología es necesario hacer un pre-cálculo en el que están

involucrados todos los vértices del objeto. Para todos los vértices del objeto, tenemos que

quedarnos con los valores mínimos de esos vértices al igual como los valores máximos.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 121 de 134

El resultado de ese punto mínimo y máximo nos definirá una caja 3D con volumen mínimo

para que pueda caber dicho objeto.

Definición de una Bounding Box

class CBoundingBox

{

private:

unsigned char m_cUsedVertex;

D3DXVECTOR3 m_vUpperVertex;

D3DXVECTOR3 m_vLowerVertex;

protected:

void CheckVertex ();

public:

CBoundingBox ();

CBoundingBox (const CBoundingBox&);

~CBoundingBox ();

void SetUpperVertex (D3DXVECTOR3& inV);

void SetLowerVertex (D3DXVECTOR3& inV);

void SetCenterAndSize (D3DXVECTOR3& inV, float

inHalfSize);

bool IsInside (D3DXVECTOR3& inV);

bool IsInside (float in1, float in2, float

in3);

bool IsVectorCollide (D3DXVECTOR3&

inPos,D3DXVECTOR3& inDir);

const D3DXVECTOR3& GetUpperVertex () const;

const D3DXVECTOR3& GetLowerVertex () const;

D3DXVECTOR3 GetCenter ();

D3DXVECTOR3 GetUpperCenter ();

D3DXVECTOR3 GetLowerCenter ();

unsigned char GetUsedVertex () const;

float GetVolume () const;

void Reset ();

CBoundingBox& operator= (const CBoundingBox& inObj);

bool operator== (const CBoundingBox& inObj);

bool operator< (const CBoundingBox& inObj);

};

Tabla 68 - Clase CBoundingBox

En la tabla anterior podemos observar la definición de un objeto de tipo Bounding Box. En esta

definición podemos observar cómo la Bounding Box está formada por dos puntos 3D que son

los que crearan la caja 3D.

Por otro lado tenemos funciones de acceso a los datos los cuales facilitan la vida al

desarrollador.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 122 de 134

3.4.6 Cámaras

En nuestro motor 3D, la cámara es el objeto encargado en todo momento de gestionar las tres

matrices más importantes de la escena. Nos referimos a la matriz de mundo, visión y

proyección.

La cámara encapsula el control de estas tres matrices y aparte da una funcionalidad gracias a

su interfaz para que el desarrollador pueda acceder rápidamente a controles de dirección de la

cámara, apertura de esta, etc.

Definición de la cámara

class CCamera

{

private:

unsigned int m_iId;

unsigned int m_iType;

D3DXMATRIX m_mView;

D3DXMATRIX m_mProjection;

D3DXMATRIX m_mViewProjection;

//...

float m_fPitch;

public:

CCamera ();

~CCamera ();

void SetId (unsigned int inId);

void SetCameraType (unsigned int inType);

void SetViewMatrix (D3DXMATRIX& inVMatrix);

void SetViewMatrixInv (D3DXMATRIX& inVMatrixI);

void SetProjMatrix (D3DXMATRIX& inPMatrix);

void SetViewProjMatrix (D3DXMATRIX& inVPMatrix);

void RotateCamera (float inDx, float inDy);

void TranslateCamera (D3DXVECTOR3& inVec);

//...

unsigned int GetId () const;

unsigned int GetCameraType () const;

const D3DXMATRIX& GetViewMatrix () const;

const D3DXMATRIX& GetViewMatrixInv () const;

const D3DXMATRIX& GetProjMatrix () const;

const D3DXMATRIX& GetViewProjMatrix () const;

CCamera& operator= (const CCamera& inObj);

bool operator== (const CCamera& inObj);

bool operator< (const CCamera& inObj);

};

Tabla 69 - Clase CCamera

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 123 de 134

Como podemos observar en la tabla anterior, una cámara contiene muchos datos en su

interior y gracias a esto, es capaz de darnos en todo momento información sobre las tres

matrices esenciales que ésta controla.

De igual forma, gracias a la interfaz de las cámaras, podemos usarlas de una forma sencilla y

eficiente.

También es necesario comentar que en la tabla anterior, la interfaz ha sido simplificada debido

a su tamaño en funciones y datos. En el código fuente se pueden encontrar en su forma

completa.

3.4.7 Frustrums

Cuando hablamos de frustrums en el apartado teórico, pudimos ver que estos nos ayudan a

determinar qué objetos están dentro del área de visión y qué objetos no.

Para generar un frustrum es necesario crear seis planos que nos delimiten un volumen. Por

ello a continuación veremos la interfaz del objeto frustrum.

Definición del objeto frustrum

class CFrustrum

{

private:

D3DXPLANE m_Left;

D3DXPLANE m_Right;

D3DXPLANE m_Bottom;

D3DXPLANE m_Top;

D3DXPLANE m_Near;

D3DXPLANE m_Far;

public:

CFrustrum ();

~CFrustrum ();

void SetFrustrum (D3DXMATRIX& inMat);

bool IsPointInFrustrum (D3DXVECTOR3& inVec);

bool IsPointInFrustrum (float inX, float inY, float inZ);

unsigned int IsBBoxInFrustrum (CBoundingBox& inBBox, bool

inExact = false);

};

Tabla 70 - Clase CFrustrum

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 124 de 134

En la tabla anterior podemos observar cómo el frustrum está constituido básicamente por seis

planos.

Cada uno de estos seis planos es generado gracias a una matriz de entrada. Concretamente

esta matriz viene determinada por la cámara.

Una vez tenemos los seis planos determinados, es trivial determinar si un punto 3D está

contenido dentro del volumen de frustrum o no.

Podemos observar también que en la interfaz existen funciones que nos determinan si un

punto esta contenido dentro del volumen o no para ayudar al desarrollador.

3.5 Sombras

3.5.1 Cube Shadow Maps

Tal y como comentábamos en el apartado teórico, las sombras de este motor 3D se han hecho

a partir de un tipo de sombras llamadas Shadow Maps. Y hemos extendido esta teoría para

usarlas como Cube Shadow Maps.

Un Cube Map no difiere mucho del anterior objeto del que hemos hablado. Los dos tienen seis

partes primordiales en su interior, donde el frustrum tenía planos, en el Cube Map tenemos

superficies de renderizado.

Datos internos del Cube Map

class CFrustrum

{

LPDIRECT3DDEVICE9 m_Device;

LPD3DXRENDERTOSURFACE m_RenderToSurface;

LPDIRECT3DSURFACE9 m_Surface;

D3DFORMAT m_ColorFormat;

D3DFORMAT m_StencilZBufferFormat;

LPDIRECT3DCUBETEXTURE9 m_CubicShadowMap;

LPDIRECT3DSURFACE9 m_DepthCubeFacePX;

LPDIRECT3DSURFACE9 m_DepthCubeFacePY;

LPDIRECT3DSURFACE9 m_DepthCubeFacePZ;

LPDIRECT3DSURFACE9 m_DepthCubeFaceNX;

LPDIRECT3DSURFACE9 m_DepthCubeFaceNY;

LPDIRECT3DSURFACE9 m_DepthCubeFaceNZ;

D3DXMATRIX m_mViewProjPX;

D3DXMATRIX m_mViewProjPY;

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 125 de 134

D3DXMATRIX m_mViewProjPZ;

D3DXMATRIX m_mViewProjNX;

D3DXMATRIX m_mViewProjNY;

D3DXMATRIX m_mViewProjNZ;

D3DXVECTOR3 m_vPosition;

CCamera m_CamHelper;

unsigned int m_iSurfaceSize;

bool m_bUseDepthBuffer;

bool m_bReleased;

//...

Tabla 71 - Datos de la Clase CCubeMap

Como podemos observar en la tabla anterior, los datos relacionados con el Cube Map son en

su mayoría las seis superficies de renderizado asociadas a cada una de las caras del Cube Map.

Junto a esto también podemos comentar que internamente el Cube Map mantiene las seis

matrices de visión con proyección en cada una de las seis direcciones que puede tener un Cube

Map.

A continuación mostramos la interfaz del Cube Map para hacernos una idea de las

instrucciones que se pueden llevar a cabo con él.

Funciones del Cube Map

//...

CCubeMap ();

~CCubeMap ();

bool CreateTexture ( LPDIRECT3DDEVICE9

inDev, unsigned int inSize, D3DFORMAT inColorFormat = D3DFMT_A8R8G8B8,

D3DFORMAT inStencilFormat = D3DFMT_UNKNOWN);

void BeginScenePX ();

void EndScenePX ();

//...

void BeginSceneNZ ();

void EndSceneNZ ();

void SetCubeMapPosition (D3DXVECTOR3& inPos);

void LoadFromFile (string inPath);

void SaveCubeMapToFile (string inName);

void SavePXToFile (string inName);

//...

void SaveNZToFile (string inName);

void Release ();

D3DXMATRIX GetViewProjMatrixPX ();

//...

D3DXMATRIX GetViewProjMatrixNZ ();

LPDIRECT3DCUBETEXTURE9 GetCubeTexture () const;

};

Tabla 72 - Interfaz de la Clase CCubeMap

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 126 de 134

En la tabla anterior podemos observar como casi todas las funciones están repetidas seis

veces, haciendo referencia a las seis superficies de renderizado o a las seis matrices específicas

de cada cara.

De igual forma también podemos observar como la función de creación del Cube Map solo

acepta una medida para este Cube Map. Esto es así debido a que un Cube Map tiene forma de

cubo, y por esto no puede ser más largo de un lado que de otro, es decir, todos los lados

medirán lo mismo.

3.6 Exportador

En el apartado teórico comentábamos que el exportador es una aplicación clave para el motor

3D. El exportador toma tanta importancia debido a que sin él sería imposible generar la

geometría específica para representarla en pantalla.

Él es el encargado de generar la geometría con el formato especifico necesario que nuestro

motor 3D entiende. Para llegar a este punto, el exportador sigue una serie de procesos de

lectura, transformación y escritura de una entrada para llegar a una salida en concreto.

A continuación mostraremos los grandes bloques de los que el exportador hace uso para su

trabajo.

Inicialización de los datos

for(unsigned int m = 0; m < (iMaxMatId + 1); m++)

{

vFaces.clear();

vFacesReOrder.clear();

vNumFaces.clear();

vPositions.clear();

vNormals.clear();

vTextureCoord1.clear();

vTextureCoord2.clear();

vUsedVertex.clear();

//...

Tabla 73 - Inicialización del constructor

En la tabla anterior podemos ver como se inicializan los datos del constructor. Esto es

necesario ya que cada objeto que analice el constructor tendrá que empezar con una memoria

limpia.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 127 de 134

Recreación de los Vertex y Index Buffers

//...

if(m == (unsigned int)m_AseObject.m_vMeshObj[i].m_vMeshMaterialId[k])

{

vNumFaces.push_back(m_AseObject.m_vMeshObj[i].m_vIndexBuffer[k].GetX());

vNumFaces.push_back(m_AseObject.m_vMeshObj[i].m_vIndexBuffer[k].GetY());

vNumFaces.push_back(m_AseObject.m_vMeshObj[i].m_vIndexBuffer[k].GetZ());

vFaces.push_back(m_AseObject.m_vMeshObj[i].m_vIndexBuffer[k]);

}

//...

Tabla 74 - Vertex y Índex Buffers en el constructor

Aquí podemos observar como leyendo el fichero de entrada se generan los vectores de

vértices y los vectores de índices de la geometría que estamos tratando.

Formato ASE a formato iLengine

//...

for(unsigned int p=0;p<(unsigned int)vNumFaces.size()&& !bWorked; p++)

{

if(vFaces[l].GetZ() == vNumFaces[p])

{

iIndex = p;

if(vUsedVertex[iIndex])

{

//...

Tabla 75 - Cambio de formato de vectores

En la tabla anterior podemos observar como cambiamos el formato de cada uno de los vértices

de entrada a los vértices que nosotros necesitemos. Es necesario comentar que el formato ASE

tiene una forma peculiar de almacenar la información, y necesitamos transformar esa forma a

nuestro formato.

Rotación de las normales

//...

vSwapRotVec = m_AseObject.m_vMeshObj[i].GetRotation();

fRotAngle = m_AseObject.m_vMeshObj[i].GetRotationAng();

fRotAngle = -fRotAngle;

//...

Tabla 76 - Rotación de las normales

Por alguna razón que desconocemos, las normales del formato ASE no vienen rotadas, y para

eso es necesario rotarlas antes de operar con ellas.

Una vez tenemos todas las geometrías exportadas a nuestro formato específico, es momento

para juntar las coordenadas de textura de aquellos objetos que sean iguales.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 128 de 134

Las coordenadas de textura se juntan debido a que necesitamos más de un mapa de texturas

por objeto en pantalla. Para poder aplicar light maps y diversas técnicas más.

Escribiendo en disco

//...

oString << "\n\t\t\t\t\t\t<vertex num=\"" << k << "\">";

//Si lo queremos normal el objeto lo cargaremos mediante este codigo

oString << "\n\t\t\t\t\t\t\t<position x=\"" << fixed <<

vGroupMeshes[i][j][k].GetPosition().GetX() <<"\" y=\"" << fixed <<

vGroupMeshes[i][j][k].GetPosition().GetY() <<"\" z=\"" << fixed <<

vGroupMeshes[i][j][k].GetPosition().GetZ() <<"\" />";

oString << "\n\t\t\t\t\t\t\t<normal x=\"" << fixed <<

vGroupMeshes[i][j][k].GetNormals().GetX() <<"\" y=\"" << fixed <<

vGroupMeshes[i][j][k].GetNormals().GetY() <<"\" z=\"" << fixed <<

vGroupMeshes[i][j][k].GetNormals().GetZ() <<"\" />";

oString << "\n\t\t\t\t\t\t\t<texcoord u=\"" << fixed <<

vGroupMeshes[i][j][k].GetTexture1().GetX() <<"\" v=\"" << fixed <<

vGroupMeshes[i][j][k].GetTexture1().GetY() <<"\" />";

oString << "\n\t\t\t\t\t\t\t<texcoord2 u=\"" << fixed <<

vGroupMeshes[i][j][k].GetTexture2().GetX() <<"\" v=\"" << fixed <<

vGroupMeshes[i][j][k].GetTexture2().GetY() <<"\" />";

oString << "\n\t\t\t\t\t\t\t<tangent x=\"" << fixed <<

vGroupMeshes[i][j][k].GetTangent().GetX() <<"\" y=\"" << fixed <<

vGroupMeshes[i][j][k].GetTangent().GetY() <<"\" z=\"" << fixed <<

vGroupMeshes[i][j][k].GetTangent().GetZ() <<"\" w=\"" << fixed <<

vGroupMeshes[i][j][k].GetTangentW() <<"\" />";

oString << "\n\t\t\t\t\t\t</vertex>";

//...

Tabla 77 - Guardando la nueva geometría

En la tabla anterior podemos observar como guardamos toda la información procesada en un

fichero que será el que leeremos en un futuro como formato propio.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 129 de 134

4 Resultados

Monitores con Glow Tuberías con Specular + Normal

Cubos con diferentes materiales Multi-material en un cubo

Material complejo Materiales en movimiento

Sala de reactores Reactor

Tabla 78 - Primera tabla de resultados

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 130 de 134

Enredaderas Enredaderas + Cielo

Personaje UT complejo Personaje UT simple

Túnel + Niebla efecto calor Túnel + Niebla efecto frio

Catedral Vidrieras

Tabla 79 - Segunda tabla de resultados

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 131 de 134

5 Conclusión

Una vez finalizado el proyecto, nos sentimos orgullosos de los resultados obtenidos al igual

como haber aprendido el uso y funcionalidades de la librería gráfica usada.

Hemos realizado con éxito un sistema de iluminación dinámica por píxel que nos ofrece una

calidad visual muy elevada juntamente con la iluminación global que nos ofrece la

componente de light map en los objetos de la escena.

El efecto experimental de la interpolación de colores en el sistema de la niebla ha dado unos

muy buenos resultados. Este sistema ahora es capaz de ofrecer las ventajas de una niebla

normal y corriente al mismo tiempo que ayuda a la integración global de la escena y ayuda a

transmitir mejor una sensación en ésta.

Las sombras que hemos implementado cumplen perfectamente su función, y aunque en

motores 3D profesionales existen soluciones de mayor calidad visual, estamos conformes por

ser una primera aproximación a las soluciones de sombras.

El sistema de post procesado funciona correctamente y el efecto resultante es de mayor

complejidad que los estudiados previamente, ofreciendo una calidad cercana a muchos

motores 3D profesionales si no igual en ciertos aspectos.

Las optimizaciones de visión de geometría están a la altura de todo el proyecto, ofreciendo en

todo momento un rendimiento óptimo para la demo en tiempo real. Las oclusiones visuales de

geometría funcionan correctamente y el primer acercamiento a éstas ha sido todo un acierto.

El sistema de eventos funciona debidamente y nos proporciona una alta funcionalidad a la

hora de gestionar acciones generadas tanto por parte del usuario como por el propio entorno.

Los materiales son otro gran logro de este proyecto, pues nos han dotado de una gran sencillez

a la hora de crear objetos de una gran complejidad en escena.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 132 de 134

6 Líneas de futuro

Un proyecto tan complejo como este puede tener unas muy variadas líneas de futuro, aunque

expondremos seguidamente las más importantes.

Una de las cosas que más puede recibir cambios es la ecuación de iluminación, y así es como

debería ser. Actualmente la ecuación de iluminación acepta solo una luz dinámica, y estaría

bien que pudiese aceptar más de una.

Esta misma ecuación de iluminación juntamente con los materiales podrían ofrecer en un

futuro la capacidad mediante máscaras de juntar diferentes materiales en una misma submalla

para poder simplificar la forma de hacer terrenos u objetos que necesiten de esa característica.

Se podría añadir a la ecuación de iluminación la componente de reflexión mediante un Cube

Map diseñado a tal efecto, revisando en todo momento la carga gráfica que esto supondría

para que no perjudique a todo el sistema.

Una línea de futuro para las sombras sería el hecho de usar una textura de ruido diseñada

específicamente para poder suavizar los bordes de las sombras y así poder ofrecer mejor

calidad visual de éstas en tiempo real.

Una de las carencias que este motor 3D tiene a raíz de la complejidad del sistema de

sombreado que posee es la falta de materiales con transparencia. Esta sería una línea de

futuro interesante en la que actuar, posiblemente haciendo que las sombras pudieran ofrecer

la característica de respetar las transparencias en su proceso de creación.

Otra línea de futuro seria implementar un sistema de partículas acorde con las ecuaciones de

iluminación que posee este motor 3D. Con los sistemas de partículas se pueden hacer hoy en

día muchos efectos y ayudar a generar muchas ambientaciones tal y como lo hacen ahora

efectos ya creados, pero con menor aportación en conjunto que sin los sistemas de partículas.

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 133 de 134

7 Glosario

Siglas Descripción

XP Terminación del nombre Windows XP, referente a experiencia en inglés.

SDK Kit de desarrollo de software, las siglas provienen del inglés.

3D Referente a tercera dimensión o tres dimensiones.

CS Terminación del nombre comercial del programa Photoshop

API Application Programming Interface, Interfaz de programación de la aplicación.

VESA Video Electronics Standards Association, Asociación de estándares de video electrónico.

C++ Lenguaje de programación evolucionado de C

DLL Dynamic Link Library, Librería de links dinámicos

DXUT DirectX Utility Toolkit, Caja de utilidades de DirectX

Proyecto iL-Engine Enginyeria i Arquitectura La Salle

Página 134 de 134

8 Bibliografía

1. Microsoft Corporation. DirectX Developer Center. [En línea] 2008. [Citado el: 10 de Junio de 2008.] http://msdn.microsoft.com/en-us/directx/default.aspx. 2. —. DXUT Programming Guide. [En línea] 2008. [Citado el: 11 de Junio de 2008.] http://msdn.microsoft.com/en-us/library/bb173316(VS.85).aspx. 3. Epic Games. Unreal Technology. [En línea] 2008. [Citado el: 10 de Junio de 2008.] http://www.unrealtechnology.com/. 4. Crytek. Crytek website. [En línea] 2008. [Citado el: 10 de Junio de 2008.] http://www.crytek.com/. 5. Torus Knot Software. OGRE 3D : Open source graphics engine. [En línea] 2008. [Citado el: 10 de Junio de 2008.] http://www.ogre3d.org/. 6. Crystal Space 3D. Crystal Space website. [En línea] 2008. [Citado el: 10 de Junio de 2008.] http://www.crystalspace3d.org/main/Main_Page. 7. Wikipedia. C++. [En línea] 2008. [Citado el: 18 de Julio de 2008.] http://es.wikipedia.org/wiki/C%2B%2B. 8. Autodesk, Inc. Autodesk 3ds Max. [En línea] 2008. [Citado el: 10 de Junio de 2008.] http://www.autodesk.es/adsk/servlet/index?siteID=455755&id=10611609. 9. VESA. VESA website. [En línea] 2008. [Citado el: 10 de Junio de 2008.] http://www.vesa.org/. 10. Creative. Creative Website. [En línea] 2008. [Citado el: 10 de Junio de 2008.] http://es.europe.creative.com/. 11. Wikipedia. Phong Shading. [En línea] 2008. [Citado el: 12 de Junio de 2008.] http://en.wikipedia.org/wiki/Phong_shading. 12. —. Oren-Nayar Reflectance Model. [En línea] 2008. [Citado el: 12 de Junio de 2008.] http://en.wikipedia.org/wiki/Oren%E2%80%93Nayar_Reflectance_Model. 13. AMD - Ati. Advanced Micro Devices Inc. [En línea] [Citado el: 3 de julio de 2008.] http://ati.amd.com/developer/gdce/oat-scenepostprocessing.pdf.