Category Archives: game development

Scroll Parallax y Tile Maps con Fragment Shaders (spanish)

Tiles Maps y Scroll Parallax: los métodos mas usados en el desarrollo de juegos 2d, sobre todo los retro style que buscan emular la epoca de las consolas de 8/16 bits.

Existen varias alternativas para “renderear” tile maps, generalmente se pinta un quad por cada Tile o se genera un quad por cada objeto de fondo.

Para dar el efecto de scroll parallax algunos métodos que he visto, sobre todo en algunos “engines” es colocar los quads de cada objeto a diferentes niveles de profundidad y mover la cámara para hacer el scroll, o usar texturas gigantes (lo cual limita el tamaño del mapa) y hacer offset de los UV.

Screen Shot 2015-03-31 at 4.32.36 PMnobacks

Si tomamos el ejemplo de “renderear” un Tile Map de 256×256, implica generar 256×256 quads o un mesh, eso da 65,536 quads o  65,792 vertices (si se usa un mesh) que se mandan al GPU y si suponemos que cada vértice requiere UV ( 8 bytes), Posición (12 bytes), color ( 4 bytes) y normales (12 bytes), da un total aproximado de 3MB tan solo de los puros vertices e indices y aun falta considerar el tamaño de las texturas de los tiles.

Lo anterior es aceptable incluso para un dispositivo móvil, pero si además queremos múltiples planos para scroll parallax (unos 4) ,ahí ya se van 12 MB + lo que se ocupe de texturas, eso ya no es muy optimo que digamos para un dispositivo móvil, memoria y ancho de banda (del GPU) que bien se pueden utilizar para aspectos mas importantes del juego.

Existe un método mas eficiente que utiliza solo 2 texturas y 1 quad (si, solo un quad) por cada nivel de scroll y emular algo así como lo que se hacia el SNES de soportar múltiple planos.

Primeramente ocupamos la clásica textura con los Tiles (“Tile Texture”) y el Tile Map (Tiled es una excelente herramienta para generar Tile Maps).

sp1

Cada valor dentro del TileMap contiene el TileId que representa el número de Tile (dentro del TileTexture) que se debe pintar. En el demo el TileTexture es de 1024×1024 (contiene tiles de 64×64 pixeles), y el TileMap para cada plano es 32×32 tiles.

sp2

El truco es usar el Tile Map generado por Tiled en forma de textura (“TileMap Texture”) y con esto delegar la parte de pintar los Tiles sobre un plano al Fragment Shader.

sp3

Generamos el plano (quad de 4 vértices, en el que pintaremos el Tile Map ) y se lo pasamos al GPU, debemos definir también cual es la “resolución virtual” del quad, en el demo se asume el quad tiene una resolución de 1024×768 , por lo tanto en ese quad caben 16×12 tiles (cada uno de 64×64 pixeles).

El quad además tiene las UV normalizadas con origen [0, 0] y destino [1, 1] , durante la etapa de rendering de los Fragmentos (pixeles) el Fragment Shader recibe la información intermedia de estos valores que corresponden al pixel que se esta procesando dentro del quad.

Para hacer el efecto de scroll dentro del shader ocupamos además el parámetro “Scroll Offset” , que contiene el desfasamiento X,Y a partir de cual comenzar a pintar el Tile Map.

Con esta información: el “Scroll Offset”, “UV” , “TileMap Texture”, y “Tiles Texture” podemos proceder a pintar el plano en el fragment Shader (como se solía hacer en software antes de la llegada de los GPUs, la ventaja es que este código corre en el GPU que utiliza los fragment units en paralelo):

sp4

Los parámetros UV nos indican el fragmento dentro del quad que estamos pintando, recordemos que el quad tiene una resolución virtual de 1024×768, así que lo primero que ocupamos es saber la posición virtual del pixel.

  • PosicionPIxel = UV x [1024,768] + ScrollOffset .XY

Con este valor, podemos obtener la posición dentro del TileMap en que nos encontramos, cada tile mide 64×64 y el TileMap mide 32×32, por lo tanto:

  • PosicionTile.UV = (PosicionPixel.XY / 64) / 32

Y con esta posición podemos localizar el valor del TileId en la textura TileMap:

  • TileId = TileMap( PosicionTile.UV)

Ahora que tenemos el TileId podemos localizar el valor del pixel correspondiente dentro del Tile Texture:

  • TilesCoord .U = ((TileId mod 16) * 64 + PosicionPixel.X mod 64) / 1024
  • TilesCoord .V = ((TileId /   16) * 64 + PosicionPixel.Y mod 64) / 1024
  • ValorPixel = TileTexture( TilesCoord.UV)

(En el demo anexo se puede encontrar el código del Fragment shader.)

Para el efecto de Scroll Parallax, es solo cuestión de mandar al GPU tantos Planos como queramos, cada uno con su correspondiente TileTexture, TileMap y valores para el Offset .

sp5

La ventaja de este método es que es posible pintar N planos en un solo “Draw call” siempre que los planos compartan el mismo set de Tiles (Tile Texture) y se tenga una textura con los TileMaps para cada plano (una especie de TileTexture de Tile Maps).

sp6

Además se pueden programar efectos de deformación, pixeleado, ondulado, etc. , todo en el fragment shader y generar efectos al estilo Modo 7 del SNES.

Demo:

Link del demo con 8 niveles de scroll parallax: ParallaxScrollDemoWin32a.zip

Referencias:

Tilemap Tutorial: http://www.the2dgame.com/index?page=articles&ref=ART4

Fast tilemap shader: http://connorhollis.com/fast-tilemap-shader/

Advertisements
Tagged , , , ,