PDA

Ver la versión completa : Programando un engine 3D



Segata Sanshiro
09/09/2005, 23:15
Siento si alguien se había ilusionado pero esto no es más que unas dudillas que quiero preguntar.

Hace tiempo se me metió en la cabeza el escribir un programa capaz de dibujar puntos en la pantalla aplicando perspectiva, es decir, un rudimentario "motor 3D". Pero todo esto lo quería hacer sin haber oído una put@ palabra sobre cómo hacer un programa de este tipo.

Finalmente me puse y en una horilla lo hice. No es que dé una perspectiva muy real ya que los objetos parecen alargarse cuando se alejan, pero bueno, el asunto era programar una transformación de persectiva sin haber recibido nunca pistas sobre cómo hacerlo.

Funciona así:

-Se leen las coordenadas del punto a dibujar
-Se elabora un coeficiente: coef = 1 - z / focus
-Y se transforman las coordenadas:

x_en_pantalla = x_origen + x * coef
y_en_pantalla = y_origen + y * coef

"Focus" determina la coordenada Z máxima en la que los puntos se concentran en el medio del horizonte.

Y ahora es cuándo quiero saber cómo funciona un engine 3D de verdad xD He empezado a mirar cosillas y por lo visto se usan una serie de matrices y razones trigonométricas. ¿Qué tipo de conocimientos matemáticos se necesitan para dibujar puntos con una perspectiva realista?

Por último, refiriéndome al desarrollo del aspecto gráfico de un juego, ¿dónde acaba el papel del programador y empieza el de las librerías 3D usadas?

Espero que más o menos se me entienda y a ver si alguien me puede responder xD
Un saludo. Adjunto el código del engine (en Fenix XD).

wborland_es
09/09/2005, 23:46
Queremos un fxe con una demo tecnica YA ;) .

Zheo
10/09/2005, 00:23
Te aconsejo que busques información sobre gráficos por computador, así como por transformaciones, perspectivas, ejes de coordenadas y en general todo tipo de matemática analítica y geometría :P

Básicamente estos son los pasos para crear una imagen 3D (resumido) Supongo que tenemos ya los vértices posicionados e iluminados :)

Los objetos 3D (modelos) se crean con su propio sistema de coordenadas (coordenadas locales o de objeto).
Los objetos se colocan todos juntos y su sistema de coordenadas se traduce a lo que se conoce como coordenadas de mundo, que no es más que un sistema de coordenadas de referencia común a todos los objetos que quieres dibujar. Esta "traducción" se conoce como transformación de mundo.

En el mundo se coloca, además de los objetos la cámara que no creo que tenga que explicar para qué sirve. La cámara no es un objeto (un modelo 3D) es simplemente una posición y orientación en el espacio, con algunas particularidades que no explicaré ahora-

Hay que definir un volumen de visión para la cámara que será lo que más adelante definirá lo que se mostrará o no por pantalla. El volumen está delimitado por planos normalmente 6.
Lo que tu llamas perspectiva depende de dicho volumen, por ejemplo si el volumen de visión es un paralelepipedo, no tendrás perspectiva, es lo que se llama proyección ortogonal.
Sin embargo si quieres hacer perspectiva, el volumen que buscas es el de una pirámide, con el vértice tocando la cámara. Si lo visualizas, verás que cuanto más te alejas de la cámara más espacio puedes "ver" (es decir, más espacio abarca el volumen de visión) eso es lo que creará la perspectiva ;)

El caso es que por razones de eficiencia, conviene que el punto donde se encuentra la cámara sea el origen de coordenadas (0,0,0), con lo cual lo que se hace es cambiar el origen de coordenadas del mundo para que lo sea la cámara. Es lo que se conoce como transformación de vista.

Por último y también por razones de eficiencia, conviene que el volumen de visión sea un "cuadrilátero", así que volvemos a aplicar una transformación para convertir el volumen de visión en un cuadrilátero con unas medidas concretas. Esta es la transformación de perspectiva.
La razón es la siguiente: el volumen de visualización define lo que vas a "ver" en pantalla Todo aquello que esté fuera del volumen se descarta. El problema es cuando algo está parte dentro del volumen, parte fuera, que hay que recortar (clipping) y es mucho más fácil recortar contra planos paralelos (y con las medidas concretas) que contra cualquier otra cosa.

