Share on Google+Share on FacebookTweet about this on Twitter

Antes de comenzar, citar posiblemente a quien me inspiró a ello, y en cierto modo me ayudó a comprender algunos conceptos y me enseñó por donde buscar, por así decirlo: Bilalt, aunque desgraciadamente no conozco su nombre real y hace ya años que “desapareció”.

Voy a ir intentando completar este “reto personal”. Entre otras cosas, siempre me sentí atriado por los gráficos 3D, aunque no quiero decir con esto los juegos, sino el diseño 3D, como crear esos mundos increibles que tanto estamos acostumbrados a ver.

Por otro lado debo de decir que no me puedo consuderar ni mucho menos un gurú en la programación. Lo suficiente quizás para ser capaz de crear el programa que sea con algo de tiempo, pero dudo mucho que este pudiese estar a la altura de un verdadero programador como los que hay, auténticos genios en su campo. Pero por el lado positivo, la informática me ha enseñado que todos los campos dentro de ella de alguna u otra forma están relacionados, lo que quiere decir es que para mi es más facil que para la gran mayoría enfrentarme a un problema concreto (claro está, informáticamente hablando), sea de programación, de diseño… hasta el punto que aunque un genio de la programación sea capaz de hacer lo mismo mucho más eficientemente y más rapido que yo (sin duda alguna), yo tengo ideas o caigo en una serie de cosas que para otro normalmente serían muy complejas o siquiera las tendría en cuenta.

Todo esto me ha ayudado a la hora de poner en marcha Adrianne. A todo esto… quien es Adrianne? Esta es Adrianne:

Con cada serie de nuevas tarjetas gráficas, nVidia ordena crear una Demo especialmente diseñada para ella, intentando poner al máximo sus prestaciones. Hace unos años, con el lanzamiento de la serie 8 de nVidia, nos deleitaba con esta Demo. Se trata de Adrianne Curry si no recuerdo mal, creo que era una PlayMate que se prestó para tal exhibición.

Evidentemente no pretendo obtener un resultado similar. Mi idea es ser capaz al menos de renderizar el cuerpo entero de ella poligonalmente con un mínimo de iluminación, y como un “imposible” dejaría abierta la posibilidad de texturizar el modelo.

En realidad el reto que me propuse pasaba simplemente por ser capaz de realizar un parser para los arcihvos nmb, que son archivos propietarios de nVidia en los que se encuentra toda la geometría de los diseños 3D de estos. Es decir, en el caso de Adrianne, el archivo “geo_model.nmb”contiene toda la información sobre el modelo 3D Adrianne. Al ser un archivo propietario, a menos que llame a la puerta de nVidia y les pida por favor la estructura interna de este, el trabajo a mano a realizar es increiblemente grande. Hay que tener en cuenta que para mis ojos, tan solo es un archivo binario de 64MB, sin aparente información usable en su interior. Luego antes de ser capaz de renderizar nada, lo que tengo q hacer es ir interpretando poco a poco cada bloque hexadecimal de tan ingente archivo. Mi proyecto principal no fue con Adrianne, sino con Dawn, una Demo más antigua. Pero a fin de cuenta la estructura de ambos es muy similar, la diferencia es que ADrianne está en 64MB, y Dawn en tan solo 13MB. Es mas, tan solo la cabeza de Adrianne es más compleja que todo el modelo de Dawn.

Antes de poder hacer nada, la primera etapa es por tanto crear un pequeño Engine gráfico, la creación de un pequeño espacio tridimensional en el que poder representar puntos, polígonos… esto no es algo complicado, sobre todo con la cantidad de información que tenemos en internet. El problema claro está es comprender los ejemplos, el código, cada instrucción… y adaptarlo todo de la mejor forma a nuestro proyecto.

En mi caso he usado OpenGL como render, y GLUT para facilitarme el jugar con las coordenadas espaciales y la perspectiva.

Una vez la plantilla principal del engine está creada, el paso siguiente es verificar si dicho archivo, “geo_model.nmb” realmente contiene la información geométrica de Adrianne, y por supuesto verificar si el engine gráfico funciona. Aquí es donde tenemos que poner a funcionar el cerebro.

Lo más simple de todo, como no tenemos idea de la estructura interna del archivo de nVidia, es intentar renderizar todo el archivo como si fuese todo él un array de puntos. Pensar en un polígono. Su esencia última es el triángulo. Un triangulo se representa por 3 vértices. En cualquier archivo de geometría, lo que más debe de abundar con diferencia son vértices. Los vertices posicionarán los polígonos (triángulos) en el espacio tridimensional, los vértices de las normales serán los que indiquen el vector normal, usado para conocer la cara poligonal que recive la luz o por ejemplo también los vertices de los vectores tangenciales. Con esto quiero decir que de dicho archivo, de ser cierto, el 90% deberían de ser coordenadas espaciales.

