Created
April 2, 2024 21:46
-
-
Save partybusiness/9ee3f24f8d6aff0877e8103674a388da to your computer and use it in GitHub Desktop.
Godot Wang Tile Shaders
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
shader_type spatial; | |
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_lambert, specular_schlick_ggx; | |
uniform sampler2D Tile_Texture:source_color; | |
//used for random values on tile | |
vec2 random(float x, float y) { | |
vec2 co = round(vec2(x,y)); | |
return vec2( | |
fract(sin(dot(co.xy, vec2(3.3853, 89.1866))) * 263724.4767), | |
fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453) | |
); | |
} | |
bool xor(bool a, bool b) { | |
return ((a && !b) || (!a && b)); | |
} | |
void fragment() { | |
const int numTiles = 8; //number of tiles displayed along 0..1 uv range | |
//round to nearest pixel for tile UV | |
vec2 uv = floor(UV*float(numTiles)); | |
//get remainder UV for that pixel for the tile uv | |
vec2 tileUV = UV*float(numTiles) - uv; | |
//bottom and left of the tile is decided by sampleLocation | |
//right and top use offset samples to match those adjacent | |
vec2 randomval = random(uv.x,uv.y).gr; | |
bool sampleLocationx = randomval.x > 0.5; | |
bool sampleLocationy = randomval.y > 0.5; | |
bool sampleLocationRight = random(uv.x+1.0,uv.y).g > 0.5; | |
bool sampleLocationAbove = random(uv.x,uv.y+1.0).r > 0.5; | |
uv = (uv) / float(numTiles); | |
//index math is a little weird but it means all adjacent tiles in source tilemap are connected, which helps mip maps look good | |
int index = 0; | |
index += 1 * (xor(sampleLocationRight, sampleLocationx)?1:0); | |
index += 2 * (sampleLocationx?1:0); | |
index += 4 * (xor(sampleLocationAbove, !sampleLocationy)?1:0); | |
index += 8 * (sampleLocationAbove?1:0); | |
vec2 offset = vec2(float(index%4),float(index/4))/4.0; | |
vec2 scaling = UV * float(numTiles); //use original UVs for LOD calculation | |
float lod = max(0.0, log2(max(length(dFdx(scaling)), length(dFdy(scaling)) ))); | |
vec4 col = textureLod(Tile_Texture, (tileUV / 4.0)+offset, lod); | |
ALBEDO = vec3(col.rgb); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
shader_type spatial; | |
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_lambert, specular_schlick_ggx; | |
uniform sampler2D Tile_Texture:source_color; //texture of wang tiles displayed in final material | |
uniform sampler2D Noise_Texture:filter_nearest; //texture with random RG values for randomizing Wang tiles | |
//needed an xor function because Godot shaders don't seem to have that built in? | |
bool xor(bool a, bool b) { | |
return ((a && !b) || (!a && b)); | |
} | |
void fragment() { | |
const int numTiles = 8; //number of tiles displayed along 0..1 uv range | |
const int noiseSize = 32; //number of pixels in the noise texture | |
//round to nearest pixel for tile UV | |
vec2 uv = floor(UV*float(numTiles)); | |
//get remainder UV for that pixel for the tile uv | |
vec2 tileUV = UV*float(numTiles) - uv; | |
float pixelOffset = 1. / float(noiseSize); | |
uv = uv / float(numTiles*noiseSize) * float(numTiles)+ pixelOffset/2.0; // make uv select pixel on noise texture | |
//bottom and left of the tile is decided by sampleLocation | |
//right and top use offset samples to match those adjacent | |
vec2 sampledVal = texture(Noise_Texture, uv).xy; | |
bool sampleLocationx = sampledVal.x > 0.5; | |
bool sampleLocationy = sampledVal.y > 0.5; | |
bool sampleLocationRight = texture(Noise_Texture, (uv+vec2(pixelOffset,0.0))).x > 0.5; | |
bool sampleLocationAbove = texture(Noise_Texture, (uv+vec2(0.0,pixelOffset))).y > 0.5; | |
//index math is a little weird but it means all adjacent tiles in source tilemap are connected, which helps mip maps look good | |
int index = 0; | |
index += 1 * (xor(sampleLocationRight, sampleLocationx)?1:0); | |
index += 2 * (sampleLocationx?1:0); | |
index += 4 * (xor(sampleLocationy,!sampleLocationAbove)?1:0); | |
index += 8 * (sampleLocationAbove?1:0); | |
vec2 offset = vec2(float(index%4),float(index/4))/4.0; //offset to current Wang tile | |
vec2 scaling = UV * float(numTiles); //use original UVs for LOD calculation | |
float lod = max(0.0, log2(max(length(dFdx(scaling)), length(dFdy(scaling)) ))); | |
vec4 col = textureLod(Tile_Texture, (tileUV / 4.0)+offset, lod); | |
ALBEDO = col.rgb; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[gd_resource type="VisualShader" load_steps=6 format=3 uid="uid://fno4pw1ppmbn"] | |
[ext_resource type="Script" path="res://WangTiles/wang_tile_shader_node.gd" id="1_dbsbf"] | |
[sub_resource type="VisualShaderNodeCustom" id="VisualShaderNodeCustom_5hy2l"] | |
initialized = true | |
script = ExtResource("1_dbsbf") | |
[sub_resource type="VisualShaderNodeTexture2DParameter" id="VisualShaderNodeTexture2DParameter_dxlme"] | |
parameter_name = "Tile_Texture" | |
texture_type = 1 | |
[sub_resource type="VisualShaderNodeInput" id="VisualShaderNodeInput_2cpev"] | |
input_name = "uv" | |
[sub_resource type="VisualShaderNodeIntConstant" id="VisualShaderNodeIntConstant_mftoa"] | |
constant = 13 | |
[resource] | |
code = "shader_type spatial; | |
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_lambert, specular_schlick_ggx; | |
uniform sampler2D Tile_Texture : source_color; | |
// WangTile | |
//random function for wang tiles | |
vec2 random_wang(float x, float y) { | |
vec2 co = round(vec2(y,x)); | |
return vec2( | |
fract(sin(dot(co.yx, vec2(3.3853, 89.1866))) * 263724.4767), | |
fract(sin(dot(co.yx, vec2(12.9898, 78.233))) * 43758.5453) | |
); | |
} | |
bool xor(bool a, bool b) { | |
return ((a && !b) || (!a && b)); | |
} | |
void fragment() { | |
// Input:4 | |
vec2 n_out4p0 = UV; | |
// IntConstant:5 | |
int n_out5p0 = 13; | |
vec4 n_out2p0; | |
// WangTile:2 | |
{ | |
//round to nearest pixel for tile UV | |
vec2 uv = floor(n_out4p0*float(n_out5p0)); | |
float u = (uv.x); | |
float v = (uv.y); | |
//get remainder UV for that pixel for the tile uv | |
vec2 tileUV = n_out4p0*float(n_out5p0) - uv; | |
//bottom and left of the tile is decided by sampleLocation | |
//right and top use offset samples to match those adjacent | |
vec2 randomval = random_wang(u,v).xy; | |
bool sampleLocationx = randomval.y > 0.5; | |
bool sampleLocationy = randomval.x > 0.5; | |
bool sampleLocationRight = random_wang(u+1.0,v).y > 0.5; | |
bool sampleLocationAbove = random_wang(u,v+1.0).x > 0.5; | |
uv = (uv) / float(n_out5p0); | |
//index math is a little weird but it means all adjacent tiles in source tilemap are connected, which helps mip maps look good | |
int index = 0; | |
index += 1 * (xor(sampleLocationRight, sampleLocationx)?1:0); | |
index += 2 * (sampleLocationx?1:0); | |
index += 4 * (xor(sampleLocationAbove, !sampleLocationy)?1:0); | |
index += 8 * (sampleLocationAbove?1:0); | |
vec2 offset = vec2(float(index%4),float(index/4))/4.0; | |
vec2 scaling =n_out4p0 * float(n_out5p0); //use original UVs for LOD calculation | |
float lod = max(0., log2(max(length(dFdx(scaling)), length(dFdy(scaling)) ))); | |
vec4 col = textureLod(Tile_Texture, (tileUV / 4.0)+offset, lod); //sample tile texture | |
n_out2p0 = col; | |
} | |
// Output:0 | |
ALBEDO = vec3(n_out2p0.xyz); | |
} | |
" | |
graph_offset = Vector2(-988.439, -205.786) | |
nodes/fragment/2/node = SubResource("VisualShaderNodeCustom_5hy2l") | |
nodes/fragment/2/position = Vector2(-300, 220) | |
nodes/fragment/3/node = SubResource("VisualShaderNodeTexture2DParameter_dxlme") | |
nodes/fragment/3/position = Vector2(-1600, -80) | |
nodes/fragment/4/node = SubResource("VisualShaderNodeInput_2cpev") | |
nodes/fragment/4/position = Vector2(-960, 120) | |
nodes/fragment/5/node = SubResource("VisualShaderNodeIntConstant_mftoa") | |
nodes/fragment/5/position = Vector2(-760, 420) | |
nodes/fragment/connections = PackedInt32Array(2, 0, 0, 0, 3, 0, 2, 1, 4, 0, 2, 0, 5, 0, 2, 2) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@tool | |
extends VisualShaderNodeCustom | |
class_name WangTileShaderNode | |
func _get_name(): | |
return "WangTile" | |
func _get_category(): | |
return "MyShaderNodes" | |
func _get_description(): | |
return "Samples random Wang Tiles" | |
func _init(): | |
pass | |
func _get_return_icon_type(): | |
return VisualShaderNode.PORT_TYPE_VECTOR_4D | |
func _get_input_port_count(): | |
return 3 | |
func _get_input_port_name(port): | |
match port: | |
0: | |
return "UV" | |
1: | |
return "TileTexture" | |
2: | |
return "TextureSize" | |
return "useless" | |
func _get_input_port_type(port): | |
match port: | |
0: | |
return VisualShaderNode.PORT_TYPE_VECTOR_2D | |
1: | |
return VisualShaderNode.PORT_TYPE_SAMPLER | |
2: | |
return VisualShaderNode.PORT_TYPE_SCALAR_INT | |
return VisualShaderNode.PORT_TYPE_SCALAR | |
func _get_output_port_count(): | |
return 1 | |
func _get_output_port_name(port): | |
return "Colour" | |
func _get_output_port_type(port): | |
return VisualShaderNode.PORT_TYPE_VECTOR_4D | |
func _get_global_code(mode): | |
return """ | |
//random function for wang tiles | |
vec2 random_wang(float x, float y) { | |
vec2 co = round(vec2(y,x)); | |
return vec2( | |
fract(sin(dot(co.yx, vec2(3.3853, 89.1866))) * 263724.4767), | |
fract(sin(dot(co.yx, vec2(12.9898, 78.233))) * 43758.5453) | |
); | |
} | |
bool xor(bool a, bool b) { | |
return ((a && !b) || (!a && b)); | |
} | |
""" | |
func _get_code(input_vars, output_vars, mode, type): | |
return "" \ | |
+ " //round to nearest pixel for tile UV\n" \ | |
+ " vec2 uv = floor(%s*float(%s));\n"%[input_vars[0],input_vars[2]] \ | |
+ " float u = (uv.x);\n"\ | |
+ " float v = (uv.y);\n"\ | |
+ " //get remainder UV for that pixel for the tile uv\n" \ | |
+ " vec2 tileUV = %s*float(%s) - uv;\n"%[input_vars[0],input_vars[2]] \ | |
+ " \n" \ | |
+ " //bottom and left of the tile is decided by sampleLocation\n" \ | |
+ " //right and top use offset samples to match those adjacent\n" \ | |
+ " vec2 randomval = random_wang(u,v).xy;\n" \ | |
+ " bool sampleLocationx = randomval.y > 0.5;\n" \ | |
+ " bool sampleLocationy = randomval.x > 0.5;\n" \ | |
+ " bool sampleLocationRight = random_wang(u+1.0,v).y > 0.5;\n" \ | |
+ " bool sampleLocationAbove = random_wang(u,v+1.0).x > 0.5;\n" \ | |
+ " uv = (uv) / float(%s);\n"%[input_vars[2]] \ | |
+ " //index math is a little weird but it means all adjacent tiles in source tilemap are connected, which helps mip maps look good\n" \ | |
+ " int index = 0;\n" \ | |
+ " index += 1 * (xor(sampleLocationRight, sampleLocationx)?1:0);\n" \ | |
+ " index += 2 * (sampleLocationx?1:0);\n" \ | |
+ " index += 4 * (xor(sampleLocationAbove, !sampleLocationy)?1:0);\n" \ | |
+ " index += 8 * (sampleLocationAbove?1:0);\n" \ | |
+ " vec2 offset = vec2(float(index%4),float(index/4))/4.0;\n" \ | |
+ " vec2 scaling =%s * float(%s); //use original UVs for LOD calculation \n"%[input_vars[0], input_vars[2]] \ | |
+ " float lod = max(0., log2(max(length(dFdx(scaling)), length(dFdy(scaling)) )));\n" \ | |
+ " vec4 col = textureLod(%s, (tileUV / 4.0)+offset, lod); //sample tile texture \n "%[input_vars[1]] \ | |
+ " %s = col;\n"%[output_vars[0]] | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A picture of it in action over here, plus the source tile I was using for that:
https://mastodon.gamedev.place/@flinflonimation/112203833709650573
It assumes your tiles are in a 4x4 texture, with two types of edges, I'll call them A and B. Each tile matches the tiles adjacent to it, so for example if a tile has a B edge on the bottom then the tile below it has to have a B edge on the top.
They're laid out like:
Left-most and right-most column have A on the right edge. Middle two columns have B on the right edge.
Two right-side columns have B on the left edge, two left-side columns have A on the left edge.
Top-most and bottom-most rows have A on top edge. Middle two rows have B on the top edge.
Top two rows have B on the bottom edge, bottom two rows have A on the top edge.
In practice, A and B don't need to mean the same thing horizontally and vertically. Any tile with B on the right edge should be able to match a B on the left edge, but it doesn't matter if they can match a B on the top edge, for example, because that will never happen.