Por último se proyecta y transforma el cuadrilátero para que encaje en la pantalla: transformación de pantalla (viewport en opengl, que salió hace poco en un hilo sobra la PSP :arriba: )

A partir de ahí vendría dibujar líneas, proceso que se conoce como rasterización, sombreado (bien sea de color, de textura o ambas) y demás.

Bueno, lo que te hacen las APIs gráficas, tanto OGL como DX, es darte las herramientas para que puedas aplicar esas transformaciones. En concreto te dan matrices y funciones para aplicarlas, pero eres tú el que tiene que decidir cómo hacerlo. El proceso de rasterización y sombreado es más "automático" por así decirlo, sin embargo tú como desarrollador puedes jugar mucho con él, por ejemplo aplicando multitexturas, y ya no te digo nada si tienes pixel shaders. :P

Por cierto, cuando digo transformación es básicamente coger cada vértice 3D y multiplicarlo por una matriz.


Piro a jugar al ET, si quereis lo amplio algo más (tampoco se mucho, pero bueno) pero ahora tengo mono xDDDDD

WinterN
10/09/2005, 03:32
Hola segata,

la verdad es no soy un experto en el tema, pero una vez me propuse hacer un engine 3d en J2ME que tirase de coma fija y con mucho esfuerzo y tirando de tutoriales conseguí algo curioso: mover objetos de unos 80 polígonos con flat shading y sin texturas a unos 8 fps. Ya sé que no es gran cosa, pero era más de lo que esperaba conseguir xD

Bueno, dejandonos de rollos... lo que suele ocurrir en internet es que hay tutoriales muy buenos de como hacer tal o cual cosa, pero ninguno que te dé una idea global de hacer un motor 3d completo desde cero.

Así que aqui te dejo una guía superrápida de las lineas a seguir. Te aconsejo que intentes modular tu motor de forma que sean lo más independientes unas de otras, de forma que puedas trabajar, y mejorar cada una por separado:

1.- El primer paso es pintar puntos en perspectiva. Así que ya sabes que, aún sin ayuda, has empezado con buen pie. La forma más facil de plasmar un punto es suponer la cámara en algún punto del eje Z (positivo o negativo) y mirando al origen de coordenadas (0,0,0). De esta forma la forma de pintar un punto 3D en la pantalla 2D se reduce a

x2d = x / z*factor
y2d = y / z*factor

cuanto mayor sea Z, más cerca estará el punto del origen de coordenadas (centro de la pantalla). Esto es muy útil por ejemplo para simular el efecto de estrellas que habrás visto en muchas demos o en algún protector de pantalla.

Sin embargo es poco práctico limitarse a mover la cámara a lo largo del eje Z y mirar siempre al origen de coordenadas. Para ello se aplican las transformaciones.

2.-Las transformaciones. Aquí es donde entra la parte de algebra lineal pura y dura. Normalmente se aplican 2 transformaciones: una primera que situa el objeto "en el mundo", consitente en traslaciones y rotaciones. Cuando tu crees un cubo, lo definirás seguramente haciendo que el centro coincida con el centro de coordenadas, pero a la hora de situarlo en tu mundo, no querrás que esté ahí, sino en otras coordenadas y con una rotación y/o escalado.

La segunda transformación es la que proyecta ese objeto ya en el mundo, sobre tu pantalla 2d. Se trata de un proceso similar al de las formulas anteriores, pero un poco más complejo ya que ahora podemos tener la camara en cualquier sitio mirando a cualquier otro. Al final se resume en mása algebra con matrices... si buscar info por ahí verás como contruirlas

3.-Poligonos. Ahora ya tenemos puntos que giran alrededor de los ejes y tal... pero queremos ir un poco más allá, queremos pintar polígonos.

Pintar un polígono consiste básicamente en unir con lineas los puntos que habíamos creado antes. Necesitarás una estructura de datos que lo represente (normalmente un array de puntos en orden). Es importate destacar que hay dos formas de tratar los polígonos en los motores gráficos. Los motores modernos solo trabajan con triángulos (o eso creo); pero tambien es factible trabajar con poligonos de cualquier número de lados.

Cada uno tiene sus pros y sus contras. Por ejemplo, trabajar con triángulos hace que sea más facil "rellenar" los poligonos de una textura o color, pero al ser más polígonos se requieren más calculos y vectores más grandes. Tambien hay que destacar que cualquier polígono es "triangularizable" (dividir en triangulos) algorítmicamente.

