Shader TileMap Custom Data Layers

Godot Version

4.4

Question

I have made a godot shader that is affecting the water tiles in my tileset. Applying the shader only to those tiles. This is working great but I am wanting to apply a gradient of color via this shader so that the further away a water tile is from a ground tile the darker it will become.

However, in the visual-shader I have been unable to find a way to grab this information or any other way to let the shader know how deep the water should be essentially.

Any help to apply a shader gradient dynamically would be greatly appreciated.

You’ll probably want to pre-bake that info into your geometry; maybe a per-vertex distance-to-land value. The shader can then chew on that.

How would I go about baking that into the geometry? This is a isometric landscape with everything on one tilemap. Both the water and the individual islands. I am just applying the shader material onto the water tiles and not on the ground tiles.

Hmm. If you can pull in the tile position of the tile being rasterized, you could presumably also generate (at level load, or before…) a signed distance field texture at a 1:1 tile to texel scale. Each texel would be a signed distance to shore, with land being negative values and water being positive. You could then reference that in your shader.

I have been trying to do this with no success at grabbing the correct tiles.

I have a tile map layer with both the water and ground tiles on it. The ground tiles do not have a shader or material but I need to access their world position with my water shader. So I can make the water darker as it get’s further away from land. I’m using the visual shader editor but this is the code it generates to work.

render_mode blend_mix;


// Varyings
varying vec2 var_WorldPos;

uniform vec4 WaterColour : source_color;
uniform float CausticTextureScale;
uniform float CausticMovementScale;
uniform float CausticMovementSpeed;
uniform float CausticMovementStrength;
uniform sampler2D CausticTexture : source_color, filter_nearest, repeat_enable;
uniform vec4 CausticColour : source_color;
uniform sampler2D CausticHighlightTexture : source_color, filter_nearest, repeat_enable;
uniform vec4 CausticHighlightColour : source_color;
uniform float NoiseScale;
uniform float NoiseModulateSpeed;
uniform float NoiseStrength;
uniform float SpecularNoiseMovementStrength;
uniform float SpecularSpeed;
uniform float SpecularScale1;
uniform float SpecularScale2;
uniform float SpecularThreshold;
uniform vec4 SpecularColour : source_color;
uniform vec4 FoamColour : source_color;
uniform float WaveSpeed = 5.0;
uniform float FoamNoiseScale = 0.0;
uniform sampler2D FoamNoiseTexture : source_color, filter_nearest, repeat_enable;
uniform float FoamNoiseAmount = 1.0;
uniform float FoamFrequency = 30.0;
uniform float FoamQuantizeAmount = 4.5;


// PerlinNoise3D

		vec3 mod289_3(vec3 x) {
			return x - floor(x * (1.0 / 289.0)) * 289.0;
		}

		vec4 mod289_4(vec4 x) {
			return x - floor(x * (1.0 / 289.0)) * 289.0;
		}

		vec4 permute(vec4 x) {
			return mod289_4(((x * 34.0) + 1.0) * x);
		}

		vec4 taylorInvSqrt(vec4 r) {
			return 1.79284291400159 - 0.85373472095314 * r;
		}

		vec3 fade(vec3 t) {
			return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
		}

		// Classic Perlin noise.
		float cnoise(vec3 P) {
			vec3 Pi0 = floor(P); // Integer part for indexing.
			vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1.
			Pi0 = mod289_3(Pi0);
			Pi1 = mod289_3(Pi1);
			vec3 Pf0 = fract(P); // Fractional part for interpolation.
			vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0.
			vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
			vec4 iy = vec4(Pi0.yy, Pi1.yy);
			vec4 iz0 = vec4(Pi0.z);
			vec4 iz1 = vec4(Pi1.z);

			vec4 ixy = permute(permute(ix) + iy);
			vec4 ixy0 = permute(ixy + iz0);
			vec4 ixy1 = permute(ixy + iz1);

			vec4 gx0 = ixy0 * (1.0 / 7.0);
			vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
			gx0 = fract(gx0);
			vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
			vec4 sz0 = step(gz0, vec4(0.0));
			gx0 -= sz0 * (step(0.0, gx0) - 0.5);
			gy0 -= sz0 * (step(0.0, gy0) - 0.5);

			vec4 gx1 = ixy1 * (1.0 / 7.0);
			vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
			gx1 = fract(gx1);
			vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
			vec4 sz1 = step(gz1, vec4(0.0));
			gx1 -= sz1 * (step(0.0, gx1) - 0.5);
			gy1 -= sz1 * (step(0.0, gy1) - 0.5);

			vec3 g000 = vec3(gx0.x, gy0.x, gz0.x);
			vec3 g100 = vec3(gx0.y, gy0.y, gz0.y);
			vec3 g010 = vec3(gx0.z, gy0.z, gz0.z);
			vec3 g110 = vec3(gx0.w, gy0.w, gz0.w);
			vec3 g001 = vec3(gx1.x, gy1.x, gz1.x);
			vec3 g101 = vec3(gx1.y, gy1.y, gz1.y);
			vec3 g011 = vec3(gx1.z, gy1.z, gz1.z);
			vec3 g111 = vec3(gx1.w, gy1.w, gz1.w);

			vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
			g000 *= norm0.x;
			g010 *= norm0.y;
			g100 *= norm0.z;
			g110 *= norm0.w;
			vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
			g001 *= norm1.x;
			g011 *= norm1.y;
			g101 *= norm1.z;
			g111 *= norm1.w;

			float n000 = dot(g000, Pf0);
			float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
			float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
			float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
			float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
			float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
			float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
			float n111 = dot(g111, Pf1);

			vec3 fade_xyz = fade(Pf0);
			vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
			vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
			float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
			return 2.2 * n_xyz;
		}
	

