Tiny3D es quizá la realización de un viejo sueño mío en PS3, allá por el 2007 cuando teníamos el exploit de aceleración gráfica activo (que murió bruscamente antes de poder nacer, al actualizar yo la consola sin conocer el hecho de que se capaba dicho exploit ), pero sobre todo es una forma de aglutinar los pequeños descubrimientos de todos (lo hecho por IronPeter en su momento, lo hecho por los que colaboramos en el proyecto PSL1GHT de forma activa) de forma que podamos tener gráficos 2D/3D en nuestras PS3 con relativa sencillez, pero sin descartar poder sacar provecho de la potencia de la máquina.
¿Por qué Tiny3D y no OpenGL?
Es una buen pregunta, pero si lo miramos de forma idealista, tal vez el hecho de poder crear uno su propio estándar adaptado a las características de la máquina, resulta llamativo (un scener que no busca nuevas vías de hacer las cosas, lo mejor que puede hacer, es no salir del PC que es lo mas estándar posible: Precisamente, lo que a mi me llama la atención de las consolas es que se programan de forma diferente y siempre hay un montón de cosas nuevas por descubrir/crear). Aparte de que OpenGL no tiene por qué ser el camino ideal de hacer las cosas, por muy estándar que sea y sería una estupidez frenar otras vías alternativas que pueden ser igualmente válidas para presentar gráficos.
En la práctica resulta que no se puede empezar a correr antes de andar: el propio desarrollo de Tiny3D me ha lleva a descubrir nuevas posibilidades que otros pueden aprovechar si quieren implementar ese OpenGL. Tiny3D no está completo, pero cada vez permite hacer mas cosas y es mas potente, sin que eso redunde en contra de que si no quieres complicarte la vida, sigas teniendo la misma facilidad de trabajar que al principio y con unas pocas funciones, puedes trabajar en 2D y mover tus sprites en pantalla (si es lo que quieres), solo que poco a poco, se van añadiendo nuevas características, como es lógico.
Si, si, todo eso está muy bien, pero ¿que es lo que se puede hacer usando Tiny3D?
- Se puede trabajar en contexto 2D o 3D
- Se pueden dibujar toda clase de polígonos soportados en PS3, desde puntos hasta figuras de mas de cuatro lados
- Se puede trabajar con luces (se soportan hasta 4 luces posicionales), materiales y texturas (se soporta la aplicación de hasta dos texturas)
- Se puede renderizar gráficos en una textura y utilizarla para envolver un objeto.
- Se pueden utilizar listas de materiales, polígonos, etc, para crear y animar objetos 3D
- Los shaders se cambian de forma automática según la lista de vértices especificada, aunque tambien tenemos alguna función para especificar nuestras preferencias.
- Recientemente hay soporte para las transformación de color YUV a RGB mediante 4 pixels shaders dedicados al efecto. Por lo que la librería resulta de utilidad para implementar Reproductores de vídeo Multimedia
- Tienes soporte para utilizar fuentes de letras fijas o capturadas de una fuente .ttf (mas rápido que dibujar fuentes .ttf directamente en una superficie, lo cual es posible tambin)
- Puedes modificar el código fuente de la librería para adaptar tus propias soluciones particulares y en general, está abierta a cualquier mejora propuesta.
Y en concreto AQUI es donde mantengo yo las librerías que voy actualizando de vez en cuando.
Mi recomendación es que bajéis de github las actualizaciones de Tiny3D pulsando el boton de "Descarga" que aparece en github al menos y que luego compiléis todo como un ejemplo más de PSL1GHT
¿Cual es la finalidad del hilo?
En un primer momento, nace para dar a conocer y documentar la librería en español y de forma didáctica, para que cualquiera de vosotros sepa entender y utilizar la librería desde mi perspectiva.
Evidentemente, aquí se puede tratar también cualquier aspecto relacionado con Tiny3D y el desarrollo bajo PSL1GHT en general. Y ahora, comencemos:
Comenzando por el Principio
En el principio de la creación, Dios... ejem,
Lo primero que tenemos que centrarnos es en la pantalla y en como se presentan los gráficos en ella.
Bajo Tiny3D podemos trabajar de dos formas diferentes, que pueden incluso combinarse (como podéis ver en alguno de los ejemplos)
Al estar hablando de una librería en 3D, tenéis que tener presente el Buffer Z. Dicho buffer se utiliza para ordenar los píxeles según la cercanía al observador (un píxel por cierto, es un punto de la pantalla). En concreto y en este caso, hace que cuando tratas de escribir un píxel en la misma posición en pantalla (X, Y) este no se dibuje si hay "escrito" otro mas cercano.
Así pues, incluso en el contexto 2D, esa Z es significativa y de hecho, siempre ha sido interesante contar con un modo de ordenación en 2D y en el pasado se utilizaba un sistema de capas para que el fondo (capa mas lejana), no sobreescribiera a los personajes e incluso que estos, no sobre escribieran a objetos que pudieran estar mas cercanos.
El Buffer Z por tanto, afecta al píxel y no al sprite, fondo, etc, que se usaban en el pasado, pero:
¿En que formato se almacena esa Z?
En el caso de PS3 se soportan buffers Z de 16 y 32 bits, que se almacenan internamente como números flotantes.
En el caso de Tiny3D, por defecto se usa Buffer Z de 32 bits, que es la mas precisa (y la que mas espacio consume 1920 x 1088 x 4 = casi 8MB de VRAM). Se puede iniciar con Buffer Z 16 bits, pero es tontería si no lo requieres por temas de velocidad o espacio.
Sea como sea, yo utilizo, prudentemente Z =0.0f para justo en frente de la pantalla y Z = 65535.0f para la Z mas lejana (obviamente, al ser en punto flotante, se admiten divisiones menores de una unidad )
Si, si, pero no me has contado nada sobre X e Y ¿que sistema de coordenadas utilizan?
Pues depende de si estás en 2D o en 3D, como es obvio y también de tu forma de proceder.
En primer lugar, la PS3 puede trabajar en resoluciones 480 i/P (720x480), 576 i/P (720x576), 720P (1280 x 720) y 1080i/p (1920 x1080).
Esto ya de por si tiene un problema: si yo hago un juego o algo, quiero que se vea mas o menos igual en todas las PS3 y en las diferentes configuraciones de vídeo: obviamente en una resolución mayor, cabe esperar mas detalle, pero el aspecto general debería ser el mismo sea la resolución que sea.
A eso se añade un problema mayor y es que la superficie de pantalla, visualmente, se sale de la pantalla.
Eso quiere decir que si pinto un punto en la esquina superior izquierda, no se verá
Así que tenemos dos problemas: unificar el sistema de coordenadas para todas las resoluciones y además, corregir la pantalla de forma que si sitúo un punto en la esquina, sea visible.
De esa tarea se ocupa Tiny3D:
En 2D, se ha adoptado un sistema de coordenadas ficticio de 848.0 x 512.0 para trabajar, ya que lo considero mas amigable que trabajar con 0.0 a 1.0 y es un compromiso razonable para las resoluciones 480P/576P (aquí corremos el riesgo de perder parte del detalle de la resolución ficticia) y no se pierde nada a resoluciones mayores por que trabajando con números flotantes, tenemos correspondencia entre un subpixel ficticio y un pixel real en pantalla. Eso se hace ajustando internamente el Viewport, siendo la esquina izquierda-superior la coordenada 0.0, 0.0 y la derecha-inferior 847.0, 511.0
En 3D, el Viewport se ajusta para trabajar en el rango -1.0 -> 0.0 -> 1.0 como es habitual en los entornos 3D.
En este caso, 0.0, 0.0 es el centro de la pantalla, la izquierda está hacia -1.0 , la derecha hacia 1.0, arriba hacia 1.0 y abajo hacia -1.0.
El modo 3D requiere el uso de una matriz llamada "de proyección" que hace que los objetos se agrandan, según se acercan y se empequeñecen al alejarse. Vamos que la matriz de proyección controla como se presenta el objeto en nuestras pantallas.
También, tanto en 2D, como en 3D, se puede especificar una matriz llamada modelo/vista (Modelview en pitinglish) que se aplica a cada objeto particularmente para modificar su posición, rotarlo, etc. Se llama de esa forma, porque normalmente, se "mezclan" los cambios que realizamos al objeto con otra matriz que establece la posición del la cámara. Al ser la matriz de proyección de una naturaleza distinta (provoca una "deformidad" del objeto que no interesa al trabajar con normales), no se mezcla con las otras dos y es el shader quien se ocupa de ello (nótese que en 2D no se usa la matriz de proyección)
¿Que es eso de una matriz? ¿Y que cojones es un "shader" de esos?
Una matriz, por definirlo de forma simple, es una lista de números que almacenan los cambios que queramos aplicar al objeto. Tiny3D usa matrices de 4x4 que al ser combinadas con una multiplicación de matrices, puede ser utilizada por el shader para aplicar UNA SOLA (ModelView) al vértice con todos los cambios.
Lo usual es que una matriz se aplique a un buen número de vértices, por lo que al combinar las matrices ahorramos cálculos .
Por ejemplo, para ilustrar las propiedades de éstas matrices: imaginad que creo una matriz que desplaza un objeto 80 unidades hacia la derecha y otra que gira 90 grados en el eje Y en sentido horario. Depende del orden como las multiplique, conseguiré que primero el objeto se desplace 80 unidades a la derecha y luego haga el giro (con lo cual el objeto girará sobre el centro con un radio de 80 unidades para colocarse justo en frente nuestra) o que haga el giro primero y luego se desplace 80 unidades a la derecha (con lo cual el objeto girará sobre si mismo 90 grados y luego se desplazará 80 unidades). Es decir, no solo almacena que hacer, si no en que orden hacerlo ¿no es maravilloso?
Bien y ahora toca explicar que es un shader : un shader es un programa que corre en nuestra gráfica y le dice como debe de comportarse. El RSX cuenta con dos tipos de shaders: Vertex Shader y Pixel Shader.
Vertex Shader permite trabajar con los atributos de vértice que pasamos mediante las funciones correspondientes en Tiny3D y luego le pasa el resultado al Pixel Shader que como su nombre indica, hace el trabajo a nivel pixel.
Por ejemplo, la aplicación de matrices de Proyección y de Modelo/Vista, es llevada a cabo en el Vertex Shader. Pongamos que está procesando un triángulo: eso equivaldría a decir que el Vertex Shader coge los atributos de los tres vértices, los procesa y le pasa el resultado al Pixel Shader.
El Pixel Shader se encargaría entonces de colorear cada pixel del triángulo mediante unas operaciones que nosotros programamos que pueden incluir obtener el texel de las texturas (texel equivale a pixel, pero para texturas) y que pueden ser tan complejas como iluminar el objeto con precisión, aplicar varias texturas o tan simples como una simple instrucción que le dice "copia el color obtenido del vértice".
Evidentemente, estoy simplificando lo mas posible y tu no debes de preocuparte por los Shaders en general, pues Tiny3D ya se encarga de su gestión (tiene una bonita colección de ellos ). Pero he pensado que no viene mal un poco de conocimiento general del tema: de hecho, todo esta sección va a ir de esa guisa: tratar de que comprendáis como trabaja Tiny3D en particular pero también todo ese mundo 3D que algunos tenéis vetado en parte, por que no tenéis asimilado el concepto de varias cosas.
Colores de guerra
Hemos hablado de posicionar objetos en pantalla, pero no hemos profundizado en lo que es un Pixel ni de que se compone.
Un pixel o punto en pantalla, se compone de una serie de atributos de color, que es de lo que nos interesa hablar ahora.
En pantalla tenemos A8R8G8B8, es decir 8 bits por componente, siendo el primero Alpha, luego Rojo, Verde, y Azul.
Aquí tengo que hacer un paréntesis para comentar que la PS3 que el tema de color en PS3, es un pequeño lío, pues el procesador (PPU) trabaja en big endian, pero el RSX usa datos en little endian.
Eso hace que por ejemplo, si quiero especificar Alpha y Verde al máximo con 32 bits, en un caso sería 0xff00ff00 y por ejemplo, aplicando atributo de color a un vértice 0x00ff00ff. Vamos, que uno es ARGB y el otro RGBA y hay mas combinaciones, claro.
A estas alturas, creo que muchos comprendéis como formar colores mediante RGB: el negro sería 0,0,0 , si mezclamos Rojo y Verde, obtenemos Amarillo = 0xff, 0xff, 0, etc.
También tenéis que tener en cuenta que se puede especificar color mediante números flotantes en algunas funciones. En este caso, 0.0 = 0 y 1.0 equivale a 0xff con respecto a 8 bits por componente, que es algo que habréis trabajado más.
La parte mas complicada es entender Alpha. Alpha puede tratarse de varias maneras, como por ejemplo, fijar una referencia de color para dibujar o no dibujar. Lo que quiero dejar claro, es que es un valor que no tiene por que tener siempre el mismo uso y depende del uso que queramos darle y la función que activemos.
En mis ejemplos de Tiny3D yo activo la función AlphaTest y AlphaBlending. La primera comprueba el valor de Alpha y en función a ello, permite que un color pueda ser o no dibujado en pantalla. Es decir, tal y como yo lo implemento, si paso un Alpha < 0x10 no se dibujará el Pixel con todo lo que eso implica (no se actualizará el buffer Z, por ejemplo). Eso se debe a la función AlphaTest.
Sin embargo, si Alpha es >= 0x10, la función Alpha Blending provoca una mezcla con el color anteriormente pintado en esa posición, de forma que el pixel se puede mostrar translucido o semi transparente, si tiene una valor intermedio y solo sólido con un Alpha = 0xff.
Igualmente pasa cuando se aplican texturas: una textura, suele ser una superficie (AKA región de memoria de la VRAM) rectangular que contiene una imagen, patrón de colores o <inserte aquí su propia definición> que se aplica sobre nuestros polígonos para darle una apariencia (por eso se llama "textura" ) y jugando con el Alpha de los colores que contenga (si es que tiene dicha componente) y las funciones que menciono se puede jugar a "agujerear" un objeto por aplicarle en ciertas zonas una textura invisible.
Por ejemplo, podrías dibujar en pantalla un rectángulo y aplicarle una textura para formar una pared de ladrillos, una ventana de cristal (semitransparente) y el hueco de una puerta abierta (Alpha = 0). Dicho objeto costaría diseñarlo a base de polígonos, pero gracias a Alpha lo puedes lograr dibujando un simple rentángulo con una textura apañada.
Si importáis imágenes que carezcan de Alpha, tendréis que una de dos: añadir vosotros ese campo (por ejemplo, si sabeis que el negro contiene 0,0,0 y os interesa hacerlo transparente, poneis Alpha = 0 y al resto Alpha = 255 para 8 bits por componente) o desactivar éstas funciones para que no interfieran.
Uso de Texturas
Antes he definido la textura como una superficie rectangular, normalmente en VRAM que contiene un mapa de color (imagen), etc.
A mi me interesa que conozcáis dos cosas: como aplicar coordenadas de textura y como estas actúan para "envolver" los polígonos.
En Tiny3D (y en muchas otras aplicaciones) las coordenadas de textura se pasan mediante números flotantes referenciando los límites de la textura como 0.0 y 1.0.
Es decir, supongamos que tengo una textura de 128 x 64 , pues bien, desde el punto de vista de definir esa textura, le tengo que decir que es de 128 x 64, tantos bits de color y en este orden, pero desde le punto de vista de coordenadas, 128 = 1.0 y 64 = 1.0, por que el límite a la derecha y abajo, es 1.0 y luego el RSX, ya se encarga de hacer la conversión correcta.
Dicho de otra forma, el rectángulo de una textura a nivel coordenadas comienza en izquierda- arriba 0.0, 0.0 y acaba derecha - abajo 1.0, 1.0 .
En realidad deberíamos restar algo a ese 1.0 pues se pasa del límite de la textura. Dependiendo del modo de envoltura, eso puede ser mas o menos importante. También depende de si se usa filtro Lineal (LINEAR) o cercano (NEAREST) puesto que en el primero se obtienen los colores interpolando(y esto hace que sobrepase un poco el límite) y en el segundo por aproximación (el primero provoca suavizado de texturas y el segundo que se vean "cuadraditos").
En Tiny3D podemos especificar 3 tipos de envolturas:
TEXTWRAP_REPEAT: Aquí si se supera el límite de 1.0, empieza a repetir la textura formando un mosaico
TEXTWRAP_CLAMP: Hace que la textura se ajuste a su límites naturales (0.0 y 1.0)
TEXTWRAP_MIRROR: Esto permite que se pueda especificar un límite negativo (entre -1.0, 0.0 y 1.0) para provocar una textura espejo.
Tambien es posible aplicar dos texturas de forma simultánea a un objeto, con sus propias coordenadas particulares y eligiendo la forma de mezclarlas para obtener el resultado final. No es el cometido de este apartado explicar como funcionan las funciones de Tiny3D, si no explicar los conceptos que debes asimilar antes de que yo empiece a explicar la primera función .
Materiales y luces: uso de normales
Una normal es un vector, algo así como una flechita que apunta en una dirección, si tener una posición real en el espacio. Las normales que nos interesan, son las que tienen longitud 1.0, también llamadas unitarias y se utilizan para conocer el angulo en el que incide la luz, para poder aplicarla de forma correcta.
Las propiedades materiales describen como debe comportarse un objeto en relación a esa luz.
Suelen ser de 4 tipos:
Emissive: El objeto emite X luz propia.
Ambient: El objeto recibe X cantidad de luz en todo momento, del ambiente
Diffuse: Indica la proporción de luz que "absorberá" el objeto de la que reciba (por ejemplo, si el material difuso es naranja y recibe luz blanca, al mezclarse tendrás un naranja mas o menos claro)
Specular: Indica la proporción de luz que se reflejará en el propio objeto. O dicho de otra forma, si tengo un objeto naranja y recibe una luz blanca, con esta propiedad parte de el (controlado por el parámetro shininess) brillará en color blanco
Entendido esto, se puede pasar a hablar de luces. Las de Tiny3D son de tipo posicional y se puede utilizar hasta 4 luces de forma simultánea.
Al ser posicionales, se dispone de una función que permite definir la posición del observador para que trabajen correctamente. Tambien se dispone de la posibilidad de que trabajen de forma difusa o especular, a gusto del consumidor.
Otro tipo de luces no son soportadas: tampoco las veo útiles para hacer proyectos homebrew y seguramente, a la mayoría de vosotros os sobre toda esta información pues haréis un uso mucho mas simple (en 2D) de la librería.
Comenzando, que es gerundio
Bien, todo esto está muy bien, ¿Pero cuando empezamos a hacer algo?
Pues, en cuanto te explique algunos detalles mínimos de la librería.
En primer lugar cualquier proyecto compilado con Tiny3D 2.0 lleva de serie activado el evento 3 al iniciar la librería.
Eso significa que si registras una función con atexit() dicha función será invocada si salís pulsando el botón "PS" y que la consola no se quedará pillada al salir. Tampoco requiere que añadáis ese evento, pues ya está hecho.
Es mas, Tiny3D cuenta con una función int tiny3d_MenuActive(); que devuelve con 1 si hay algún menú en activo y 0 si no lo hay (puedes usar esto por ejemplo, para pausar lo que estés haciendo.
y ahora si podemos meternos en faena:
Mi primera aplicación no es un "Hola Mundo"
Bien, llegó la hora de ponerse en marcha: vamos a crear nuestra primera aplicación en Tiny3D. Pero como Tiny3D no trabaja sola y requiere de PSL1GHT, supongo que no os importará que haga referencia a mas cosas.
También estoy un poco "vago" y veo mas interesante explicar uno de los ejemplos ya hechos bajo Tiny3D y explicarlo de pe a pa, que hacer algo nuevo que luego quizá no sepas aplicar por otros problemas.
Como ya pone en cabecera, mi primera aplicación con Tiny3D no fue un "hola mundo", si no algo mucho mas complejo (Spheres3D) y ahora vamos a describir la segunda, llamada Sprites2D que creo que será fácil de asimilar (hace falta la última actualización para seguir esto)
Primero tenemos que preparar el proyecto, así que editamos el fichero "Makefile"
En concreto, para los Sprites se usa la librería de módulos, el PNG decoder interno de la consola, el pad para salir, la Tiny3D y mas cosas.
Esto es para crear el pkg e identificarlo haciendo "make pkg" en lugar de un simple "make"
Si añades ésta línea:
ICON0 = $(SOURCE)/../ICON0.PNG
Podrías incluir un ICON0.PNG propio al lado del Makefile, en vez de usar el que viene por defecto en PSL1GHT.
De todas formas, para cargar aplicaciones en pruebas, lo mejor es utilizar PS3LoadX: es uno de los ejemplos de Tiny3D y al ser tan especial, los suministro ya compilado y empaquetado en el git (mira en la carpeta samples)
Ahora vamos a echarle un ojo a la carpeta "data": dentro podemos observar una serie de ficheros tipo "ghostX_png.bin" que no son mas que simples PNG que al estar en formato .bin, al hacer "make" se convertirán mágicamente y se añadirán en memoria para usarlos. Si no me crees, prueba a cambiar el .bin por .png y verás que sorpresa .
Okis, ya hemos visto donde tocar el Makefile y como incluir datos dentro de la "chepa" del .self, poniendo la extensión .bin. Sobre como hacer "make", no voy a dar explicaciones, porque en el mismo hilo de instalación PSL1GHT que enlazo arriba hay referencias a ello y en todo caso, en el paquete de PSL1GHT que suministro, tienes un compila_ifcaro.bat que muestra las variables de entorno necesarias y además, te suministro todo, todo, excepto lo mas nuevo de Tiny3D que eso deberías bajarlo tu (lo mas probable es que actualice todas las librerías en los próximos días, pues hay cambios a todos los niveles Dicho a 8 de Enero del 2011))
Así que vamos a echar un ojo al main.c y ver que cosas interesantes tiene que contarnos:
¡Ah pillín! ¿asi que es así como incluyes los PNG de antes?
PngDatas texture_ghost [8]; // PNG container of texture u32 texture_ghost_offset[8]; // offset for texture (used to pass the texture)
Lo primero es una estructura que usaremos con el PNG decoder para crear nuestras texturas. Esos mismos datos que nos proporciona al cargar, podemos usarlos para definir la textura. El fantasma cuenta con 8 fotogramas así que esa es la razón del array [8] y texture_ghost_offset es para referenciar la textura al RSX.
Cuando nosotros asignamos memoria desde el RSX, ésta se mapea como memoria virtual en nuestro sistema. De esa forma el PPU (procesador) puede acceder a esos datos para modificarlos y cargar así una textura.
Pero claro, el RSX no entiende de memorias virtuales: necesita una referencia a su memoria que le diga: "aquí es donde tienes que buscar la textura" y esa referencia es ese offset.
Esos son datos para manejo del PAD, pero no me voy a extender sobre ellos.
tiny3d_Init(1024*1024);
ioPadInit(7);
SysLoadModule(SYSMODULE_PNGDEC);
atexit(exiting); // Tiny3D register the event 3 and do exit() call when you exit to the menu
Lo primero es la inicialización de Tiny3D: el parámetro es la cantidad de memoria de RSX que se reservará para los vértices. Esto es muy importante y aunque 1 MB pueda parecer mucho, hay que tener cuenta que debe ser suficiente para todo los que se trate de dibujar en un frame.
Si ese límite se rebasara, lo que ocurriría es que Tiny3D dejaría de dibujar a partir de ahí y probablemente veáis un borde rojo justo a la derecha que indicará dicho error, para que agrandéis el tamaño del buffer. Si se especifica menos de 1MB te escupe en la cara y asignará 1MB de todas formas .
Por cierto, si a ese valor, le aplicas (TINY3D_Z16 | (valor)), actívarias el buffer Z a 16 bits en lugar de a 32 bits. Se hace así por mantener la compatibilidad hacia atrás y evitar el uso de funciones superfluas.
La segunda función, inicializa los pads, la tercera carga el módulo PNG y la cuarta registra como función de salida "exiting" que descargará el módulo PNF en cuanto salgamos, ya sea pulsando el botón "PS" o de otra forma.
Luego le sigue LoadTexture(); y saltamos allí para ver como funciona eso:
Cargando texturas
void LoadTexture() { int i;
u32 * texture_mem = tiny3d_AllocTexture(64*1024*1024); // alloc 64MB of space for textures (this pointer can be global)
u32 * texture_pointer; // use to asign texture space without changes texture_mem
if(!texture_mem) return; // fail!
texture_pointer = texture_mem;
La primera función aloja 64 MB del RSX para usarlo como texturas. Es decir, ese bloque está alineado a 16 bytes.
Algo que debes de saber ya es que todas las texturas deben estar alineadas a esos 16 bytes y además, no eres libre de utilizar cualquier dimensión de las mismas: existen reglas (reglas oscuras ) que dicen que si el ancho (mas bien el pitch) no es el adecuado, puede que hasta se cuelgue la consola. Como mínimo y teniendo en cuenta esa alineación de 16 bytes, habría que tener la precaución de dividir esos 16 por los bytes que ocupe el color (que puede ser 1, 2, 4) para obtener por cuanto debe ser divisible el ancho/alto de la textura (o sea, que al dividir por ese número no produzca decimales).
Otro problema es que tu puedes ir asignando VRAM del RSX, pero luego no la puedes liberar!. Aquí no hay un "free" para ello y es por eso por lo que uso un sistema un tanto "raro". Y, precisamente, la asignación de texturas y el copiado al RSX, puede que sea la parte menos pulida de la librería, porque es posible que para muchos usos, tu puedas asignar la memoria que te haga falta y eso no lo vuelves a tocar más, pero ¿que pasaría si quisieras reemplazar tus texturas por otras nuevas?
Pues que simplemente, tendrías muchos "pedacitos" de VRAM que podrían resultar inadecuados.
Por eso yo empiezo asignando un gran bloque a un puntero que debería ser global (texture_mem) e inmediatamente, copio dicho puntero a otro (texture_pointer) donde iré haciendo el reparto. Y en el momento que me interese, podría volver a asignar texture_pointer = texture_mem; y hacer un reparto nuevo en esos 64MB que asigno (se supone que el RSX cuenta con 256MB y 64MB es un valor de referencia, aunque hazte la cuenta que como poco 32MB pueden gastarse para usos "internos" como bufferes de pantalla, Z, etc)
Volviendo al "tajo" te encuentras con Load_PNG();
Ahí es donde se cargan todos los PNG.
La función LoadPNG (que no es la misma que la otra) requiere dos parámetros: el primero es la estructura tipo PngDatas que comentaba al principio y el segundo, es o bien NULL, lo que indicaría que PngDatas contiene la dirección del PNG en memoria o un ruta tipo "/dev_usb/misco_jones.png" donde buscarlo.
Sea como sea, nosotros cargamos desde memoria y lo hacemos pasando en texture_ghost[X].png_in la dirección y en texture_ghost[X].png_size el tamaño del PNG.
Por supuesto, mis PNGs están calculados para que no den problemas al crear una textura con esos datos.
Así que volvemos de la función y continuamos la carga de textura: ya tenemos los PNG cargados e internamente, en el puntero al que apunta el campo bmp_out de PngDatas , tenemos los datos en formato A8R8G8B8.
Así que aquí:
// copy texture datas from PNG to the RSX memory allocated for textures
free(texture_ghost[i].bmp_out); // free the PNG because i don't need this datas
texture_ghost_offset[i] = tiny3d_TextureOffset(texture_pointer); // get the offset (RSX use offset instead address)
texture_pointer += ((texture_ghost[i].wpitch * texture_ghost[i].height + 15) & ~15) / 4; // aligned to 16 bytes (it is u32) and update the pointer } }
Hacemos un bucle para primero, comprobar que los PNG se cargaron correctamente, luego copiar los datos a la VRAM mediante el puntero texture_pointer, "offtenemos" el offset a la VRAM con tiny3d_TextureOffset() y ya solo nos queda actualizar texture_pointer para que señale a la VRAM donde podremos asignar la siguiente textura.
Para ello usamos el wpitch de PngDatas, que contiene el numero de bytes que requiere de ancho el PNG, lo multiplicamos por la altura y lo alineamos a 16 bytes con la combinación (bytes+15) & ~15; (si no sabes como funciona ésto último, da igual: el caso es que funciona )
Pero texture_pointer es un puntero u32 y por tanto, se incrementa de 4 en 4 bytes por cada unidad que sumemos. Así que esa es la razón de dividir entre 4, finalmente.
Una vez que ya conocemos el proceso mediante el cual, cargamos los PNG y los copiamos a VRAM, obteniendo el offset y repartiendo de forma correcta la memoria, podemos volver. Para luego poder usar ésto como textura, se necesitan datos adicionales, pero por suerte, los tenemos ya en la estructura tipo PngDatas.
Al volver al main() vemos que se asignan los datos para cada fantasma y tal, y pasamos al while(1) que cerrará el bucle del programa.
Bucle en main()
while(1) {
/* DRAWING STARTS HERE */
// clear the screen, buffer Z and initializes environment to 2D
tiny3d_Clear(0xff000000, TINY3D_CLEAR_ALL);
// Enable alpha Test tiny3d_AlphaTest(1, 0x10, TINY3D_ALPHA_FUNC_GEQUAL);
La primera función borra todos los buffers de este frame (como se trabaja a doble buffer, aunque lo borres todo, no afecta a la salida de la pantalla, todavía...) con el color especificado (solo alpha en este caso).
No solo tiene este efecto: también se cambia al modo 2D, se inician las matrices y se restablecen algunas cosas para empezar de cero.
Las funciones AlphaTest y BlendFunc son de las que hablé antes, las que controlan el uso de Alpha. Si no entiendes los parámetros, NO IMPORTA: solo importa que sepas que la primera hace que solo los colores con Alpha >= 0x10 se dibujen (controla transparencia) y la segunda, como se mezclan en función de Alpha (translucidez)
A eso le sigue unas funciones que leen el PAD y controlan que si pulsas 'X', salgas del ejemplo. Si no sabes como funciona eso, no te preocupes por el momento.
La función drawScene(); como indica su nombre, es la que dibujará toda la "escena" y ahora nos meteremos en profundidad con ella.
Por último, se llama a tiny3d_Flip(); que provocará que se dibuje y se espere al cambio de buffer antes de permitir que de nuevo, se borren los bufferes y se proceda a dibujar.
drawScene()
Bien, esto es como cuando te interesas por un videojuego: primero te muestran imágenes para que veas lo que molan, luego vídeos, luego vas a la tienda y tienes que esperar hasta llegar a casa y probarlo. Tal vez leas las instrucciones, lo cargas, antes de empezar ves un vídeo, te pones a jugar y otro puñetero vídeo, te deja moverte algo y ¡Otro vídeo!.
Pero después ya empiezas a jugar y mas mola mil.
Ahora estamos en ese punto, ya tenemos la pantalla limpia, estamos en modo 2D y con las funciones Alpha fijadas y toca "pintar" monigotes .
Despues de actualizar algunos datos referentes al ángulo de rotación de cuando los fantasmas dan volteretas (compilar y ver el ejemplo), nos encontramos con una función, tiny3d_Project2D() que técnicamente, no sirve para nada aquí, pues ya la función tiny3d_Clear() la llama de forma interna. Antes de que me pegues , la añado para que se sepa que estás en modo 2D o por si metes algo en modo 3D antes, que funcione bien.
En todo caso, mas vale asegurarse, que llorar. Si por casualidad, quisieras activar el modo 3D, la función equivalente es tiny3d_Project3D() a la cual debería seguir la función tiny3d_SetProjectionMatrix() para fijar la matriz correspondiente (y necesaria!)
Bien, pero ahora estamos en 2D, lo que significa que nuestro sistema de coordenadas ficticio (o virtual, como prefieras) es de 848.0 x 512.0 en pantalla.
Aqui se llama a una función DrawBackground2D(0x0040ffff) cuyo uso podríamos evitar fijando el color en la función tiny3d_Clear(). La incluyo porque sirve de test al Z buffer y porque tal vez tu en vez de dibujar un fondo de color, quieras cambiarlo por un fondo texturizado y ahí sería el punto idóneo.
Bien, acabamos de dibujar nuestro primer rectángulo que ocupa toda la pantalla con un solo color.
Si conoces un poco OpenGL, no te será dificil entender las funciones, pero de todas formas, yo lo explico!
Bien, lo pimero que debes conocer es como se especifican los polígonos. La cosa comienza siempre con un tiny3d_SetPolygon(x) y acaba con un tiny3d_End() con una lista de atributos de vértices por medio.
Eso quiere decir que podemos dibujar un buen puñado de la misma clase de polígonos simplemente, proporcionando los vértices necesarios (Captain Obvious)
Por ejemplo, en este caso es un QUAD. Los QUADs requieren 4 vértices que deben estar ordenados "según las agujas del reloj" de esta forma:
a---b | | d---c
Como ves, a,b, c, d siguen el sentido horario y si te fijas en los VertexPos, verás que las X e Y se ordenan de esa forma, mientras que la Z usa mí valor "mágico" de fondo de pantalla, el valor 65535.0 (aunque veas valores enteros, en realidad la función lo toma como float, pues solo admite floats)
Si quisiera dibujar otro Quad, simplemente, añadiría otros 4 vértices antes de cerrar con tiny3d_End(). No te preocupes, que Tiny3D hace sus cuentas (y el RSX) y siempre que no le falten pies al gato, sabrá como dibujar.
Las funciones como tiny3d_VertexPos() o tiny3d_VertexColor() es lo que se llaman atributos del vértice. Mas adelante, en el post que tengo reservado, describiré las funciones una a una, pero ahora toca explicar un poco como funciona esto, así por "encima".
El primer atributo y el único obligatorio es el de posición. Estas son las tres formas de decir "lo mismo".
void tiny3d_VertexPos(float x, float y, float z); void tiny3d_VertexPos4(float x, float y, float z, float w); void tiny3d_VertexPosVector(VECTOR v);
En este caso, ya sabéis X, e Y en función de las coordenadas "ficticias" y la Z entre 0 y 65535.0 para que no se espante. El W de la función de en medio, vedlo como 1.0 para vuestros usos (no preocuparrse, no preocuparrse )
La primera forma recibe el color en un u32 siendo el Rojo los 8 bits de mayor peso y Alpha los 8 de menor peso ( (R<<24) | (G<<16) | (B<<8) | A). La segunda forma es como dije al principio, con números flotantes que van de 0.0 a 1.0.
Como veis el atributo de color solo fijo una vez y este se repetirá por si mismo en los siguientes vértices.
En tiny3d_SetPolygon(x) se puede especificar:
TINY3D_POINTS = Puntos, requiere 1 vertice por punto
TINY3D_LINES = Lineas, requiere 2 vertices por punto
TINY3D_LINE_LOOP = Lineas enlazadas, dibujando una última para unir el último punto con el primero. La primera requiere 2 vertices, pero las siguientes usan el vértice anterior y otro nuevo.
TINY3D_LINE_STRIP = igual que LINE_LOOP solo que no cierra el último con el primero.
TINY3D_TRIANGLES = Triángulos, requiere 3 vértices por triángulo.
TINY3D_TRIANGLE_STRIP = Triángulos enlazados. El primero requiere 3 vértices, pero el siguiente utiliza los dos últimos vértices y otro nuevo para crear el triángulo.
TINY3D_TRIANGLE_FAN = Triángulos formando abanico. El primer vértice es el centro, requiere otros dos para formar el primer triángulo y un nuevo (mas dos antiguos) para formar los siguientes. Lo que se suele hacer es que se pasa el centro y luego los vértices de la periferia, para por ejemplo crear las caras de arriba y abajo de un cilindro.
TINY3D_QUADS = Cuadrados con 4 vértices (recordad que requiere orden horario y tal pascual)
TINY3D_QUAD_STRIP = Similar a Triangle Strip, pero requiere dos nuevos vértices para formar el siguiente Quad
TINY3D_POLYGON = Forma un polígono utilizando los vértices que le pases (por ejemplo con 5 puedes formar un pentágono)
Bien, retornamos de la función y nos topamos con un bucle donde se dibujan los sprites de los fantasmas.
void tiny3d_SetTexture(u32 unit, u32 offset, u32 width, u32 height, u32 stride, text_format fmt, int smooth);
El unit hace referencia a la unidad de textura, por lo general 0 (TEX0) pero si especificas otro valor, haces referencia a otras unidades de textura (en Tiny3D para 2D/3D se puede usar TEX1 y para modo YUV hasta TEX2, pasando 1 y 2 respectivamente, por lo que se pueden aplicar hasta tres texturas de forma simultánea si el shader lo soporta). Pero como tu eres un noob, olvida esto y quédate con que pasas 0 y que tal vez algún día, tengas que venir a leer esto para utilizar varias texturas .
El offset es el offset a la VRAM obtenido cuando creamos nuestra textura ¿recordáis?. width, height y stride (en este caso 4*width pues se usan 32 bits) no los proporciona la estructura PngDatas al cargar el PNG. Stride si fuera color a 16 bits, pues lo mismo: 2*width, aunque lo importante es que es width sea adecuado para no cagarla (y por eso hay un parámetro stride además del width ¿que rollo, verdad? )
Como formato de textura se usa A8R8G8B8, pero se soportan estos tambien:
TINY3D_TEX_FORMAT_L8 -> almacena valores de 8 bits. Util ahora mismo para el mapa YUV en tres texturas TINY3D_TEX_FORMAT_A1R5G5B5 -> O todo o nada: solo 1 bit alpha TINY3D_TEX_FORMAT_A4R4G4B4 -> formato guapísimo que a mi me vino de perlas para libfont por su capacidad alpha TINY3D_TEX_FORMAT_R5G6B5 -> cuidado con este que no tiene alpha!!! TINY3D_TEX_FORMAT_A8R8G8B8 -> este es way del Paraguay, pero son 4 bytes por cada texel
El último dato. smooth lo podemos fijar como TEXTURE_NEAREST (o 0), lo cual tiende hacer cuadraditos como en la PSX o como TEXTURE_LINEAR (o 1) para tener un apurado mas suave
Vale, ya le hemos dicho al RSX donde encontrar la textura y de que tipo es, pero no la aplicará hasta que pasemos coordenadas de textura y eso provoque el cambio de shader apropiado en Tiny3D.
Antes de nada, cabe comentar que esa función para añadir texturas, es la antigua y por tanto, admite que la textura se pueda repetir. Si recordáis el capítulo donde hablo de la envoltura de las texturas, me refería al uso que se le puede dar a ésta función:
void tiny3d_SetTextureWrap(u32 unit, u32 offset, u32 width, u32 height, u32 stride, text_format fmt, int wrap_u, int wrap_v, int smooth);
Como se puede apreciar, es igual que la otra pero añadiendo wrap para U, V (que es como decir X e Y para texturas).
De las funciones que siguen en mi ejemplo, DrawSprites2D() y DrawSpritesRot2D() voy a tratar de sintetizar los comentarios. El resto, son solo actualización de variables que no vienen a cuento y no tiene sentido explicar las cosas que sean comunes.
Lo diferente de DrawSprites2D() es que especifica atributos de textura, además de la posición y color ya vistas. Si os fijáis, las coordenadas U, V (ver como X e Y para texturas) van desde 0.0 a 0.99 usando la referencia que ya os explique. E igualmente, siguen el sentido horario pues queremos mapear el rectángulo (o cuadrado en este caso) de la textura con el rectángulo del QUAD.
En tiny3d.h se puede ver que se pueden especificar así los atributos de textura:
void tiny3d_VertexTexture(float u, float v);
void tiny3d_VertexTexture2(float u, float v);
tiny3d_VertexTexture2 hace referencia a la segunda unidad de textura, esto es unit = 1 (TEX1). Si se usan dobles texturas, lo conveniente es que se especifique antes, como deben aplicarse con la función:
// set method for merge multiple textures (it change the pixel shader)
void tiny3d_SelMultiTexturesMethod(u32 method);
Y por supuesto: deberías fijar la segunda textura con las funciones tiny3d_SetTexture o tiny3d_SetTextureWrap. Si te has liado aquí, por favor, pasa desde el punto donde comienzo a hablar de tiny3d_VertexTexture2 hasta el siguiente punto.
DrawSpritesRot2D()
Lo que hace diferente a DrawSpritesRot2D() es que por primera vez, salen a relucir las matrices. En este caso, las usaremos para rotar sobre el eje Z y que nuestro fantasma haga volteretas
Siempre que hable de un eje, imagínate una naranja atravesada por una de las agujas de hacer punto que usan nuestras madres/abuelas y apuntala en la dirección a la que hago referencia, en este caso Z. Si girases la aguja podrías girar la naranja en sentido horario o antihorario, pero se deplazaría en el plano X, Y manteniéndose a la misma "profundidad" Z. Es decir, que el truco para saber sobre que eje rotar es pensar justo en el plano que no queremos (o no vamos) a modificar.
Así que lo primero que debemos de pensar es en referenciar nuestro QUAD con el centro (0.0, 0.0) y para ello dividimos entre 2 el ancho y el alto y utilizamos -dx , +dx y -dy, +dy como coordenadas de posición en VertexPos para ello (es un truco mas viejo que Satán )
Luego con:
matrix = MatrixRotationZ(angle);
Calculamos la matriz de rotación necesaria.
Pero claro, nosotros tenemos una posición en pantalla en la que dibujar nuestro sprite que hemos perdido al trabajar así. Normalmente, le pasamos la posición de la esquina izquierda-superior, el ancho y el alto y ahora necesitamos decirle de alguna manera al Shader a posición donde debería dibujar el QUAD, una vez rotado (por que la rotación la hace el, claro)
Afortunadamente, la multiplicación de matrices acude a nuestro rescate:
matrix = MatrixMultiply(matrix, MatrixTranslation(x + dx, y + dy, 0.0f));
Como veis la función de translación se ajusta con la mitad del ancho y alto para referenciar donde estaría las coordenadas del centro del QUAD y así, despues de rotar, que se ajuste a la posición adecuada.
La función tiny3d_SetMatrixModelView(&matrix) se encarga de programar la matriz que usará el shader.
Por defecto ModelView usa la matriz identidad, que es una matriz que no causa cambio alguno en el objeto.
En Tiny3D existe una forma de decirle "usa la matriz identidad" sin tener que pasarle una. Es justo lo que hacemos con:
tiny3d_SetMatrixModelView(NULL); // set matrix identity
justo después de tiny3d_End().
tiny3d_End() es una función importantísima pues no solo cierra la lista de polígonos, si no que es la que se encarga de cargar los shaders, actualizar las matrices de forma efectiva e indicarle a RSX la lista de polígonos y todo lo que debe de hacer. Así que recordadlo bien "tiny3d_End() es solo el principio de las cosas que están por venir" .
Si necesitáis mas documentación, echadle un ojo a tiny3d.h y tambien a matrix.h para ver las funciones disponibles.
Yo haré una descripción mas tarde en Español, para los que no entienden el pitinglish, pero tambien para los que si lo entienden, pero no me entienden a mí o tal vez para añadir cosas extras que no explico en el .h en parte por que no ha lugar, en parte por lo limitado de mi Inglés.
Como ya habréis adivinado, mi explicación del ejemplo iba en ese sentido: trato de describir no solo lo que se ve, si no lo que no se ve si solo te limitas a explicar el uso de las funciones.
El loco y maravilloso Mundo de las 3D
Ya he explicado algunos detalles sobre 3D en relación al sistema de coordenadas que emplea (recordemos: hacia la izquierda negativo,hacia la derecha positivo, hacia arriba, positivo, hacia abajo negativo, hacia el fondo positivo, fuera de la pantalla... es fuera de la pantalla y puede pasar cualquier cosa )
Se supone que las coordenadas toman como referencia 0.0 y 1.0 pero resulta que tenemos modos 16:9 con lo cual, para mantener la proporción, el ancho debe ser mayor. Pero también un objeto se agranda según se acerca y empequeñece al alejarse y por tanto, la apariencia va a depender del factor de escala que se aplique con la distancia y para esa distancia, la X e Y abarcará mas o menos rango (si dibujas un objeto justo en frente de las narices, es muy probable que se salga de la pantalla, pero si lo dibujas relativamente lejos, cabrían muchos objetos como ese en la pantalla.
Como no sería lógico que destripara por completo un ejemplo como hice antes (me temo que habría que escribir un libro), habrá que sintetizar (ejemplos en 3D tenéis Tiny3D_List (que utiliza el sistema de listas) y Spheres3D (que utiliza un uso avanzado para renderizar la escena en una textura y luego crear otra que por un lado, dibuja un rectángulo (QUAD) con esa textura y por oto, usa la habilidad multitexturas para envolver una esfera con esa textura)). Sea como sea, aquí van los puntos mas importantes:
Iniciando la librería:
tiny3d_Init(TINY3D_Z16 | 4*1024*1024); // asigna 4 MB para vértices y buffer Z de 16 bits
o
tiny3d_Init(4*1024*1024); // asigna 4 MB para vértices y buffer Z de 32 bits
En el bucle en main()
tiny3d_Clear(0xff000000, TINY3D_CLEAR_ALL); // esto siempre, al iniciar un frame
// Enable alpha Test. Añade esto si quieres alphatest tiny3d_AlphaTest(1, 0x10, TINY3D_ALPHA_FUNC_GEQUAL);
// Enable alpha blending. Añade esto si quieres alpha blending tiny3d_BlendFunc(1, TINY3D_BLEND_FUNC_SRC_RGB_SRC_ALPHA | TINY3D_BLEND_FUNC_SRC_ALPHA_SRC_ALPHA, NV30_3D_BLEND_FUNC_DST_RGB_ONE_MINUS_SRC_ALPHA | NV30_3D_BLEND_FUNC_DST_ALPHA_ZERO, TINY3D_BLEND_RGB_FUNC_ADD | TINY3D_BLEND_ALPHA_FUNC_ADD);
DrawScene(); // aquí dibujaremos la escena propiamente dicha
..... // aquí podemos leer el pad u otras cosas
tiny3d_Flip(); // aguarda (y fuerza) a que el RSX termine de dibujar y cambiar el buffer de pantalla.
En DrawScene()
MATRIX tmp; // una matriz temporal cualquiera
tiny3d_Project3D(); // change to 3D context. Bienvenido al contexto 3D
/* fix Perspective Projection Matrix. Creamos nuestra matriz de proyección en tmp y la asignamos con la función tiny3d_SetProjectionMatrix(). La matriz es copiada internamente, al igual que pasa en el caso de ModelView, por lo tmp se puede utilizar para otras cosas.
A destacar el uso de la variable Video_aspect que contiene 1 si estamos en un modo de video 4:3 y 2 si es 16:9. Aquí es usado para variar el factor de escala de las X, pues por defecto todo está preparado para 16:9
El resto son parámetros para fijar la proyección de los objetos y hay ejemplos por la red similares para comparar (esos que pongo, son los que a mi me han gustado) */
Ahora lo suyo es que fijáramos las fuentes de luz:
tiny3d_SetLightsOff(); // apaga todas las luces en uso
tiny3d_SetLightCameraPosition(0.0f, 0.0f, 0.0f); // fija la posición del observador en 0,0,0
tiny3d_SetAmbientLight(0.5f, 0.5f, 0.5f); // fija el nivel de luz ambiental de la escena
/* activa una primera luz posicional (desde arriba - derecha) blanca, tipo especular (light0_x se emplea para desplazar la luz) */ tiny3d_SetLight(0, 50.0f + light0_x, 50.0f, 0.0f, 0.95f, 0.95f, 0.95f, LIGHT_SPECULAR);
/* activa una segunda luz posicional (izquierda, arriba) amarilla, tipo especular */ tiny3d_SetLight(1, -500.0f, 500.0f, 0.0f, 0.95f, 0.95f, 0.0f, LIGHT_SPECULAR);
/* activa una tercera luz posicional (desde abajo) verde, tipo especular */ tiny3d_SetLight(2, 0.0f, -500.0f, 0.0f, 0.0f, 0.95f, 0.0f, LIGHT_SPECULAR);
Evidentemente, son valores de referencia en este caso, sacados de Spheres3D. Para mas detalles sobre estas funciones, tiny3d.h o la descripción en español que haré mas tarde.
Ahora ya estamos listos para trabajar con objetos. Lo que tocaría ahora, sería definir el tipo de material del que está "hecho", para que iluminación se aplique como queremos:
// no emite luz tiny3d_EmissiveMaterial(0.0f, 0.0f, 0.0, 0.0f);
// nivel de luz ambiental para el material (gris oscuro). Ojo al Alpha, que debe ser 1 si el objeto es sólido tiny3d_AmbientMaterial(0.33f, 0.33f, 0.33f, 1.0f);
// nivel de luz difusa para el material (gris moderado). Si alpha es 0, se apaga esta propiedad material tiny3d_DiffuseMaterial(0.58f, 0.58, 0.58, 1.0f);
// nivel de luz specular para el material (blanco). Si alpha es 0, se apaga esta propiedad material tiny3d_SpecularMaterial(0.99f, 0.99f, 0.99f, 27.8f);
Aparte de fijar el tipo de material, también podríamos fijar un textura en este momento. Como ya hemos visto como hacerlo (tiny3d_SetTextureWrap()), nos saltamos ese paso.
Ahora tocaría fijar la posición en el mundo virtual mediante la matriz ModelView:
MATRIX matrix;
tmp = MatrixTranslation(0, 0.0, 80); // desplazamiento en 80 unidades hacia el fondo matrix = MatrixRotationY(angY); // rotación a lo largo del eje Y (giro planetario) matrix = MatrixMultiply(matrix, tmp); // crea una matriz que gira y luego desplaza
En lugar de eso, lo que nos interesa es que tipo de lista hay que pasar como vértices.
Y vemos que en el caso de esta función se pasan los siguientes atributos:
tiny3d_VertexPos(x, y, z); tiny3d_Normal(nx, ny, nz);
Posición y Normal. Aquí no hay atributo de color, puesto que eso se hace mediante el material descrito y tambien depende de la luz que incida. Si podría haber coordenadas de textura.
tiny3d_Normal() debe recibir un vector unitario. Un vector unitario es un vector que tiene longitud 1. Recordemos que dicho vector se utiliza para calcular el ángulo de incidencia de la luz y que el shader sepa que cantidad de luz debe de aplicar.
Como ejemplo de crear normales, en la función CreateSphereNormal() estoy utilizando el punto de la esfera calculada (que está referenciado a 0 y por tanto, señala en una dirección) para crear la normal. Para ello se usa éste método que acorta la distancia desde el centro a 1, haciendo el vector apto para utilizarse como normal:
#define NORMALIZE(X,Y,Z) \ l=sqrtf(X*X + Y*Y + Z*Z); /* calcula longitud total */\ \ if(l == 0.0f) l = 1.0f; /* si longitud 0, salva la situacion */ \ \ nx= X / l; ny= Y / l; nz = Z / l; /* divide por la longitud para que el vector sea unitario */
Ahora tenemos una función en matrix.h que hace eso mismo solo que trabaja con el tipo vector:
typedef struct {
float x, y, z;
} VECTOR;
VECTOR VectorToUnit(VECTOR v);
Ya tenemos nuestro objeto listo para dibujarse. Podríamos seguir dibujando cosas en 3D o volver a en cualquier momento al modo 2D llamando a tiny3d_Project2D() para por ejemplo, dibujar un texto en pantalla
El uso de listas
Las listas son algo muy nuevo en Tiny3D. Se han pensado para simplificar procesos, reutilizarlos, despejar el código fuente de detalles que puedan despistar y tal vez como método de importación/exportación.
Las listas tienen su fuerte en 3D, que es donde de verdad las vamos a necesitar para crear objetos complejos. Una lista, puede a su vez contener otras listas, que a su vez contengan listas en su interior y así hasta la locura
Pero, ¿Que es lo que contienen las listas para que resulten interesantes?
Pues contienen descripciones de materiales, matrices, descripciones de textura y como aplicarla, polígonos y vértices. Todo junto o por separado.
Por ejemplo, puedes crear una lista que almacene un tipo de material y luego utilizarla con los polígonos oportunos.
Tambien puedes crear una lista que contenga todos los objetos, indicando una serie de matrices externas que lo controlarán y luego tu solo tienes que modificar esas matrices e indicar que se dibuje la lista contenedora. ¿Mola no?. Todo encapsulado, se acabó el tener que trabajar con polígonos dispersos o incluso tener que crear el mismo objeto varias veces (como en mi ejemplo de spheres3D) y muy fácil de usar!
NOTA: Debes tener en cuenta que las listas pueden ahorrar espacio en RAM pero no en la VRAM, en concreto, la lista de vértices. Si haces una lista con un objeto que gaste 3000 vértices y la utilizas 8 veces, gastarás 24000 vértices de cara al RSX. En ese sentido, no se ahorra, que conste
NOTA2: A nivel de memoria, una lista comienza asignando un bloque de 4096 bytes. De ese bloque se reservan 72 bytes de forma especial para evitar rebasar el bloque (no es que no se utilicen, pero si se pueden "malgastar"). Si se supera ese límite, se asigna otro bloque de 4096 bytes y se escribe un comando en el bloque anterior para concatenar ambos. Así, todo lo que haga falta. El último bloque al cerrar la lista (el último puede ser el primero, si solo hay uno ) es reducido para ahorrar memoria.
El sistema puede parecer extraño pero pensad que Tiny3D no puede saber que tamaño real necesitará vuestra lista y siempre es mejor asignar trozos relativamente pequeños de memoria, que bloques enormes. Obviamente, se podría haber tenido en cuenta utilizar un tamaño de bloque mayor, teniendo en cuenta que el último es optimizado y tal vez lo haga: 65536 bytes podría ser un buen tamaño, teniendo en cuenta que hay unos pocos bytes que se desperdician .
El ejemplo tiny3d_lists es la referencia aquí. Para ayudaros a orientaros, al principio se añade todo lo referente a las listas y que funciones están implicadas.
En principio, estas son las funciones de control de listas:
/* comienza a grabar una lista. Devuelve < 0 si error */
int tiny3d_RecordList();
/* detiene la grabación de una lista. Devuelve el puntero a la cabecera de la lista o NULL. No debes olvidar llamarla!!! */
void * tiny3d_StopList();
/* dibuja una lista. Esta función se puede añadir entre RecordList y StopList para concatenar listas */
void tiny3d_DrawList(void * headlist);
/* borra la lista (pero no otras listas que se puedan usar llamando a tiny3d_DrawList()
void tiny3d_FreeList(void * headlist);
A esas de control, se añaden algunas funciones especiales solo para uso con listas:
/* tiny3d_DynamicMatrixList: función para añadir una matrix de control externa. Almacena un puntero a la matriz, así que mucho ojo si utilizáis una matriz como variable local de una función con dibujar la lista fuera de esa función */
void tiny3d_DynamicMatrixList(MATRIX *mat);
/*tiny3d_ApplyMatrixList: esta función provoca que la matriz se aplique a todos los atributos de posición y normal que se especifiquen directamente en los vértices, por lo que se almacenan ya los cambios. Esto no implica que ModelView sea ignorado si es fijado en algún punto anterior tiny3d_SetMatrixModelView(): se aplicará primero los cambios y luego los de ModelView, Pero si la función tiny3d_SetMatrixModelView() se invoca después o se invoca a tiny3d_DynamicMatrixList() se deshabilitará tiny3d_ApplyMatrixList() */
void tiny3d_ApplyMatrixList(MATRIX *mat); // apply directly the matrix to the vertex position and normals
También es utilizable tiny3d_SetMatrixModelView() pero necesita explicación adicional:
Cuando creas una lista, esa lista es tratada como un único objeto y por tanto, al dibujarla se ve sometida a la matriz ModelView que fijamos en la escena con tiny3d_SetMatrixModelView().
Si embargo, dentro de la lista, se pueden especificar matrices, tanto externas con la función tiny3d_DynamicMatrixList, como internas (estáticas) con la función tiny3d_SetMatrixModelView().
En éste último caso, la matriz ModelView afectará a partir de ese punto a todos los polígonos presentes en esa lista y al resto de listas que sean dibujadas, concatenando (multiplicando) las matrices en caso necesario.
Sin embargo, fuera de la lista, el valor de ModelView de la escena no se ve alterado: de hecho es concatenado con todos los cambios de la lista, como acabo de describir. Así que como es lógico, el uso de tiny3d_SetMatrixModelView() en listas, solo se circunscribe a ellas y es diferente del uso fuera de esa lista en concreto.
Por contra, si ajustamos ModelView con tiny3d_SetMatrixModelView() se almacenarían de forma estática esos valores para que sin cambiar los vértices, se aplique luego mediante una multiplicación de matrices
Sobre que funciones se pueden usar dentro de una lista, lo dejo en spoiler, por que son un buen puñado :
// matrices
void tiny3d_SetMatrixModelView(MATRIX *mat); /* effect : put one static matrix (when you call to DrawList this matrix is the first to multiply) */
void tiny3d_ApplyMatrixList(MATRIX *mat); /* apply directly the matrix to the vertex position and normals to create the object */
void tiny3d_DynamicMatrixList(MATRIX *mat); /* put one matrix to control the object externally (if you modify the matrix, the object changes) */
// poligonos
int tiny3d_SetPolygon(type_polygon type); int tiny3d_End();
// atributos de vertice
void tiny3d_VertexPos(float x, float y, float z); void tiny3d_VertexPos4(float x, float y, float z, float w); void tiny3d_VertexPosVector(VECTOR v);
void tiny3d_SelMultiTexturesMethod(u32 method); void tiny3d_SetTexture(u32 unit, u32 offset, u32 width, u32 height, u32 stride, text_format fmt, int smooth); void tiny3d_SetTextureWrap(u32 unit, u32 offset, u32 width, u32 height, u32 stride, text_format fmt, int wrap_u, int wrap_v, int smooth);
Nota Importante sobre el RSX
El RSX obedece a una serie de reglas "no escritas" por las cuales puede o no dibujar el polígono cuando le salga de los eggs. O mejor dicho, dependerá de que se llene el FIFO asociado y del momento en el que él acabe de hacer las cosas o se le fuerce a hacerlas: solo podemos estar seguro de que el RSX ha terminado de hacer lo que sea, una vez hayamos llamado a tiny3d_End() y estemos de vuelta. Es por eso por lo que no debes pensar que las cosas que haces, las haces de forma instantánea.
Por ejemplo, cuando especificas matrices, estas guardan una copia en un lugar reservado y solo se pasan al RSX al llamar a la función tiny3d_End(), que como ya expliqué y vuelvo a explicar, es la madre del cordero, el inicio real de todas las cosas, pues, completa la lista de vértices, cambia el shader, fija matrices el polígono a dibujar(estas matrices se pasan al shader) y envía las listas de vértices al RSX que actuará en el momento oportuno (creo que cuando se llena el FIFO y deja de estar ocupado o cuando se lo ordena el flip())
Eso significa que por ejemplo, no puedes (o mejor, no debes) cambiar una matriz en medio de una lista de vértices: debería cerrar la lista con tiny3d_End() y luego, ya si podrías, justo antes de tiny3d_SetPolygon().
Esto es un inconveniente bastante gordo cuando se trabaja con texturas: las texturas NO DEBEN sufrir cambios desde el momento que las usas. Es decir, imagina que yo creo una textura dibujo un polígono y luego usando el puntero a la textura (en memoria virtual) cambio el dibujo de la textura y dibujo otro polígono.
Tu pensarás de forma equivocada que has fijado el orden de como debe procederse, pero en realidad lo que le has dicho al RSX es: "usa la textura para dibujar éste polígono y luego usa la misma textura para dibujar éste otro". Y antes de que el RSX haya tenido tiempo a ver eso, ya has cambiado la textura!!!
Por tanto, la regla de oro es que antes de comenzar o después de acabar de usar texturas (o una textura), las puedes modificar: solo una vez por frame y antes de usarla con polígonos.
Esto explica por qué en libfont (que luego veremos) se capturan fuentes .ttf en vez de usarlas de forma directa: se puede hacer, pero entonces tendrías que crear una textura del tamaño de toda la pantalla (para hacerlo de forma apropiada) y escribir a "mano" tirando de CPU, los texels oportunos para luego "pegar" con un QUAD esa imagen en primer plano, tirando de transparencia (creo que se refleja lo lento que es, el gasto de VRAM que supone y lo poco flexible. De hecho, incluso si no hubiera este problema con las texturas, sigue siendo igual de "lento" formar una carácter y que tuviéramos que aguardar al RSX para poder modificar la textura).
En su lugar, en libfont se crea una lista de texturas con cada uno de los caracteres de la fuente, a un tamaño prudencial. Y en el caso de .ttf añadiendo las correcciones de posición oportunas: no es perfecto, pero es una solución bastante buena para salir del paso, rápida y desde luego, menos gastona con la VRAM.
Bien, pero ¿Cómo puedo hacer un "hola mundo"?. ¿Cómo puedo utilizar fuentes de letras?
Pues...ahí va:
LIBFONT, la librería de fuentes
¿Os acordáis de nuestro querido Makefile? Pues habrá que echarle de nuevo, un ojo:
Tambien si queremos utilizar fuentes .ttf, hay que añadir las ps3libraries (en el enlace a EOL que suministro al principio sobre la instalación de PSL1GHT, en mi post, está la descarga a las librerías, aparte de en el README.md de Tiny3D se menciona)
De nuevo recomiendo echar un ojo a libfont.h y que le echéis un ojo a los ejemplos de fuentes de Tiny3D
En nuestro main.c hay que incluir:
#include <tiny3d.h> #include <libfont.h>
Las fuentes TTF requieren una inicialización y una captura especial y me temo que lo mejor será que mires el código fuente de fonts_from_ttf para ver como se relacionan ambas librerías, por que sería largo de contar y a mi lo que me interesa, es que sepas utilizar la librería y ya está
Así que primero vamos a detallarla un poco: Libfont ya he comentado que usa fuentes fijas, pero he omitido que está orientada a ser utilizada en modo 2D, con las coordenadas de referencia 848.0 x 512.0. La Z es posible cambiarla mediante una función pero en principio, lo normal es que un texto vaya en primer plano y se utiliza 0.0 como referencia.
Se pueden añadir hasta 8 fuentes de letra y la función original está adaptada casi para poder admitir cualquier fuente que use un rectángulo monocromo o con hasta 8 bits de intensidad por punto, pudiendo elegir el inicio y el final de la fuente en un rango de hasta 256 caracteres.
Lo primero que tenemos que hacer, es conocer como se añaden las fuentes, claro.
En el ejemplo fonts incluyo tres fuentes de letra, dos que ya he usado en Wii que constan de 224 caracteres ANSI (me ahorro del 0 al 32, que no se consideran imprimibles) de 16x32 píxeles y con 2 bits de color para suavizar la fuente. Y luego otra que procede del MSX, un guiño a los viejos tiempos de PS2 y que consta de 256 caracteres de 8x8 y 1 bit.
Para cargarlos, nos vamos a nuestra ya clásica función de textura y hacemos en dicho ejemplo:
void LoadTexture() {
u32 * texture_mem = tiny3d_AllocTexture(64*1024*1024); // alloc 64MB of space for textures (this pointer can be global)
u32 * texture_pointer; // use to asign texture space without changes texture_mem
if(!texture_mem) return; // fail!
texture_pointer = texture_mem;
ResetFont(); // importante para liberar fuentes previas e iniciar todo
Como se puede apreciar, se asigna el puntero que almacena la fuente, nuestro conocido texture_pointer, el primer y último caracter contenido en el bitmap de la fuente , el ancho, el alto, el numero de bits por color (mas bien intensidad de color) y lo último indica el orden en que se almacenan los píxeles en un byte (sabiendo donde está el primer píxels, sabes donde están los demás, claro). Esto no tiene mucha importancia conocerlo, salvo que necesites adaptar otra fuente o vayas a crear una por el estilo.
El retorno devuelve texture_pointer alineado y actualizado. Como se puede apreciar,las fuentes se añaden una detrás de otra y según ese orden las podréis asignar luego.
Las funciones TTFLoadFont(), TTFUnloadFont(), TTF_to_Bitmap() son parte del ejemplo y no de libfont.
Las dos primeras se llaman para crear y destruir la fuente TTF. En el caso de TTFLoadFont() el primer parámetro puede ser la ruta a un dispositivo o NULL, en cuyo caso hay que especificar la dirección en memoria de la fuente y su tamaño en bytes. La última función se encarga de capturar el bitmap que contiene el carácter ttf.
La función AddFontFromTTF() si forma parte de libfont y funciona de forma similar AddFontFromBitmapArray() , pasando texture_pointer, el primer carácter y el último a capturar y el ancho y alto al que se capturan las fuentes.
Como se puede apreciar, es posible tomar dos o más capturas a tamaño distinto para tener mas o menos detalle. Cada carácter se almacena en formato A4R4G4B4 por lo que cada pixel ocupa dos bytes y el total de una textura de fuente sería (n caracteres) * (ancho) * (alto) * 2. Ojo con poner anchos y altos raros, que esas medidas no deben estar pensadas en relación a la letra, si no a la textura donde se almacenará; y por eso simpre es mejor poner valores como 8, 16, 32, 64 .... y quitarse de líos raros .
Ahora pegamos un salto bien grande y nos vamos a utilizar la fuente, imaginaos que estamos en la función DrawScene(), después de haber borrado la pantalla y escrito un montón de cosas y ahora queremos escribir algo en pantalla con nuestras letricas tan monas .
Primero nos aseguramos de estar en el modo 2D y de que Alphatest y Alpha blending esten activos, si queremos utilizar dicha propiedad:
// change to 2D context (remember you it works with 848 x 512 as virtual coordinates) tiny3d_Project2D();
// Enable alpha Test tiny3d_AlphaTest(1, 0x10, TINY3D_ALPHA_FUNC_GEQUAL);
Luego, si tenemos varias fuentes de letra, elegimos una:
SetCurrentFont(0); // usa la fuente 0 (van de 0 a 7)
Seleccionamos el tamaño de la fuente en pantalla (no tiene por que ser el mismo tamaño que el original)
SetFontSize(12, 24); // usa letras a 12x24
Seleccionamos el color de letra y del fondo de la letra (recordad que usa RGBA y por tanto A es el de menor peso)
SetFontColor(0xffffffff, 0x0); // letra blanca y fondo trasparente
Ahora si queremos creamos dos variables x e y para seleccionar el punto donde escribir en pantalla y recordarlo:
float x= 0.0, y = 0.0;
Y ahora podemos escribir un texto sin formato:
DrawString(x, y, "Esto es un ejemplo de uso");
o con formato, como printf()
DrawFormatString(x, y, "Feliz %s %i", "Año", 2011);
Las funciones DrawString y DrawFormatString retornan la posición x actualizada. Esto puede ser usado para por ejemplo, cambiar el color a una parte del texto o cambiar la fuente en un punto dado:
SetCurrentFont(1); SetFontColor(0xffffffff, 0x0); x = DrawString(x,y, "Hello World!. My nick is ");
SetFontColor(0x00ff00ff, 0x0); SetCurrentFont(0); x = DrawString(x,y, "Hermes ");
SetCurrentFont(1); SetFontColor(0xffffffff, 0x0); x = DrawString(x,y, "and this is one sample working with\nfonts.");
Tambien admiten el "\n" pero ten en cuenta que no serás informado del cambio de posición 'Y' directamente.
Existen dos funciones para conocer la 'X' e 'Y' actual después de llamar a estas funciones:
// last X used
float GetFontX();
// last Y used
float GetFontY();
Tambien es posible especificar Z (normalmente 0.0f) mediante:
// Z used to draw the font. Usually is 0.0f
void SetFontZ(float z);
Es posible decirle a la librería que centre el texto escrito si utilizamos:
Nótese que el centrado se aplica a la función llamada y no a un texto compuesto por varias llamadas DrawString / DrawFormatString.
Si un texto se sale de la pantalla, es cortado por el RSX dado que no se comprueba límite alguno en la librería salvo que lo ajustes.
Si quieres ajustar un borde hacia la derecha y que el texto cambie de línea, puedes hacerlo con esta dos función:
void SetFontAutoNewLine(int width); // pon 0 para desactivar o el ancho que vayas a utilizar
Si el texto rebasa por abajo será cortado, pero el texto tratara de justificarse entre 0 y width-1 desplazando hacia la línea de abajo las palabras que no quepan. Puedes utilizar GetFontY() para conocer la posición a este respecto.
Por otro lado, seguro que estás pensando que vendría bien poder corregir la posición desde la izquierda... puedes hacerlo, cambiando la matriz ModelView por otra que desplace el texto.
Las fuentes .ttf son mas problemáticas, pues almacenan correcciones de carácter que hacen que el texto no se ajuste igual que en el caso de las fuentes fijas. Especial cuidado tenéis que tener con el tamaño, pues no olvidéis nunca que la pantalla "ficticia" tiene más resolución real en un modo 720P, que en 480P y por tanto, una fuente se puede ver bien a 720P y fatal a 480P, si no hemos calculado el tamaño de la fuente (o comprobado) para que se vea relativamente bien.
Solo me queda una función por comentar:
#define FONT_SINGLE_TEXTURE 0 // default method #define FONT_DOUBLE_TEXTURE 1 // enable and maps external second texture char by char #define FONT_DOUBLE_TEXTURE2 2 // enable and maps external second texture using the screen coordinates rectangle
void SetFontTextureMethod(int method);
Esto es tan nuevo, que ni siquiera está publicado : consiste en la posibilidad de utilizar una textura que se mezclará con nuestra fuente de letra para que use ese motivo o patrón. La fuente se captura en blanco intenso y no viene mal poder decorarla un poco de esta forma.
Echadle un ojo a los ejemplos que no muerden, pero ya sabéis todo lo que tenéis que saber para poder hacer un "hola mundo", después de haber dibujado el Mundo
Dibujando la escena en una superficie (textura) para aplicarla en otra
En ocasiones especiales se requiere renderizar la escena desde un punto de vista, para luego poder añadir el resultado a los objetos. Este es el caso por ejemplo, de un espejo, el cual reflejaría justo lo que ocurre en frente.
Nosotros lo que haríamos, es situar la cámara justo delante del espejo (Tiny3D no cuenta con una función específica para manejar el punto de vista de cámara, por el momento) y renderizar los objetos desde allí en una textura (excluyendo los objetos que estén detrás de dicha cámara) y luego volveríamos a filmar la escena ésta vez desde el punto de vista del observador y aplicando la nueva textura donde corresponda, de manera adecuada.
Así dicho, parece sencillo, pero la primera complicación, es conseguir que sea posible hacer eso en una textura
Afortunadamente, averigüé la forma de hacerlo y de hacerlo fácil.
Lo primero que tenemos que hacer, es reservar espacio para la textura. Esta operación es crítica debido a que no es posible renderizar en cualquier superficie y hay que cumplir una serie de condiciones.
En principio, es posible hacerlo en superficies de 16 y de 32 bits, respetando una regla de divisibilidad al menos respecto al ancho, que yo aplicaría también al alto: siendo divisibles entre 32 cubrirá tanto los modos de 16 como los de 32 bits (que en principio, admite ser entre 16). Si no se hace bien, la consola se colgará irremediablemente.
Sobre el tamaño, de dicha textura, el tamaño máximo de pantalla es 1920 x 1080. En realidad depende del tamaño del Buffer Z, que reserva 1920 x 1088 puntos para todos los casos (no es de recibo "ahorrar" memoria de texturas cuando el programa puede trabajar en cualquier resolución: lo mejor en este caso, es que gastar el máximo). Yo creo que probé 32 x 32, 256 x 256 como resoluciones de ejemplo (obviamente, eso provoca deformación del objeto pues se espera algo en plan 16:9 al ajustar la matriz de proyección y podría requerir correcciones)
El ejemplo spheres3D utiliza éste método para renderizar a 1080p independientemente de la resolución de salida.
Nótese aquí que texture_pointer es de tipo u32 * y por lo tanto, traducido a bytes, el incremento de surface_w * surface_h se traduce como surface_w * surface_h * 4 (no me preocupo de alinear por el resultado ya lo está)
Bien, ya tenemos nuestra textura reservada, así que ahora toca "conectarla" para renderizar.
Ya hemos visto la función Tiny3d_Clear(), para renderizar en textura se requiere:
// clear the buffers. Usually call it with tiny3d_Clear2(color, TINY3D_CLEAR_ALL, ... // it is used to render in one alternative surface (texture)
Con lo cual estamos en modo 2D, con los test alpha desactivados, etc. Aquí procederíamos igual que con tiny3d_Clear() solo que en este punto, no se realiza salida por pantalla: eso provocaría un problema si el buffer de vértices es mas pequeño de lo conveniente, ya que no habría luego espacio para renderizar la salida a pantalla. Así que aseguraos que iniciáis Tiny3D con un tamaño para vértices mas que suficiente.
Para salir del modo, solo hay que llamar a tiny3d_Clear(), para que configure la salida hacia la pantalla:
tiny3d_Clear(0xff000000, TINY3D_CLEAR_ALL);
Recordad SIEMPRE llamar a esta función, antes de tiny3d_Flip() y recordad que, en efecto, se trata exactamente de dos escenas distintas en el mismo frame, solo que tiny3d_Clear() establece la salida real por pantalla.
Los shaders YUV
La implementación YUV es uno de los últimos añadidos y trabaja de forma especial, teniendo en cuenta que el RSX trabaja sobre modos de color RGB.
La idea es sencilla: el pixel shader lleva a cabo las operaciones de conversión necesarias mediante una fórmula que e aplica para obtener cada componente.
YUV es un espacio de color mas próximo a como nosotros vemos, donde la Y corresponde a la intensidad de luz o brillo, mientras que la información de color propiamente dicha, está en los otros dos componentes. Se utiliza en los formatos de video y debido a sus características, es usual ver píxeles que comparten la misma UV (es decir, que sirve para comprimir la imagen)
En concreto disponemos de dos juegos de Pixels Shaders distintos: uno que se centra en una textura A8R8G8B8 pero que tratará el color como si fuera A8Y8U8V8 y otro que trabajará con 3 texturas tipo L8 (que almacenan los valores de intensidad para cada componente)
El tipo de 32 bits se incluyó por que YUV se puede codificar de muchas formas (que reciben otros nombres en función de su organizacion) y tal vez te pueda resultar mas interesante crear tu el color de esta forma, desde el bitmap original, delegando solo la transformación de color al Pixel Shader. Tambien tienes que tener en cuenta que el shader no está preparado para convertir todos los espacios de color, si no unos en concreto asociados al video (DivX, MPEG..). JPEG creo que utiliza una fórmula de conversión de color distinta.
Precisamente, pensando en reproductores DivX, MPEG, se evolucionó al Pixel Shader que utiliza 3 texturas distintas. Que yo recuerde, FFMPEG daba una salida en forma de 3 mapas de 8 bits con una proporción Y(2x2), U(1x1), V(1x1). Es decir, que una rejilla de 2x2 pixeles, compartían U y V.
En el caso del Pixel Shader Y, U, V, se conectan a las unidades de texturas 0,1 y 2, respectivamente, compartiendo la misma coordenada de textura. Ojo con el detalle del stride (o ancho de la textura en bytes): lo correcto es crear una textura mayor y copiar el mapa Y, U, V, scanline a scanline de forma que se adapte a las texturas de forma adecuada (probablemente se pueda copiar todo de forma directa a la textura sin que se produzca ningún desajuste, pero lo menciono porque hay unas reglas de alineación que si no son respetadas, obviamente, el resultado no será el correcto)
Pues bien, después de tanta chapa queda conocer como se conecta el modo YUV.
Antes se debe crear las texturas YUV (ver ejemplo YUV, aunque yo me voy a salir de ese ejemplo, para dar otro punto de vista):
A estas alturas, no creo necesario explicar donde se crea la textura y el método que usamos . Se obvia la parte en donde se copiarían los datos a la textura pero se sobrentiende que antes de usarla, se copian datos con los componentes A8Y8U8V8 necesarios (en la direccion texture[0].addr )
Después deberíamos dibujar un polígono con una sola coordenada de texturas, siendo posible especificar color (que actúa como filtro, como luego explicaré)
- No utilizar normales, ni coordenadas de doble texturas, etc
De nuevo lo de antes, no creo necesario explicar donde crear la textura. Se obvia la parte en donde se copiarían los datos a la texturas pero se sobrentiende que antes de usarla, se copian datos con los componentes Y8 , U8, V8 necesarios en sus respectivas texturas ( en la direccion texture[x].addr )
Para conectar las texturas sería:
// set Y 8 bit texture tiny3d_SetTextureWrap(0, texture[0].offset, texture[0].w, texture[0].h, texture[0].stride, TINY3D_TEX_FORMAT_L8, TEXTWRAP_CLAMP, TEXTWRAP_CLAMP, TEXTURE_LINEAR);
// set U 8 bit texture tiny3d_SetTextureWrap(1, texture[1].offset, texture[1].w, texture[1].h, texture[1].stride, TINY3D_TEX_FORMAT_L8, TEXTWRAP_CLAMP, TEXTWRAP_CLAMP, TEXTURE_LINEAR);
// set V 8 bit texture tiny3d_SetTextureWrap(2, texture[2].offset, texture[2].w, texture[2].h, texture[2].stride, TINY3D_TEX_FORMAT_L8, TEXTWRAP_CLAMP, TEXTWRAP_CLAMP, TEXTURE_LINEAR);
Y para conectar el modo YUV de 8bits, 3 texturas, se llama a esta función:
Después deberíamos dibujar un polígono con una sola coordenada de texturas, siendo posible especificar color (que actúa como filtro, como luego explicaré)
- No utilizar normales, ni coordenadas de doble texturas, etc
Para desactivar el modo YUV llamar a:
tiny3d_Disable_YUV();
[b]El asunto del color
Se puede aplicar color con las funciones tiny3d_VertexColor() y tiny3d_VertexFcolor()
Pero al ser un caso especial, el componente Alpha es un aditivo al resultado RGB final. Es decir, produce un desplazamiento al blanco, y debería ser 0 o 0.0 para no alterar el color original.
Los componentes R,G,B se multiplican con sus respectivos, por lo que deberían ser 0xff o 1.0 para no alterar el color original.
De ésta forma, se provee de un sistema de corrección de color/control de brillo para la imagen.
Y ahora si: creo que puedo dar por concluido este post-tutorial sobre Tiny3D: es posible que se añadan mas detalles mas adelante, obviamente, pero por el momento, esto es todo: ahora solo queda la descripción de las funciones en el siguiente post.
Espero que sea de vuestro agrado y sobre todo, de utilidad.
Este sitio es propiedad de Hermes Corporacion y queda reservado en espera a que su legítimo dueño, amo y señor de todas las Bestias Eolianas, tome las riendas del destino del Mundo y lo administre a su antojo
EDIT: No se que ha hecho jamonazo, pero no era necesario que borraras los mensajes: solo te pedí que lo pusieras junto al otro
Tutorial de C, tutorial de Tiny3D... genial, gracias hermes, me va a venir genial porque quiero aprender ha hacer algo con mi PS3 aunque sea mover un simple sprite por pantalla
Intentaré ir complementando la información de uno y otros post reservado.
Seguramente en el segundo post comience la explicación de las funciones propiamente dichas y en el primero termine hablando de la inicialización de la librería y un ejemplo 2D simple, paso a paso.
Eso al menos, en principio: ahora me tomo un descanso que ya está bien de tanta letra
Hermes escribió:Este sitio es propiedad de Hermes Corporacion y queda reservado en espera a que su legítimo dueño, amo y señor de todas las Bestias Eolianas, tome las riendas del destino del Mundo y lo administre a su antojo
gracias hermes por este valioso tuto,con esto tendremos mas que una consola,auque yo enpezare por el 2d y luego a por el 3d,jajajajaja cuando domine el asunto claro,gracias por estar aqui y no abandonarnos,sigue asi champion
Bueno, sabed que voy actualizando el post con correcciones de todo tipo: es difícil explicar las cosas así al vuelo y no cometer errores ortográficos e incluso comerse palabras .
Mi velocidad de escritura no la he medido. No es tan alta teniendo en cuenta que debo ordenar pensamientos, buscar referencias, corregir y además, escribo a cuatro dedos-pollas
Pero que mas da, todo se andará y al final, quedará un volcado del disco duro de mi cabeza, que me voy a quedar muy a gusto
Saludos
CoDeX
Car 3D designer
1.285 mensajes desde ene 2004 en donde cago el conde
Como programador con conocimientos de C, pero con capacidad de aplicación muuy limitada xD, solo puedo decir Este va a ser el año de PS3. Ojala tuviese el tiempo para poder "conocerla" tan bien como tú, geohot, kakarotto, posixninja, etc =)
Me parece un proyecto genial y solo queda quitarme el sombrero y como ya dijo alguno por aki. Cuando muera ps3 por favor publica un libro que te aseguro que tendrás mucho éxito. Un abrazo, grácias por seguir trabajando en la ps3 y sobretodo por compartir tus hallázgos con la gente!
Grácias!
dark_on_5
So much ʸᵃʸ
1.137 mensajes desde ago 2010 en Afueras Bilbao VIP: Si
dark_on_5 está baneado por "saltarse baneo temporal con clon"
Hermes escribió:Bueno, sabed que voy actualizando el post con correcciones de todo tipo: es difícil explicar las cosas así al vuelo y no cometer errores ortográficos e incluso comerse palabras .
Mi velocidad de escritura no la he medido. No es tan alta teniendo en cuenta que debo ordenar pensamientos, buscar referencias, corregir y además, escribo a cuatro dedos-pollas
Pero que mas da, todo se andará y al final, quedará un volcado del disco duro de mi cabeza, que me voy a quedar muy a gusto
Buen tuto!! y sobretodo un buen trabajo... publica el libro ya!! (anrtes tomate un descansito, te lo mereces) Eres grande Hermes ,ahora nuestras consolas hexaran humo del gran avance de scene.
Salu2 y no te estrese, descansa un poqkitin... tomate unas cervezas
una pregunta ,esto para que cfw es?es que me interesa hir probando un poco.tengo una ps3 con lector roto y es mi conejillo de indias,osea que si se brikea no pasa na,
Muchas gracias por tomarte el tiempo y compartir ésto con nosotros, va a ser de gran utilidad para desarrolladores sin lugar a dudas, y eso deriva en que también será de gran utilidad para todos nosotros Por cierto, una consulta medio offtopic Hermes: Estoy planteándome empezar a aprender por mi cuenta algo de programación, ya que me gusta el tema (aunque sé que puede llegar a ser un dolor de cabeza, pero dicen que si te gusta tienes mucho camino hecho). Teniendo en cuenta que no tengo ni idea de lenguajes de programación, por dónde me recomendarías empezar? alguna sugerencia? Y por último: Crees que intentando aprender por mi cuenta obtendré resultados, o bien será directamente una pérdida de tiempo ya que es mejor cursar estudios oficiales para hacer cosas "jugosas"? Gracias y espero no haberte molestado con éstas preguntas
stratoeddie escribió:Y por último: Crees que intentando aprender por mi cuenta obtendré resultados, o bien será directamente una pérdida de tiempo ya que es mejor cursar estudios oficiales para hacer cosas "jugosas"? Gracias y espero no haberte molestado con éstas preguntas
Un saludo!!
A mí la experiencia me ha dicho que por estudios oficiales no aprendes nada, mejor por tu cuenta ya que aprendes lo que quieres y para lo que quieres hacer, sin restricciones ni obligaciones
Yo te recomendaría empezar con algún tuto de C (como el de EOL con chincheta) y un compilador para tu S.O. y empezar a hacer tus primeros programitas, mucha paciencia y nunca dejarlo, al cabo de los meses ya tendrás una buena fluidez a la hora de escribir código, pero !OJO¡ Zamora no se ganó en una hora
Por cierto Hermes, solo me he leído el tutorial por encima pero vaya currro te has metido, porque yo vengo de programar para NDS y me interesa las diferencias de 600KB a 256MB de Vram xDD y demás aspectos, lo primero que tendría que hacer es instalarme el psl1ght correctamente pero es que no tengo nah de tiempo...
Todavía que da mucho por contar, así que un poco de paciencia: aún no me he metido con la librería de fuentes, con el uso de superficies para renderizar, con el tema YUV (esto tampoco es que tenga mucha historia: con el ejmplo debería bastar) y con la descripción pormenorizada de las funciones (ya estoy pensando en adquirir una pistola en el mercado negro y pegarme un tiro )
Sobre el tema de aprender C y demás, yo creo que cada persona es un mundo y depende de no solo de tus aptitud, si no de tu actitud. Yo ya conté que tuve mi primer ordenador, un spectrum con 15 años y que tardé 3 meses en pasarme al lenguaje ensamblador y eso, o te mata o te hace mas fuerte
El tema de los shaders, en principio, se puede trabajar en C. Con las tools de NVIDIA vienen ejemplos que pueden servir de base con algunos apaños y ahí estan los shaders que he implementado.
El más problemático es el Pixel Shader ya que hay cosas que no funcionan bien (no se si por error en el compilador o por características propias del RSX) y aunque puedas compilar un Shader en C y pasarlo a ASM, luego toca adaptar cosillas a mano.
Yo no soy un experto en shaders, vamos, pero he logrado lo suficiente para que la librería funcione. Es lo que digo muchas veces: no hay que tener miedo de experimentar y probar cosas hasta que salen.
Hermes escribió:Todavía que da mucho por contar, así que un poco de paciencia: aún no me he metido con la librería de fuentes, con el uso de superficies para renderizar, con el tema YUV (esto tampoco es que tenga mucha historia: con el ejmplo debería bastar) y con la descripción pormenorizada de las funciones (ya estoy pensando en adquirir una pistola en el mercado negro y pegarme un tiro )
Aún está en estado "beta" pero te he pasado lo que has hecho hasta el momento al Wiki.
Si alguien lo quiere seguir poniendo bonito --> wiki/Tiny3D
Cuando esté "chachi" sería ideal que sustituyese al primer mensaje del hilo.
Hermes escribió:Todavía que da mucho por contar, así que un poco de paciencia: aún no me he metido con la librería de fuentes, con el uso de superficies para renderizar, con el tema YUV (esto tampoco es que tenga mucha historia: con el ejmplo debería bastar) y con la descripción pormenorizada de las funciones (ya estoy pensando en adquirir una pistola en el mercado negro y pegarme un tiro )
Sobre el tema de aprender C y demás, yo creo que cada persona es un mundo y depende de no solo de tus aptitud, si no de tu actitud. Yo ya conté que tuve mi primer ordenador, un spectrum con 15 años y que tardé 3 meses en pasarme al lenguaje ensamblador y eso, o te mata o te hace mas fuerte
El tema de los shaders, en principio, se puede trabajar en C. Con las tools de NVIDIA vienen ejemplos que pueden servir de base con algunos apaños y ahí estan los shaders que he implementado.
El más problemático es el Pixel Shader ya que hay cosas que no funcionan bien (no se si por error en el compilador o por características propias del RSX) y aunque puedas compilar un Shader en C y pasarlo a ASM, luego toca adaptar cosillas a mano.
Yo no soy un experto en shaders, vamos, pero he logrado lo suficiente para que la librería funcione. Es lo que digo muchas veces: no hay que tener miedo de experimentar y probar cosas hasta que salen.
Saludos
Ya te lo voy poniendo en completados en mi hilo salu2
No suelo escribir mucho en el foro, pero déjame felicitarte por el empeño que le pones a todo esto. Te has llevado todo mi respeto con tu arduo trabajo y colaboración con la Scene. Enhorabuena Hermes... gracias.
Bueno, ya queda menos para acabar la primera parte (arf, arf ).
Se ha incluido la descripción de como funciona libfont: solo queda hablar algo del método que se usa para renderizar la escena en una textura y luego poder pegarla a otra cosa y podremos pasar a la segunda parte, que es mas descriptiva de lo que hace cada función. Eso será si el teclado me deja, que parece que el cable falla y a veces me encuentro tecleando "al aire".
Hermes escribió:Bueno, ya queda menos para acabar la primera parte (arf, arf ).
Se ha incluido la descripción de como funciona libfont: solo queda hablar algo del método que se usa para renderizar la escena en una textura y luego poder pegarla a otra cosa y podremos pasar a la segunda parte, que es mas descriptiva de lo que hace cada función. Eso será si el teclado me deja, que parece que el cable falla y a veces me encuentro tecleando "al aire".
Saludos.
Currada impresionante tío, sigue asi de verdad, gracias por compratir con la comunidad tus conocimientos
Hermes escribió:[...]- Recientemente hay soporte para las transformación de color YUV a RGB mediante 4 pixels shaders dedicados al efecto. Por lo que la librería resulta de utilidad para implementar Reproductores de vídeo Multimedia [...]
¿ puedes explicar algo más esta característica ? He mirado por encima el ejemplo yuv que hay en el git, pero no termino de verlo claro ¿¿ pasas los datos RGB del PNG que cargas a YUV para luego poder probar la transformación YUV a RGB ?? Y ya de paso, ¿ como funciona exactamente el pixel shader para hacer esta transformación ? Viendo nv_shader.h la verdad es que poco se puede sacar , ya que son todo valores hexadecimales ... Por último, ¿ qué ventajas/inconvenientes tiene hacer la transformación YUV2RGB en el RSX frente a hacerlo en las SPUs ? Y, por curiosidad, después del trabajazo que te estás pegando ... ¿ en el futuro tienes planteado meterte a optimizar Tiny3D con las SPUs o con las operaciones vectoriales del núcleo PPC del Cell ? Por lo que veo en matrix.c por ahora todo se hace a lo "clásico" y lo "divertido" de programar en PS3 , como dices en la introducción del tutorial, sería eso.
Hermes escribió:[...]- Recientemente hay soporte para las transformación de color YUV a RGB mediante 4 pixels shaders dedicados al efecto. Por lo que la librería resulta de utilidad para implementar Reproductores de vídeo Multimedia [...]
¿ puedes explicar algo más esta característica ? He mirado por encima el ejemplo yuv que hay en el git, pero no termino de verlo claro ¿¿ pasas los datos RGB del PNG que cargas a YUV para luego poder probar la transformación YUV a RGB ?? Y ya de paso, ¿ como funciona exactamente el pixel shader para hacer esta transformación ? Viendo nv_shader.h la verdad es que poco se puede sacar , ya que son todo valores hexadecimales ... Por último, ¿ qué ventajas/inconvenientes tiene hacer la transformación YUV2RGB en el RSX frente a hacerlo en las SPUs ? Y, por curiosidad, después del trabajazo que te estás pegando ... ¿ en el futuro tienes planteado meterte a optimizar Tiny3D con las SPUs o con las operaciones vectoriales del núcleo PPC del Cell ? Por lo que veo en matrix.c por ahora todo se hace a lo "clásico" y lo "divertido" de programar en PS3 , como dices en la introducción del tutorial, sería eso.
Para lo de YUV en efecto, convierto el PNG desde RGB a YUV, en dos formatos para hacer el test.
En el primero, la textura es de 32 bits y en RGB sería A8R8G8B8 pero se interpreta como A8Y8U8V8.
El segundo es quizá mas adecuado para reproductores pues se utilizan 3 texturas de 8 bits, conteniendo cada una Y, U ,V. Teniendo en cuenta que comparten las mismas coordenadas, el tamaño del bitmap de cada campo determina el reparto, por así decir.
Por ejemplo, el formato Y(2x2), U(1x1), V(1x1) (DivX, etc) se hace trabajando exactamente con las mismas coordenadas de textura, de forma "automática"
Sobre como funciona, pues es muy sencillo: hay unas relaciones matemáticas para obtener RGB y el shader se encarga de ello. El código fuente del shader lo puedes ver mirando en "tiny3d / tools / toolshaderf / samples"
Ahí puedes ver el nivel ASM de los shaders, pero lo entenderás mejor si entras en la carpeta "cg" que contiene los shaders YUV en alto nivel y una utilidad para convertir (eso si, algun shader requiere leves retoques en su ensamblador para evitar que saque los componentes R,G, B uno a uno al registro)
Sobre optimizar con SPU y operaciones vectoriales del PPC, la verdad es que no he mirado nada absolutamente: el SPU no es cosa mía y no tiene nada que ver con Tiny3D. Y del PPC, desconozco como implementarlas por que no lo he mirado nada. Pero digo yo que no soy el único desarrollador de la Tierra y si alguien las implementa, gustosamente se añadirán (la verdad es que yo no necesito ni la mitad )
LuzbelFullHD escribió:Gracias por la respuesta, me ha aclarado bastante.
En cuanto a las SPU y optimzaciones con operaciones vectoriales ¿ hay algún equivalente al usuario mavy de psxreality en EOL ?
He añadido notas al post principal (se supone que la primera parte, está acaba a falta de corregir alguna tonteria que haya puesto )
Lo de las unidades vectoriales, no debe ser complicado, pero es que no he mirado absolutamente nada (si ni siquiera tenemos función para fijar la cámara ) y en cuando los SPU, pues tampoco es que los necesitemos (el RSX cruje mas números por segundo, de calle: de hecho, por cada vértice, aplicas dos matrices de posicion y muchas mas cosas cuando iluminas)
Sobre mavy, lo único que se es que el otro día deje un post saludando en psxreality y ya está