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 );
}