void vertex() {
// Input:3
	mat4 n_out3p0 = MODEL_MATRIX;


// Input:2
	vec2 n_out2p0 = VERTEX;


// TransformVectorMult:4
	vec3 n_out4p0 = (n_out3p0 * vec4(vec3(n_out2p0, 0.0), 1.0)).xyz;


// VaryingSetter:5
	var_WorldPos = vec2(n_out4p0.xy);


}

void fragment() {
// ColorParameter:49
	vec4 n_out49p0 = WaterColour;


// VaryingGetter:195
	vec2 n_out195p0 = var_WorldPos;


// VectorFunc:59
	vec2 n_out59p0 = floor(n_out195p0);


// FloatParameter:8
	float n_out8p0 = CausticTextureScale;


// VectorOp:7
	vec2 n_out7p0 = n_out59p0 * vec2(n_out8p0);


// Reroute:196
	vec2 n_out196p0 = n_out7p0;


// FloatParameter:90
	float n_out90p0 = CausticMovementScale;


// Input:193
	float n_out193p0 = TIME;


// FloatParameter:89
	float n_out89p0 = CausticMovementSpeed;


// FloatOp:88
	float n_out88p0 = n_out193p0 * n_out89p0;


// FloatParameter:94
	float n_out94p0 = CausticMovementStrength;


// FloatOp:198
	float n_out198p0 = n_out88p0 * n_out94p0;


// UVFunc:202
	vec2 n_out202p0 = vec2(n_out198p0) * vec2(n_out90p0) + n_out7p0;


// Reroute:204
	float n_out204p0 = n_out94p0;


// FloatOp:93
	float n_out93p0 = n_out202p0.x * n_out204p0;


// VectorCompose:95
	vec2 n_out95p0 = vec2(n_out93p0, n_out93p0);


// VectorOp:92
	vec2 n_out92p0 = n_out196p0 + n_out95p0;


	vec4 n_out10p0;
// Texture2D:10
	n_out10p0 = texture(CausticTexture, n_out92p0);


// ColorParameter:41
	vec4 n_out41p0 = CausticColour;


// VectorOp:32
	vec4 n_out32p0 = n_out10p0 * n_out41p0;


	vec4 n_out44p0;
// Texture2D:44
	n_out44p0 = texture(CausticHighlightTexture, n_out92p0);


// ColorParameter:45
	vec4 n_out45p0 = CausticHighlightColour;


// VectorOp:46
	vec4 n_out46p0 = n_out44p0 * n_out45p0;


// VectorDecompose:48
	float n_out48p0 = n_out46p0.x;
	float n_out48p1 = n_out46p0.y;
	float n_out48p2 = n_out46p0.z;
	float n_out48p3 = n_out46p0.w;


// Mix:47
	vec4 n_out47p0 = mix(n_out32p0, n_out46p0, vec4(n_out48p3));


// VectorDecompose:57
	float n_out57p0 = n_out47p0.x;
	float n_out57p1 = n_out47p0.y;
	float n_out57p2 = n_out47p0.z;
	float n_out57p3 = n_out47p0.w;


// VaryingGetter:13
	vec2 n_out13p0 = var_WorldPos;


// Vector2Constant:40
	vec2 n_out40p0 = vec2(0.000000, 0.000000);


// FloatParameter:17
	float n_out17p0 = NoiseScale;


// Input:37
	float n_out37p0 = TIME;


// FloatParameter:39
	float n_out39p0 = NoiseModulateSpeed;


// FloatOp:38
	float n_out38p0 = n_out37p0 * n_out39p0;


	float n_out36p0;
// PerlinNoise3D:36
	{
		n_out36p0 = cnoise(vec3((vec3(n_out13p0, 0.0).xy + vec3(n_out40p0, 0.0).xy) * n_out17p0, n_out38p0)) * 0.5 + 0.5;
	}


// FloatParameter:18
	float n_out18p0 = NoiseStrength;


// FloatOp:31
	float n_out31p0 = n_out36p0 * n_out18p0;


// FloatOp:60
	float n_out60p0 = n_out31p0 * n_out57p3;


// VectorCompose:61
	vec4 n_out61p0 = vec4(n_out57p0, n_out57p1, n_out57p2, n_out60p0);


// VaryingGetter:96
	vec2 n_out96p0 = var_WorldPos;


// VectorFunc:97
	vec2 n_out97p0 = floor(n_out96p0);


// FloatParameter:129
	float n_out129p0 = SpecularNoiseMovementStrength;


// FloatOp:128
	float n_out128p0 = n_out95p0.x * n_out129p0;


// VectorOp:127
	vec2 n_out127p0 = n_out97p0 - vec2(n_out128p0);


// FloatParameter:102
	float n_out102p0 = SpecularSpeed;


// Input:103
	float n_out103p0 = TIME;


// VectorOp:101
	vec2 n_out101p0 = vec2(n_out102p0) * vec2(n_out103p0);


// VectorOp:104
	vec2 n_in104p0 = vec2(1.00000, 1.00000);
	vec2 n_out104p0 = n_in104p0 - n_out101p0;


// FloatParameter:106
	float n_out106p0 = SpecularScale1;


// FloatConstant:99
	float n_out99p0 = 2.000000;


	float n_out105p0;
// PerlinNoise3D:105
	{
		n_out105p0 = cnoise(vec3((vec3(n_out127p0, 0.0).xy + vec3(n_out104p0, 0.0).xy) * n_out106p0, n_out99p0)) * 0.5 + 0.5;
	}


// VectorOp:100
	vec2 n_in100p0 = vec2(1.00000, 1.00000);
	vec2 n_out100p0 = n_in100p0 + n_out101p0;


	float n_out98p0;
// PerlinNoise3D:98
	{
		n_out98p0 = cnoise(vec3((vec3(n_out127p0, 0.0).xy + vec3(n_out100p0, 0.0).xy) * n_out106p0, n_out99p0)) * 0.5 + 0.5;
	}


	vec3 n_out107p0;
// ColorOp:107
	{
		float base = vec3(n_out105p0).x;
		float blend = vec3(n_out98p0).x;
		if (base < 0.5) {
			n_out107p0.x = 2.0 * base * blend;
		} else {
			n_out107p0.x = 1.0 - 2.0 * (1.0 - blend) * (1.0 - base);
		}
	}
	{
		float base = vec3(n_out105p0).y;
		float blend = vec3(n_out98p0).y;
		if (base < 0.5) {
			n_out107p0.y = 2.0 * base * blend;
		} else {
			n_out107p0.y = 1.0 - 2.0 * (1.0 - blend) * (1.0 - base);
		}
	}
	{
		float base = vec3(n_out105p0).z;
		float blend = vec3(n_out98p0).z;
		if (base < 0.5) {
			n_out107p0.z = 2.0 * base * blend;
		} else {
			n_out107p0.z = 1.0 - 2.0 * (1.0 - blend) * (1.0 - base);
		}
	}


// VectorDecompose:112
	float n_out112p0 = n_out107p0.x;
	float n_out112p1 = n_out107p0.y;
	float n_out112p2 = n_out107p0.z;


// Vector2Constant:111
	vec2 n_out111p0 = vec2(0.000000, 0.000000);


// FloatParameter:109
	float n_out109p0 = SpecularScale2;


// FloatConstant:110
	float n_out110p0 = 1.000000;


	float n_out108p0;
// PerlinNoise3D:108
	{
		n_out108p0 = cnoise(vec3((vec3(n_out127p0, 0.0).xy + vec3(n_out111p0, 0.0).xy) * n_out109p0, n_out110p0)) * 0.5 + 0.5;
	}


// FloatOp:113
	float n_out113p0 = n_out112p0 - n_out108p0;


// FloatParameter:115
	float n_out115p0 = SpecularThreshold;


// Step:114
	float n_out114p0 = step(n_out113p0, n_out115p0);


// ColorParameter:117
	vec4 n_out117p0 = SpecularColour;


// VectorOp:116
	vec4 n_out116p0 = vec4(n_out114p0) * n_out117p0;


// VectorDecompose:123
	float n_out123p0 = n_out116p0.x;
	float n_out123p1 = n_out116p0.y;
	float n_out123p2 = n_out116p0.z;
	float n_out123p3 = n_out116p0.w;


// VectorDecompose:119
	float n_out119p0 = n_out116p0.x;
	float n_out119p1 = n_out116p0.y;
	float n_out119p2 = n_out116p0.z;
	float n_out119p3 = n_out116p0.w;


// FloatFunc:126
	float n_out126p0 = ceil(n_out60p0);


// FloatOp:121
	float n_out121p0 = n_out119p3 * n_out126p0;


// VectorCompose:122
	vec4 n_out122p0 = vec4(n_out123p0, n_out123p1, n_out123p2, n_out121p0);


// Mix:124
	vec4 n_out124p0 = mix(n_out61p0, n_out122p0, vec4(n_out121p0));


// VectorDecompose:125
	float n_out125p0 = n_out124p0.x;
	float n_out125p1 = n_out124p0.y;
	float n_out125p2 = n_out124p0.z;
	float n_out125p3 = n_out124p0.w;


// Mix:52
	vec4 n_out52p0 = mix(n_out49p0, n_out124p0, vec4(n_out125p3));


// ColorParameter:132
	vec4 n_out132p0 = FoamColour;


// VectorDecompose:173
	float n_out173p0 = n_out132p0.x;
	float n_out173p1 = n_out132p0.y;
	float n_out173p2 = n_out132p0.z;
	float n_out173p3 = n_out132p0.w;


// Input:130
	vec4 n_out130p0 = COLOR;


// VectorDecompose:131
	float n_out131p0 = n_out130p0.x;
	float n_out131p1 = n_out130p0.y;
	float n_out131p2 = n_out130p0.z;
	float n_out131p3 = n_out130p0.w;


// Input:155
	float n_out155p0 = TIME;


// FloatParameter:172
	float n_out172p0 = WaveSpeed;


// FloatOp:156
	float n_out156p0 = n_out155p0 * n_out172p0;


// VaryingGetter:142
	vec2 n_out142p0 = var_WorldPos;


// FloatParameter:164
	float n_out164p0 = FoamNoiseScale;


// MultiplyAdd:186
	vec2 n_in186p2 = vec2(0.00000, 0.00000);
	vec2 n_out186p0 = fma(n_out142p0, vec2(n_out164p0), n_in186p2);


	vec4 n_out177p0;
// Texture2D:177
	n_out177p0 = texture(FoamNoiseTexture, n_out186p0);


// FloatParameter:163
	float n_out163p0 = FoamNoiseAmount;


// FloatOp:187
	float n_out187p0 = n_out177p0.x * n_out163p0;


// FloatOp:188
	float n_out188p0 = n_out156p0 * n_out187p0;


// FloatOp:185
	float n_in185p1 = 2.00000;
	float n_out185p0 = pow(n_out131p0, n_in185p1);


// FloatParameter:182
	float n_out182p0 = FoamFrequency;


// VectorOp:183
	vec2 n_out183p0 = vec2(n_out185p0) * vec2(n_out182p0);


// FloatOp:178
	float n_out178p0 = n_out188p0 - n_out183p0.x;


// FloatFunc:179
	float n_out179p0 = sin(n_out178p0);


	float n_out165p0;
// Remap:165
	float n_in165p1 = -1.00000;
	float n_in165p2 = 1.00000;
	float n_in165p3 = 0.10000;
	float n_in165p4 = 0.40000;
	{
		float __input_range = n_in165p2 - n_in165p1;
		float __output_range = n_in165p4 - n_in165p3;
		n_out165p0 = n_in165p3 + __output_range * ((n_out179p0 - n_in165p1) / __input_range);
	}


// FloatOp:181
	float n_out181p0 = n_out131p0 - n_out165p0;


// Clamp:184
	float n_in184p1 = -0.00000;
	float n_in184p2 = 0.40000;
	float n_out184p0 = clamp(n_out181p0, n_in184p1, n_in184p2);


// FloatParameter:180
	float n_out180p0 = FoamQuantizeAmount;


// FloatOp:169
	float n_out169p0 = n_out184p0 * n_out180p0;


// FloatFunc:170
	float n_out170p0 = ceil(n_out169p0);


// FloatOp:171
	float n_out171p0 = n_out170p0 / n_out180p0;


// FloatOp:174
	float n_out174p0 = n_out171p0 * n_out173p3;


// VectorCompose:175
	vec4 n_out175p0 = vec4(n_out173p0, n_out173p1, n_out173p2, n_out174p0);


// VectorDecompose:149
	float n_out149p0 = n_out175p0.x;
	float n_out149p1 = n_out175p0.y;
	float n_out149p2 = n_out175p0.z;
	float n_out149p3 = n_out175p0.w;


// Mix:136
	vec4 n_out136p0 = mix(n_out52p0, n_out175p0, vec4(n_out149p3));


// Output:0
	COLOR.rgb = vec3(n_out136p0.xyz);


}

