Hexagonal tilemap, simple version

This shader draws an hexagonal tilemap. It takes as input:

– an array of textures, one for each “biome”. Textures should be seamless to avoid visual artifacts, and have the “repeat” flag (Flag = 7 is the most likely choice here)

– a texture containing the “biome” of each tile, encoded on the red chanel. This texture should have Flag 0  (No mipmaps, no filter, no repeat. ) 

To use it, attach it to a sprite with a dumy texture scaled to the size of the world.

This simple version has no blending between neighboring tiles, and the hexagons are thus apparent.

For a more advanced version including blending, look there: https://godotshaders.com/shader/hexagonal-tilemap-with-blending/

See also:

– Hexagonal primitives where adapted from : https://www.redblobgames.com/grids/hexagons/

– this shader is inspired from https://godotshaders.com/shader/rimworld-style-tilemap-shader-with-tutorial-video/

– how I use this shader (well … the advanced version) in my own game: https://www.gobsandgods.com/blog/

CC0, but feel free to credit me!

Shader code
shader_type canvas_item;

///  ****  DATA to pass to this shader  **** ///
// array containing the textures to paint the different types of tiles (eg plain, water,...)
// These textures must have the REPEAT Flag. (Mimpmaps and filter are also good ideas) 
// They should be seamless to avoid visual artifacts.
uniform sampler2DArray textureAtlas;

// this texture is used as a 2D array containing the tile type for each tile (in the red chanel)
// tile coordinates are encoded using "offseted" coordinates, ie in the same way as in Godot.Tilemap 
// texture size is thus equal to the world grid size.
// It * must *  have flag 0 (No mimmaps, No filter, No repeat.) 
uniform sampler2D mapData;

// size of the textures in the atlas
uniform float atlasSizeInPixels = 512;

// size of an hexa in pixels
uniform float CellSize = 32;
// size of the hexa grid, +1  (eg (101,101) for a 100x100 grid) 
uniform vec2 GridSizePlusOne = vec2(101,101);
// size of the world in pixels: set to CellSize * (1.5f , Sqrt(3)) * GridSize
uniform vec2 WorldPixelSize; 

// for hexa geometry
const float sqrt3 = 1.73205080757;

//// **** Hexa grid primitives.  **** ////
// these functions were directly adapted from:  https://www.redblobgames.com/grids/hexagons

// Find to which hexagon belongs a points.
// input :  "axial coordinates" of a point
// output: axial coordinates of the hexa 
ivec2 axial_round_(vec2 frac)
	float s0 = -frac.x - frac.y;
    float q = round(frac.x);
    float r = round(frac.y);
    float s = round(s0);

    float q_diff = abs(q - frac.x);
    float r_diff = abs(r - frac.y);
    float s_diff = abs(s - s0);

    if (q_diff > r_diff && q_diff > s_diff)
        q = -r-s;		
    else if (r_diff > s_diff)
        r = -q-s;
    return ivec2( int( q  ), int( r ));

// Find to which hexagon belongs a points.
// input :  "pixel coordinates" of a point
// output: axial coordinates of the hexa 
ivec2 GetTileInAxialCoo(vec2 xy)
	float x = xy.x;
    float q = ( 2./3.0 * x  );
    float r = (-1./3.0 * x  +  sqrt3/3.0 * xy.y);
    return axial_round_(vec2(q, r));

// Computes the position in pixel coo of an hexa
// input: hexa in axial coordinates
// output: position of the pixel (in the normal 'othonormal' coordinates) 
vec2 TileAxialCooCenter( ivec2 hex )
	float x = float(hex.x);
	return vec2( x * 3.0 / 2.0, (x / 2.0 + float(hex.y)) * sqrt3);	

// Convert axial coo on an hexa to "offset coordinates" we used to store data
ivec2 AxialToTile(ivec2 hex) // "axial_to_oddq" on redblobgames
    int col = hex.x;
    int row = hex.y + ((hex.x  + (hex.x %2 ) ) / 2 );    
    return ivec2(col, row);	

// Compute "offset coordinates" of the hexa containing input point.
ivec2 GetTile( vec2 xy )
	return AxialToTile( GetTileInAxialCoo(xy));

//// ***  Reading texture data for one hexagon  *** ////
// for each hexagon, the texture id is stored in mapdata, in the red chanel.
// Fetch Texture Id from hexa coordinates (in "offset coordinates") 
int getTileId(ivec2 tile) 
	vec4 color = texture(mapData,  vec2(tile) / GridSizePlusOne, 0);
	return int(color.r * 255.);

// Fetch color from at point xy for specified texture id
// input:  xy :  coordinates of the pixel in the world
// tileId : id of the texture to read
vec4 getColorForCurrentPixel(vec2 xy, int tileId) 
	vec2 atlastxy =  xy / atlasSizeInPixels ;
	return texture( textureAtlas,  vec3( atlastxy.x , atlastxy.y , float(tileId) )  );

void fragment() 
	// pixel position scaled to hexagon size 
	vec2 xy = UV * WorldPixelSize / CellSize ;

	// Retrieve hexa containing current pixel
	ivec2 currentHexa = GetTile(xy); 
	int tileId = getTileId(currentHexa);
    // blend these colors according to weighting
	COLOR = getColorForCurrentPixel( UV * WorldPixelSize , tileId );
2d, hexagons, tilemap
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from alexandre.gilotte

Hexagonal tilemap with blending

Related shaders

Hexagonal tilemap with blending

Hexagonal tiling + cog wheels

Tilemap Wind shader

Notify of

Inline Feedbacks
View all comments