inmensia |
WebGL
Juan Mellado, 17 Septiembre, 2011 - 09:52
Una versión modificada de la demo original escrita por Brandon Jones. He arreglado un pequeño error en el código original para que las texturas se vean correctamente, ya que con las versiones actuales de Chrome y Firefox apenas se distinguen. Y sobre todo he cambiado los parámetros del shader, para añadir mucho especular y que destaque el normal mapping. Todo el código es JavaScript, el modelo se lee de su formato original en MD5, y se renderiza con WebGL directamente en el navegador. El modelo 3D es propiedad de id Software.
Juan Mellado, 8 Septiembre, 2011 - 16:16
Lo bueno de intentar las cosas por uno mismo es que luego se aprecia mejor el trabajo que realizan los demás. E implementar un simple visor de modelos 3D con alguna sencilla técnica de iluminación hace desarrollar cierto sentido de mirada crítica hacia los pixels. ¡Y te permite encontrar un montón de errores! Veamos un par de ellos que he cometido. En el primer render que puse obtuve un resultado bastante coherente. Poco más o menos lo que buscaba, una imagen "bonita". Sin embargo, ampliando la imagen y parándome a pensar lo que estaba mostrándose por pantalla, observé algunas zonas un tanto extrañas. Concretamente unas partes donde la textura era completamente negra, que aunque no quedaban mal, intuitivamente me hicieron sospechar de que algo no estaba del todo bien.
Mi error fue convertir las texturas desde su formato original TGA a JPG, para poder utilizarlas con WebGL, sin tener en cuenta el canal alfa. Esas zonas negras en realidad son regiones con el canal alfa activo, y que yo estaba simplemente ignorando. La gracia del asunto es que al convertir las textura de nuevo, esta vez con el alfa correcto, el modelo dejó automáticamente de verse bien. El problema es que si dos polígonos se solapan, el que tradicionalmente se dibuja es el más cercano al observador y el otro se ignora. Y si el más cercano es transparente entonces lo que se dibuja es el fondo de la imagen, cuando en realidad debería dibujarse el polígono ignorado para producir el efecto de transparencia. La solución habitual en este caso es desactivar el "depth test" y activar el "blend", pero aún así es necesario mandar los polígonos ordenados de atrás hacia adelante para que se dibujen correctamente. Eso es bastante costoso en JavaScript, desde el punto del rendimiento, así que de momento lo he descartado, aunque he tenido un poco de suerte con la geometría del modelo y he podido generar una parte de forma correcta para poder comparar con el original. Otro error que cometí estaba esta vez en el segundo render. De nuevo una imagen que parecía correcta... pero que no lo es en absoluto. El problema está en la dirección de los vectores que se utilizan para calcular la iluminación. Al generarlos no había tenido en cuenta que la malla tiene partes simétricas, como las piernas, que utilizan la misma textura, o lo que es lo mismo, las mismas coordenadas uv sobre ella, pero con una orientación contraria. La solución más común en este caso es calcular, además de la tangente, la orientación, típicamente un valor de 1 ó -1, y utilizarla para corregir el valor de la bitangente. El problema es que implica tener que pasar un nuevo buffer de atributos de entrada al shader, lo que no me ha convencido mucho, pero me ha permitido generar un render parcial más correcto para comparar. Todos estos errores son muy básicos y existe mucha información para minimizarlos o eliminarlos completamente en función del objetivo que se pretenda conseguir. ¡Pero hasta que no lo intentas no te das cuenta!
Juan Mellado, 6 Septiembre, 2011 - 15:17
Como continuación de mi anterior post, hoy me toca poner la típica imagen del normal mapping. A la izquierda el modelo renderizado con ambiente, difusa y especular. Y a la derecha exactamente el mismo modelo, con el mismo número de polígonos, renderizado igual que el anterior, pero con el mapa de normales aplicado. ![]() Es una técnica que tiene muchos años y que utilizan prácticamente todos los juegos hoy en día, pero no por ello sigue proporcionando un resultado muy espectacular. La imagen está generada directamente en Chrome con WebGL, con tres mapas de normales. Uno para el pelo de 512x512, otro para la cabeza de 512x512 también, y uno último de 1024x1024 para el cuerpo. Hay mucha información en la red acerca de como funciona esta técnica. Resumiendo bastante, se puede simplificar diciendo que consiste en partir de dos modelos. Uno high poly y otro low poly. Sobre el high poly se calculan las normales en cada vértice, que se guardan en una textura, codificadas como componentes RGB, con la misma distribución que el mapeado uv del low poly. Y a la hora de renderizar el low poly se usan las normales de dicha textura para calcular la iluminación. La única matemática que hay que realizar es el cálculo de un sistema de coordenadas (tres vectores) que permita moverse sobre el espacio de la textura, ya que la posición de la cámara y la dirección de la luz se tienen que trasladar a ese espacio para que resulten coherentes con las normales a la hora de aplicar el modelo de iluminación.
Juan Mellado, 3 Septiembre, 2011 - 11:41
Vaya, ni un sólo post en julio y agosto. Resulta curioso, ya que es la época en que más tiempo libre tengo. En fin..., al grano. He estado trasteando un poco más con WebGL, haciendo cosas básicas, como un cargador de ficheros OBJ. Es un formato de texto plano del que siempre había leído que era fácil de implementar. Lo que no había leído es que en la práctica resulta difícil encontrar buenos modelos para probar. Y lo que es peor, ficheros OBJ bien construidos. Creo que hay por ahí un montón de plugins que no hacen muy bien su trabajo. Y un montón de gente que le da al botón de exportar y luego no se preocupa de lo que ha generado es un fichero que pueda ser de utilidad para alguien. Pero en fin, al mal tiempo buena cara, y a pensar que gracias a esto he podido aprender algunas cosas. Para la imagen de este post he utilizado este modelo low-poly de 6.227 triángulos. Después de trastear con él un rato me ha acabado extrañando un poco que tuviera tanta calidad para ser gratuito, así que me he puesto a buscar y resulta que pertenece a un juego llamado "Soul of the Ultimate Nation" (más conocido como S.U.N.), un MMORPG coreano. ![]() La imagen está generada directamente en Chrome con WebGL. El modelo de la izquierda sólo tiene aplicado las texturas de difusa. E incluso así luce bastante bien, porque las texturas son muy detalladas y, a mi juicio, están muy bien elaboradas. Tiene una textura para el pelo de 512x512, otra para la cara de 512x512 también, y una última para el cuerpo de 1024x1024. Al segundo modelo le he aplicado una iluminación muy sencilla, con una componente ambiental y una luz situada arriba a su izquierda. Mejora mucho con respecto al primero al insinuarse los volúmenes, en vez de verse tan plano, y el ojo/cerebro lo agradece. El tercer modelo tiene especular, calculado, sin texturas. Bastante exagerado quizás, pero es un efecto que siempre me ha gustado bastante. Queda muy bien sobre todo por los brillos que aparecen en el vientre y muslo derecho. Aunque otros brillos un tanto forzados, como el de brazo y pecho, quizás no deberían estar ahí. El OBJ no tiene referencias a ningún material, por lo que he tenido que inventarme los coeficientes de iluminación. Lo que si tiene son tres texturas con los mapas de normales, que a priori debería ser lo siguiente que implemente. Una pena que no tenga animaciones.
Juan Mellado, 17 Junio, 2011 - 18:58
Sigo cerrando temas. Esta vez le ha tocado el turno a la demo que tenía pendiente de hacer para demostrar el uso de js-openctm. Al final he tirado por lo más básico y he hecho un pequeño visor en WebGL para un modelo 3D con más de un millón y medio de triángulos. Demo online: La demo carga un modelo 3D en formato .ctm con 842.718 vértices y 1.685.024 triángulos, para ser exactos. El fichero pesa 6 megas, que aunque es muy poco para los estándares actuales, hace que mi hosting se resienta bastante. Al final tarda más en descargar desde la web que en descomprimir. Podría haber puesto un modelo más pequeño, pero es con un fichero de ese tamaño cuando se aprecia realmente el trabajo que realiza la librería. El modelo en su formato original ocupaba seis veces más. La malla del modelo es realmente densa. Aunque en el vídeo parezca lo contrario, el modelo no tiene ninguna textura aplicada, sólo color en los vértices. La gracia está en que es un modelo obtenido directamente de una persona de verdad mediante un escanner 3D de la empresa Cyberware. Supongo que de algún empleado "voluntario". Mi código de pruebas original que use cuando estaba desarrollando la librería creo que resultaría de poca utilidad a alguien ajeno a mí, así que he optado por adaptar uno de los tutoriales de learningwebgl.com, que es para WebGL lo que NeHe para OpenGL. Aunque me ha obligado a meter una dependencia en la demo con glmatrix, una librería JavaScript para operar con vectores y matrices. Para terminar, una cuestión técnica acerca de la implementación. En Firefox he podido (debido) utilizar un web worker para procesar el fichero en segundo plano, mientras que en Chrome no he podido porque actualmente no es capaz de traspasar Typed Arrays desde y hacía un web worker. No tiene implementada todavía la serialización de este tipo de objetos en JavaScript. He encontrado un issue al respecto, donde dice que están esperando que se defina mejor como tiene que ser el paso de este tipo de parámetros en la especificación. El inconveniente es que obliga a realizar el proceso en el hilo principal de la página, y en la mayoría de ordenadores es bastante probable que aparezca el típico mensaje avisando de que un proceso JavaScript está tardando más de la cuenta. |