Three backticks (```) before and after a code block will make it format properly.

1 Like

You have a vec2 var_WorldPos there which gives you part of what you need, but the problem you need to solve is figuring out how far that position is from land, right?

Brute force searching the map in the shader would be extremely slow, so you’ll probably want something precalculated.

That’s why I’m suggesting a signed distance field texture. When you generate your tilemap, generate a texture alongside it that’s a single channel, that is, one value per texel. Probably float? Make the texture the same dimensions as the map (or at least, big enough to hold the entire map) at 1:1 texel to tiles. So if you have a 256x256 tile map, make a corresponding 256x256 texture.

Given you’re doing diamond tiles you might have to do some shenanigans, but as long as a world position can be mapped linearly to texture coords, it should be fine. You don’t even really need to be 1:1 tiles to texels, it just makes it easier to think about.

The idea here is that each texel in the texture represents a tile (or at least, some regularly spaced point) on the map, and you should be able to use var_WorldPos (possibly along with local_to_map() or some other scale conversion) to get the texel representing a given var_WorldPos position.

For the texture itself, when you generate it, make it so that the value in each texel is the distance to the closest land/water transition; if it’s a land tile, make it the distance to the closest water tile. If it’s a water tile, make it the distance to the closest land tile. Flip the sign for land tiles, so their value is negative.

At that point, in the shader you use var_WorldPos and a sampler on your texture to pull in the texel value. If it’s negative you’re on land. If it’s positive, it’s how far from land you are. You can even potentially set the sampler to bilinear so you get smoothing on your distance values.

You then have, for each fragment, exactly what you need, I believe.

I see, currently none of this is generated but they are just hand placed tiles to create the islands.

So no where in code does this know what an island is versus what an ocean is. It’s just some tiles are marked as ground and some tiles are marked as water in the custom data layers of the TileMap Layer. Which I can access in my code.

I had assumed it would be possible to read that information from the shader but alas it seems that functionality doesn’t exist?

I could write an island generator and then just assign a height to each tile as I create the world then it would read that information. Seems overkill just for an ocean shader to know where the ground tiles are…

Thank you none the less.

You can still do this with hand-placed tiles. Just generate your signed distance field texture at level start, referencing the existing tilemap. It’s a post-processing phase.

Basically, the shader is running on the GPU, and has no direct access to the tilemap information in the way you’d like. You need some way of presenting that information to he shader on-GPU. Whether you do that by generating an SDF texture or through some other means, you have to somehow get the information onto the GPU in a form it can use.

GPUs are slow. We don’t think of them that way because they’re also massively parallel, but each individual core is slow and quite restricted in what it can do. If every pixel passing through the fragment shader had to scan the tilemap to determine the nearest land, I bet you’d be in seconds-per-frame (or worse) territory even on a high-end gaming rig.

So, the trick is, preprocess somehow. Pre-scan everything, and get it in a form that the shader can use as a simple lookup table. Make it so the frag shader can pull in a handful of values and crunch those.

There are alternate ways you could do this; you could (say) build some sort of voronoi representation of the map in some form the GPU could read. You could have some small array of uniforms that are a set of points you’ve determined are the farthest from land, and the shader could use distance to the closest of those points. I’m sure there are other methods as well.

Personally, if I were doing this, I’d use the SDF texture, but YMMV.