4.- Ocultar caras no visibles. Los polígonos en un mundo 3D tienen dos caras. Como normalmente vamos a trabajar con solidos, nos interesa saber que cara queda hacia dentro del sólido y cual hacia fuera. Para saber esto hay un truco que es al crear el polígono, ordenar siempre los puntos en sentido horario (o antihorario), de forma que podamos distinguir si la cara visible es la que vemos o por otra parte es la cara invisible. Hay una sencilla operación para cálcular la normal del polígono (vector perpendicular al plano que contiene el polígono y que apunta de la cara visible hacia el infinito). Si la componente Z de la normal (previas transformaciones) es positiva, entonces vemos la cara visible, o si es negativa la cara invisible, y por tanto no es necesario pintar el polígono (lo que nos ahorrará un CPU importante).

5.- Rellenado de de polígonos. Se trata simplemente de rellenarlos de algún color para darles un aspecto más solido. Existen varios algoritmos para rellenar polígonos (los de los triángulos son algo más sencillos).

6.- Recorte de polígonos. Otra forma interesante para ahora CPU, es saber que polígonos van a aparecer en la pantalla y cuales van a quedar fuera de ella. De forma que aquellos que no van a salir en la pantalla ni nos molestamos en pintar. Es importante aplicar esta operación antes que otras como la ordenación, el sombreado o la texturización.

Tambiénes interesante "recortar" aquellos polígonos que salen parcialmente en pantalla, de forma que los convertimos en otros polígonos formados solamente por la parte que aparece en la pantalla. Esto no ahorrará operaciones de rellenado complejo sobre zonas del poligonos que no va a aparecer en la pantalla. Aunque parezca una operación fácil, la verdad es que tiene cierta complejidad pero, por supuesto, ya hay algoritmos diseñados y conocidos para dicho fin.


7.- Ordenación de polígonos. Es interesante saber que polígonos quedan más cerca de la camara y cuales más lejanos, para que a la hora de pintarlos no cometamos el error de pintar un polígono lejano sobre uno cercano. Los algoritmos más conocidos son estos dos:

Algoritmo del pintor - para cada polígono se calcula el punto medio y se ordenan según ese punto medio, para despues pintarlos desde el más lejano al más cercano. Es un algoritmo que da buen rendimiento cuando el número de poligonos no es muy algo, aunque pueden aparecer imperfecciones cuando 2 polígonos se solapan.

Z-Buffer - Es el algortimo óptimo. Algo complejo para explicarlo aqui, aunque no tanto como para no entenderlo a la primera. Elimina los fallos que pueden aparecer con el algoritmo del pintor y el rendimiento es tan bueno o mejor cuando el número de poligonos es muy elevado. Requiere bastante más memoria y tiene el problema de la distancia máxima.

8.- Iluminación. Parece mentira que ya hayamos llegado a la iluminación. Se podría tratar antes el tema de la texturización, pero personalmete cosidero más completo un motor gráfico con iluminación y sin texturas, que con texturas y sin iluminación alguna.

Hay muchos modelos de iluminación. Algunos tan complejos y realistas como la radiosidad o el raytracing que en un ordenador potente pueden tardar varios minutos o incluso horas en generar una imagen o fotograma.

Los más básicos son el flat shading, y el gouraud. El primero consiste en calcular la normal del polígono y ver el ángulo que forma con la linea que forma el centro del polígono con la fuente de luz. Cuanto más perpendicures sean, menor luz llegará al polígono y más oscuro se pintará. El gouraud es una técnica similar, solo que en lugar de aplicarse sobre el centro del polígono, se aplica sobre cada vértice por separado, de forma que se define una intensidad de color para cada vértice. A la hora de rellenar del polígono, en vez de rellenarlo de un único color, se hará de forma degradada según el color de cada vértice (hay algoritmos para esto).

9.- Texturización - Aqui de momento no te puedo ayudar ya que no he trabajado aún con ella, pero piensa que sólo se trata de una técnica de rellenado de polígonos bastante más compleja.

Luego ya están las técnicas más avanzadas como las sombras dinámicas, el suavizado de texturas, la corrección de prespectiva, los modelos de partículas...

Seguramente me haya equivocado en más de una cosa, ya que soy bastante novel. Aceptaré gustosamente cualquier corrección. Me hubiera gusta ponerlo de una forma más gráfica, pero tampoco ando muy sobrado de tiempo

Un saludo.