Un vértice en un espacio de tres dimensiones se representa por 3 coordenadas x, y z. Si es un espacio bidimensional, usado por ejemplo en el mapeo de las texturas hacen falta tan solo 2 coordenadas u, v. Pero volviendo al tema principal, mi primer parser podría consistir simplemente en leer todo el archivo e interpretarlo como he dicho como un array de vértices. Un vertice normalmente se considera un numero de tipo float (numeros en coma flotante), un tipo de representación numérica, que es usada muy comúnmente cuando se necesita una resolución muy grande o muy pequeña. Un float se representa por medio de 32bits, es decir 4 bytes. Dicho esto, la primera aproximación a realizar es interpretar todo el archivo como una hilera de bloques de 32bytes, en el que se repetirá constantemente el patrón: X, Y, Z. Es decir, los primeros 4*3bytes del archivo serían el vértice 1: la componente X la componente Y y la componente Z. Los 3 floats siguientes se interpretarían como el vértice 2 con sus tres componentes… así sucesivamente.

Renderizar esto es facil, tan solo tenemos que cargar el archivo en memoria y desde nuestro engine hacer q se se representen en pantalla los vértices contenidos en nuestro buffer o la estructura creada. En mi caso he usado una estructura de este tipo:

Typedef Struct {

float x, y,z;
}vertices;

vertices *buffer;

Es decir, creo un array buffer de tipo vértices.Una vez todo está preparado, en el mismo engine tan solo tengo que llamar a este buffer y representar todos los puntos con un bucle simple for, que recorra todo el array. Previamente se ha calculado el número de vértices que tendría el archivo. Esto es facil, si he interpretado que todo el archivo en sí es un conjunto de vértitces, tan solo tengo que conocer por medio de ifstream el tamaño del archivo. Una vez conocido el tamaño del archivo en bytes, dividirlo entre 4 bytes (1 float = 4bytes) y después dividirlo por 3 (cada vértice tiene 3 componentes). El número resultante son los vértices. Por medio de new creo un array dinámicamente.

Cuando todo ello está terminado, con un for recorro todo el array, representando en cada momento el supuesto vértice:

glVertex3f (buffer.x, buffer.y, buffer.z);

Pero para poder lograr tener más información de todo ello, hago que dependiendo del puntero de nuestro buffer (la posición de este) me represente cada punto de un color u otro. Para ello divido el numero de vértices en 4, y el resultado será la umbral de cada trozo. De esta forma si el punto representado se encuentra en el primer cuarto del archivo, se representará de color rojo, si corresponde al segundo verde. Blanco si es al tercero y azul si es al cuarto. Esto es simple con una instrucción if y glColor3f.

Por último queda pensar en algo… de funciona, hemos presupuesto que el primer vértice comienza en la posición del archivo 0, y todos los consiguientes en saltos de 4 bytes. Imaginemos por un momento que tenemos realmente un archivo tan solo con vértices, pero que el primer byte del archivo es un identificador. Quiere decir que de representarlo como digo, no tendría nada!! puesto que mi matrón se extiende por todo el archivo, y jamás tendría un solo vértice representado correctamente. Pero esto mismo sucedería no solo si los vertices comenzasen en el primer byte, sino tb con el segundo y con el tercero. Las posibilidades por tanto sería un desplazamiento de 0, 1, 2 y 3 bytes. 4 no haría falta, dado que son bloques de 4 Bytes cada float, si desplazase 4 sería igual que no desplazar. Hablo de localidad dentro del mismo archivo. Con lo que si quiero representar el archivo a pelo, tengo que hacer en realidad 4 representaciones, cada una variando el desplazamiento 1 byte.

Como si de magia se tratase, con un desplazamiento de 0, y con todo lo comentado, esto es lo que obtengo:

Si no me falla la vista, eso parece ser el bañador de Adrianne? Evidentemente no quiere decir que todo lo que vemos sea parte de la geometría, ni mucho menos. Tan solo hemos pasado a nuestro engine un buffer de 64MB interpretado todo ello como vértices y obtenemos eso. Cláramente el bañador en azul, si está en azul quire decir que el bañador se encontraría en el último cuarto del archivo.

Para un desplazamiento de 1 Byte:

Vaya… en este caso hemos acertado en la cabeza de lleno, una pierna, medio cuerpo, un brazo, el ojo izquierdo, un pendiente… La cabeza en Verde nos dice que está en el segundo cuarto.

Para un desplazamiento de 2:

Otra pierna, en azul a la derecha y girado parece que podría ser el collar y un posible ojo? Tener en cuenta que las posiciones no tiene porqué corresponderse, al menos por ahora.

Y por último para un offset de 3:

Por último tenemos otro brazo, collar, cuerpo…

Creo que la mayoría de toda la geometría queda al descubierto.

Como segundo paso se podría acotar aun más la búsqueda de cada malla (parte del cuerpo) por separado, para conocer la ubicación concreta de cada una de las partes. Una vez realizado podríamos comenzar visualizando el archivo en un editor hexadecimal y ver si podemos extraer información directamente de él, teniendo en cuenta lo qye ya tenemos.

Pero ei… de momento el engine funciona, y se ve claramente un voceto muy primitivo de Adrianne diseccionada, en forma de puntos de colores.