Buenas, continúo con la saga de tutoriales para programar 3D para NDS. En esta ocasión veremos las transparencias (el poner un objeto transparente encima de otro con el fondo), la niebla (para simular sombreados) y aprenderemos a usar 3D en ambas pantallas (dije que no se podía, pero hay una forma de hacerlo mediante código)
Bueno, sin más preámbulos, empezamos!
1- Interactuando con transparencias en NDS
Bien, hasta ahora hemos estado dando casi todo lo que permite la NDS en 3D, ahora es el turno de los "efectos especiales" que admite (transparencias, sombreados y entornos), que son las cosas que vamos a dar en estos tutoriales que faltan.
Bueno, y ahora llega el turno de las transparencias, esto nos va a permitir que un objeto sea transparente, semitransparente, o que tenga el modo wireframe (solo aristas), además la NDS nos permite hacer un objeto transparente sobre otro, siempre que tengan distinta ID. Muchos conceptos sin explicar pero ahora los vamos a ver todos, primero veamos un pseudocódigo de cómo vamos a usar las transparencias en este tutorial:
int main(){ Inicia3D(); ACTIVAMOS TRANSPARENCIAS while(1){ Empezar3D(); APLICAR TRANSPARENCIA A UN OBJETO DIBUJAR OBJETO APLICAR TRANSPARENCIA A OTRO OBJETO DIBUJAR OTRO OBJETO Actualiza3D(); } }
Bien, esto de las transparencias es más fácil de lo que parece, pero con saltarnos un solo detalle el objeto no sería transparente.
Lo primero que vemos es que tenemos que decirle a OpenGL que queremos usar transparencias, como solemos usar, con la función glEnable():
code glEnable(GL_BLEND); // Activa transparencias glDisable(GL_BLEND); // Desactiva transparencias code<
También hay otro modo de hacer transparencias, es con GL_ALPHA_TEST, lo veremos después de esto...
Bien, una vez inicializado el motor de transparencias, pasamos al bucle de dibujado, una vez allí, tenemos que aplicar con glPolyFmt() los atributos de transparencias, hacemos esto:
glPolyFmt(POLY_ALPHA(alpha) | POLY_ID(id) | POLY_CULL_NONE); alpha --> El valor de transparencia del objeto/s afectado/s, va de 0 a 31 31 = objeto sólido 1 - 30 = objeto transparente 0 = Solo se ven las aristas (modo wireframe) id ---> Tenemos que darle un ID al polígono/s afectado/s, la NDS soporta 63 IDs o Slots para las transparencias, tenemos que poner un número distinto para cada glPolyFmt() para que el objeto pueda ser transparente encima de otro POLY_CULL_NONE --> Importante, no debemos activar el culling cuando hacemos transparencias, puesto que salen cosas muy raras al hacerlo, lo desactivamos con POLY_CULL_NONE
Al tener puestos los atributos de transparencia, dibujamos el objeto transparente, por medio de glBegin() o por listas compiladas. Si queremos cambiar los valores de transparencia pues solo añadimos otro glPolyFmt() y listos.
Un ejemplo de cómo usar las transparencias:
// Activa Alpha-Blending glEnable(GL_BLEND); while(1){ Empieza3D(); // Atributos del objeto 1 glPolyFmt(POLY_ALPHA(14) | POLY_ID(0) | POLY_CULL_NONE); // Es semitransparente con ID 0 glCallList(modelo1); // Dibujamos el modelo // Objeto 2 glPolyFmt(POLY_ALPHA(31) | POLY_ID(1) | POLY_CULL_NONE); // Es solido con ID 1 glCallList(modelo2); // Lo dibujamos // Objeto 3 glPolyFmt(POLY_ALPHA(0) | POLY_ID(2) | POLY_CULL_NONE); // Esta en modo wireframe (solo se ven aristas) glCallList(modelo3); // Dibujamos Actualiza3D(); }
Bien, así es como se usan las transparencias del modo más básico, pero también podemos hacer un objeto totalmente transparente (que no se vea ni se procesen sus caras), para ello usamos el GL_ALPHA_TEST:
glEnable(GL_ALPHA_TEST | GL_BLEND); // OpenGL nos pondrá el Alpha a los objetos mediante un patrón glDisable(GL_ALPHA_TEST | GL_BLEND); // El Alpha lo tendremos que poner nosotros
Una vez iniciada la transparencia total, tenemos que darle un patrón para que ponga transparentes los que su transparencia sea menor que el patrón solicitado, esto lo hacemos con una función:
glAlphaFunc(int valor); valor --> El valor minimo del alpha, si el Alpha es más bajo que 'valor' el objeto no se ve
Un ejemplo usando ALPHA_TEST:
glEnable(GL_ALPHA_TEST); // Activa el Alpha test glAlphaFunc(10); // Los objetos con Alpha 10 no se ven glPolyFmt(POLY_ALPHA(11) | POLY_ID(3) | POLY_CULL_NONE); glCallList(m1); // El objeto se ve glPolyFmt(POLY_ALPHA(9) | POLY_ID(30) | POLY_CULL_NONE); glCallList(m2); // El objeto no se ve
Bueno, ahí tenéis cómo usar las transparencias, con patrón o sin patrón, y con valores configurables con cada polígono y con 63 slots de transparencia distintos.
2- Usando la niebla, nuestro primer sistema de sombreado
Bien, este es el segundo "efecto especial" que vamos a usar, se trata de la niebla, un tipo de sombreado que oscurece o aclara una imagen en función de la cámara (posición del usuario). Esto lo vais a entender mejor con una imagen, a ver si os convencen los resultados:
Bien, si véis la imagen (hecha con NDS en 3D dual, ahora veremos cómo hacerlo), en la pantalla superior está el modelo de un desierto al cual le hemos aplicado niebla, y que, cuanto más lejos lo vemos, más atenuado está, y va atenuándose según la distancia de la cámara, es decir, lo que tengamos enfrente se ve normal, pero cuando más avanzamos más oscuro lo vemos.
Y en la imagen de la pantalla inferior vemos el mismo modelo del desierto pero sin niebla, como véis no queda bien...
Y con todo esto vamos a aprender a usar la niebla y configurarla (qué color tiene, dónde empieza, donde termina, la atenuación con la distancia etc...) todo con este pseudocódigo veremos cómo vamos a usar la niebla aquí:
void main(){ Iniciar3D(); CONFIGURAR NIEBLA while(1){ Empieza3D(); ACTIVAR NIEBLA DIBUJAR OBJETOS DESACTIVAR NIEBLA Actualiza3D(); } }
Bien, como podemos ver en el código de arriba, lo primero que hay que hacer para usar la niebla es configurarla, para ello tenemos que hacer unas cuantas cosas:
- Decir la distancia entre una atenuación y otra
- Decir el color de la niebla
- Configurar la densidad de la niebla
- Decir dónde empieza la niebla
Bien, lo primero que hay que saber es que la NDS no es automática y hay que decirle a los 32 slots de niebla su densidad, que lo haremos de menor a mayor para lograr el efecto de la imagen anterior, pero antes debemos decir la distancia entre una densidad y otra, todo con esta función:
void glFogShift(int skip); Define la distancia entre un slot y otro Cuanto mayor sea 'skip', mayor será la distancia skip --> Distancia entre un slot y otro Ejemplo: glFogShift(4); La distancia entre un slot y otro será de 4 (int)
Después de esto tenemos que decir el color de la niebla (en la imagen anterior la niebla es de color negro, pero podemos poner el que queramos), todo con esta otra función:
void glFogColor(u8 r, u8 g, u8 b, u8 a); Define el color de la niebla r --> Valor rojo de la niebla (0-31) g --> Valor verde (0-31) b --> Valor azul (0-31) a --> Valor Alpha (0-31) Ejemplo: glFogColor(31, 0, 0, 31); // La niebla es roja y opaca
Después tenemos que decirle a la NDS la densidad de cada uno de sus slots de niebla. Esto os va a sonar a rollo, porque tendremos que escribir 32 líneas para configurar la niebla, pero con un for() las cosas serán más fáciles. Para poner la densidad a un slot usamos esta función:
glFogDensity(int slot, int densidad); Define la densidad de la niebla en uno de los 32 slots disponibles slot ---> El slot a definir (0-31) densidad --> La densidad de 'slot' (0-127) Ejemplo: glFogDensity(3, 63); // Aplica al slot 3 de niebla una densidad media Ejemplo 2, método más usado para poner a los 32 slots la niebla. int slot, densidad; // Variables que guardan el slot y la densidad for(slot = 0, densidad = 0, slot < 32; slot ++){ // Empieza el bucle for densidad *= 4; // Multiplica la densidad por 4 glFogDensity(slot, densidad); // Aplica al slot la densidad calculada antes }
Ya casi lo tenemos, lo último que falta es decir dónde empieza la niebla, todo con esta función:
void glFogOffset(int offset); Define donde empieza la niebla offset --> Valor donde empieza en hexadecimal (0x0000 - 0x8000) Ejemplos: glFogOffset(0x7FFF); // La niebla empieza muy atrás glFogOffset(0x5000); // La niebla empieza más cerca
Recordad que para usar la función glFogOffset() hay que saber hexadecimal, es decir, no vale poner cualquier número, si no os pasaréis de rango y os dará error.
Terminamos de configurar la niebla con un ejemplo:
// CONFIGURACION DE LA NIEBLA // DISTANCIA ENTRE UN SLOT Y OTRO glFogShift(3); // 3 puntos entre slot // COLOR DE LA NIEBLA glFogColor(0, 0, 0, 31); // Color negro opaco // CONFIGURA LA DENSIDAD for(int slot = 0; slot < 32; slot ++){ glFogDensity(slot, slot * 4); } // DI DONDE EMPIEZA glFogOffset(0x4000); // Más o menos en la mitad de la escena
Bien, todo este código que hemos aprendido es para configurar la niebla, pero ahora llega lo más fácil, activarla y desactivarla, os dejo un par de funciones que os ayudarán en ello:
void ActivaNiebla(void){ glPolyFmt(POLY_ALPHA(31) | POLY_FOG | POLY_CULL_NONE); // POLY_FOG hace que afecte la niebla a los poligonos glEnable(GL_FOG); // Activa la niebla // Dibujar objetos con niebla ....... } void DesactivaNiebla(void){ glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE); // No ponemos POLY_FOG esta vez glDisable(GL_FOG); // Desactiva la niebla // Dibujar objetos sin niebla ...... }
Bueno, todo esto es lo que tenéis que saber para usar la niebla en la NDS, leedlo poco a poco, porque de golpe puede llegar a ser muy lioso, os recomiendo practicar con la niebla y las transparencias, jugad con los valores a ver qué pasa.
3- Usando 3D en ambas pantallas, una limitación menos
Llegamos al último apartado de este tutorial, en el que vamos a aprender a usar 3D en ambas pantallas.
Ahora que digo esto os preguntaréis: ¿Pero eso no era imposible? Bueno la NDS no puede usar 3D en ambas pantallas, pero en los ejemplos de Libnds hay un pequeño algoritmo para lograrlo, usando capturas de pantalla (capturando la escena 3D y dibujándola en la pantalla 2D). Lo que vamos a hacer es algo así:
int main(){ Iniciar3D(); Iniciar fondo inferior para capturas while(1){ Empieza3D(); Captura la pantalla y dibujala en la otra pantalla Dibujar superior Dibujar inferior Actualiza3D(); } }
Tranquilos que ahora explico todo:
El sistema que vamos a usar el mediante capturas de pantalla, cojemos la escena 3D de la pantalla inferior (que está en la superior), la capturamos y la imagen resultante la dibujamos en la otra pantalla (no estamos rompiendo las reglas, sólo capturamos la escena 3D y la dibujamos en la pantalla 2D). Y luego con una variable BOOL sabremos cuándo tenemos que dibujar en la superior y cuándo en la inferior.
Esto de usar el 3D dual tiene un inconveniente:
El juego te va a ir a 30FPS (frames por segundo) como máximo, debido a que lo máximo son 60FPS, y lo que hacemos es dibujar 3D en una pantalla en un frame y en el siguiente dibujamos la otra, por lo que como máximo te irá a esa velocidad.
Lo primero que tenemos que hacer es iniciar la pantalla 2D para hacer capturas, todo con esta función:
// Modo de video BITMAP REG_DISPCNT_SUB = MODE_5_2D; // Inicia el fondo 3 inferior (BITMAP) para la imagen bgInitSub(3, BgType_Bmp16, BgSize_B16_256x256, 0, 0); // Inicia el banco D de VRAM para guardar la captura vramSetBankD(VRAM_D_LCD); // Inicia el banco C de VRAM para mostrar la imagen vramSetBankC(VRAM_C_SUB_BG);
Bien, lo que viene en este código es para iniciar la pantalla 2D, con ello, como en 3D, ponemos un modo de vídeo para mostrar la imagen (en este caso el modo Bitmap 16bits (máxima calidad)), indicamos el fondo en la capa 3 e iniciamos los bancos C y D (C para mostrar la imagen y D para guardar la captura (Gracias a NightFox por aclarar esto)).
Bien, ahora que hemos iniciado la pantalla inferior para las capturas, tenemos que usar esta función en el bucle while(1), ésta se encargará de controlar las pantallas y de realizar las capturas, y de decirte en qué pantalla se está dibujando actualmente:
// Variable donde guardamos la pantalla principal (donde se dibuja el 3D) bool mainscreen = true; // Operaciones del modo dual 3D void ModoDual3D(void){ // Manda la captura del banco D al C (pantalla bitmap) // Para que sea más rápido hazlo por DMA, canal 3 while(dmaBusy(3)); // Espera a que esté libre DC_FlushRange((void*)0x06860000, 98304); // Reserva espacio en caché para la copia dmaCopyHalfWords(3, (void*)0x06860000, (void*)0x6200000, 98304); // Haz la copia por DMA (16 bits) DC_InvalidateRange((void*)0x06860000, 98304); // Evita que se almacenen en caché // Espera a que el registro de capturas este libre while(REG_DISPCAPCNT &DCAP_ENABLE); // Captura el frame actual en el banco D REG_DISPCAPCNT = DCAP_BANK(3)| DCAP_ENABLE | DCAP_SIZE(3); // Cambia la pantalla principal mainscreen = !mainscreen; if(mainscreen) lcdMainOnBottom(); // Pantalla inferior else lcdMainOnTop(); // Pantalla superior }
Esta función sí que hay que explicarla, primero copiamos el contenido del banco D al banco C (gráficos pantalla inferior) para mostrar la imagen. Y luego cambiamos la pantalla principal en la variable "mainscreen" . Según la pantalla que sea, configuramos la VRAM y realizamos la captura y la ponemos en la pantalla inferior que definimos antes.
Bien, este es el código completo para mostrar una pantalla 3D dual:
int main(){ Iniciar3D(); // Inicia la pantalla inferior para dibujar la captura IniciarCapturas(); while(1){ Empieza3D(); // Realiza las operaciones del modo 3D dual ModoDual3D(); // Dibuja la escena dual if(mainscreen){ // Dibujamos en la pantalla inferior }else{ // Dibujamos en la pantalla superior } Actualiza3D(); } }
Muy bien, con estas 2 funciones que he dado y con el pseudocódigo de arriba seréis capaces de dibujar en ambas pantallas. Ahora pondré un par de ejercicios para asimilar todo lo aprendido, y recomiendo releer este tutorial , ya que es largo y con muchas cosas, si tenéis alguna duda ponedla en los comentarios.
Ejercicios
Ejercicio 8
En este vamos a usar las transparencias que admite la NDS (sin usar el Alpha_Test), y jugaremos con el Alpha de los 3 modelos que tenemos.
Arriba-Abajo --> Transparencia del objeto seleccionado
Derecha-Izquierda --> Seleccionar objeto
LR --> Zoom
http://www.mediafire.com/?9cq4b9nqnmbmmbh
Ejercicio 9
En este vamos a usar la niebla y el 3D en ambas pantallas, mostraremos el modelo de un desierto en la pantalla superior con niebla y en la inferior sin niebla, para poder ver la enorme diferencia entre ambos.
Más adelante veremos el "toon shading", otro modo de sombreado que es bueno dárselos a los personajes, pero
eso lo veremos más adelante...
Pad direccional --> Mover la cámara en ambas pantallas
LR --> Girar el ojo X de la cámara
http://www.mediafire.com/?weqpgffd5kc686d
Bueno, este es el fin de este tutorial, en el siguiente veremos los entornos (cube maps) y el toon shading.
Hasta entonces, saludos!
~Actualmente estudiando Ingeniería de las Tecnologías de la Telecomunicación en la Escuela de Ingenieros~
Otra vez, felicitaciones, que
Otra vez, felicitaciones, que tal trabajote que te esta dando.
^^
Pues todavía quedan 2 más xD
Otra opcion mas "rapida" para
Otra opcion mas "rapida" para dual 3d seria esta, usando un banco en modo LCD para guardar la captura, asi te ahorras procesar toda la malla de sprites:
http://pastebin.com/FGjZZN4B
El codigo es el que use en los creditos de mind maze y ya sabes lo bien que quedo ^^
Nuestra web oficial:
http://www.nightfoxandco.com/
Siguenos en facebook:
http://www.facebook.com/pages/NightFox-Co/284338634917917
Por favor, no useis los MP para preguntas, usar el FORO:
http://www.nightfoxandco.com/forum/
Asi nos ahorramos de contestar lo mismo 20 veces.
Muy bueno ^^
Muy bueno, encima le puedes añadir el VBlank, no como el mío sacado de los ejemplos de Libnds xD
Cuando tenga un momento adapto el código al ejemplo y lo publico en el tutorial.
Gracias ;)
EDIT: Ya lo he adaptado y va de lujo, solo que la imagen va algo lenta, pero creo que será porque estoy copiando el VRAM_D al VRAM_C con el memcpy(), voy a probar con DMA a ver si sale más rápida.
EDIT2: He probado con DMA y ya va bien, incluso mejor que antes ^^
~Actualmente estudiando Ingeniería de las Tecnologías de la Telecomunicación en la Escuela de Ingenieros~