A.r.R.c.H.E.r
10/09/2005, 03:39
Muy buenas segata!

yo la rutina que utilizo para la perspectiva es esta:

/** FUNCION PARA LA PERSPECTIVA SLL**/
void PerspectivaSLL(sll x, sll y, sll z, sll *xout, sll *yout)
{
sll z1 = slldiv(SLL_CONST_1, z);
*xout = slladd( sllmul( sllmul(x, z1), DistanciaSLL), CentroXSLL );
*yout = slladd( sllmul( sllmul(y, z1), DistanciaSLL), CentroYSLL );
}


Modificando DistanciaSLL puedes jugar con el tema que comentas de ver las cosas estiradas cuando se aleja de la camara.


Te adjunto un pequeña demo de mi engine para que veas los resultados.

PD-> Se me olvidaba decirte que esa funcion de perspectiva utiliza punto fijo.

dagus
10/09/2005, 03:41
joer muchachos, da gusto escucharos! :D

A.r.R.c.H.E.r
10/09/2005, 03:54
Se me olvidaba comentar una cosilla, en la demo de mi engine3D que he puesto un poco mas arriba se puede cambiar los Mhz pulsando el gatillo derecho, cambiara entre 66, 133 y 166Mhz

Segata Sanshiro
10/09/2005, 10:41
J0der y tanto que da gusto escucharos!

Gracias a A.r.R.c.H.E.r, WinterN y Zheo, habéis sido muy claros con las explicaciones y me han entrado ganas de seguir con el tema este o_o

Voy a releer vuestros posts detenidamente y a ir añadiendo modificaciones al engine. De momento será fácil dibujar wireframes.

A.r.R.c.H.E.r
10/09/2005, 11:34
J0der y tanto que da gusto escucharos!

Gracias a A.r.R.c.H.E.r, WinterN y Zheo, habéis sido muy claros con las explicaciones y me han entrado ganas de seguir con el tema este o_o

Voy a releer vuestros posts detenidamente y a ir añadiendo modificaciones al engine. De momento será fácil dibujar wireframes.


Segata echale un ojo al engine que te enviado.

Segata Sanshiro
10/09/2005, 11:43
Tengo que preparar una smc virtual, que me quedé sin gp32. Funcionará en el geepee, no?

A.r.R.c.H.E.r
10/09/2005, 12:02
Si si que funciona :) aunque con muchos menos frames por segundo jejej pero bueno para que te hagas una idea ya va bien :D

Zheo
10/09/2005, 13:21
Sólo una cosa, los pasos 4 y 6 van antes del 3, y el paso 7 es de los últimos ,depués de la texturización.

WinterN
10/09/2005, 13:50
Sólo una cosa, los pasos 4 y 6 van antes del 3, y el paso 7 es de los últimos ,depués de la texturización.

Tienes toda la razón, aunque yo en un principio lo ordenaba por orden de aprendizaje, más que de aplicación. Es decir, considero más interesante aprender a rellenar los polígonos, antes que recortarlos... pero se puede ver tambien de la otra forma ;)

El paso 7, con el algortimo del pintor se podría hacer antes o después, pero el z-buffer, como bien dices, debería ser de los últimos en aplicarse (cuando ya se sabe el color de cada pixel del polígono).

una-i
10/09/2005, 16:07
El paso 7, con el algortimo del pintor se podría hacer antes o después, pero el z-buffer, como bien dices, debería ser de los últimos en aplicarse (cuando ya se sabe el color de cada pixel del polígono).

Yo haria el z buffer y despues si el pixel hay que pintarlo le calcularia el color, la luz(si es por pixel) etc...

Yo respecto a que hacer antes direa no hacer nada :P es decir comobin dijo alguien na vez el poligono más rapido es el poligono que no se pinta.... lo mismo con los pixeles...

Zheo
10/09/2005, 19:44
Yo haria el z buffer y despues si el pixel hay que pintarlo le calcularia el color, la luz(si es por pixel) etc...

Yo respecto a que hacer antes direa no hacer nada :P es decir comobin dijo alguien na vez el poligono más rapido es el poligono que no se pinta.... lo mismo con los pixeles...
Bingo, no es necesario texturizar píxeles que luego no aparecerán :)

Segata Sanshiro
10/09/2005, 20:28
ArRcHEr, cuáles son los valores adecuados para DistanciaSLL y SLL_CONST_1?

Es que me salen cosas muy raras xD