Cellular Triangulation
This is an advanced, procedural Triangular Tiling Shader adapted for Godot’s CanvasItem. It generates a continuous, mosaic-like pattern where the color of each triangle is determined by its center point and a time-varying gradient.
The technique involves:
-
Triangular Grid: Transforming screen coordinates into a triangular grid space (
cart2tri). -
Point Jitter: Using Cubic Spline Interpolation (
randCircleSpline) driven by time and noise (hash) to smoothly move the vertices of the grid triangles, creating organic, flowing motion. -
Color Blending: Applying a dynamic linear gradient based on the triangle’s position and time, and blending between the custom “Ice” and “Fire” colors (
pal). -
Anti-aliasing: Using signed distance functions (
dseg) andsmoothstepto create smooth anti-aliased borders between the triangles.
Adjustable Uniforms (Shader Parameters):
| Parameter | Type | Description |
| color_hielo | vec3 |
The base color for one end of the spectrum (the “Ice” color). |
| color_fuego | `vec3** | The base color for the opposite end of the spectrum (the “Fire” color). |
Shader code
shader_type canvas_item;
uniform vec3 color_hielo : source_color = vec3(0, 1, 1);
uniform vec3 color_fuego : source_color = vec3(0.41, 0.0, 0.0);
const float s3 = 1.7320508075688772;
const float i3 = 0.5773502691896258;
const mat2 tri2cart = mat2(vec2(1.0, 0.0), vec2(-0.5, 0.5 * s3));
const mat2 cart2tri = mat2(vec2(1.0, 0.0), vec2(i3, 2.0 * i3));
const float HASHSCALE1 = 0.1031;
const vec3 HASHSCALE3 = vec3(443.897, 441.423, 437.195);
vec3 pal(in float t) {
return mix(color_hielo, color_fuego, t);
}
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * HASHSCALE1);
p3 += dot(p3, p3.yzx + 19.19);
return fract((p3.x + p3.y) * p3.z);
}
vec2 hash23(vec3 p3) {
p3 = fract(p3 * HASHSCALE3);
p3 += dot(p3, p3.yzx + 19.19);
return fract((p3.xx + p3.yz) * p3.zy);
}
vec3 bary(vec2 v0, vec2 v1, vec2 v2) {
float inv_denom = 1.0 / (v0.x * v1.y - v1.x * v0.y);
float v = (v2.x * v1.y - v1.x * v2.y) * inv_denom;
float w = (v0.x * v2.y - v2.x * v0.y) * inv_denom;
float u = 1.0 - v - w;
return vec3(u, v, w);
}
float dseg(vec2 xa, vec2 ba) {
return length(xa - ba * clamp(dot(xa, ba) / dot(ba, ba), 0.0, 1.0));
}
vec2 randCircle(vec3 p) {
vec2 rt = hash23(p);
float r = sqrt(rt.x);
float theta = 6.283185307179586 * rt.y;
return r * vec2(cos(theta), sin(theta));
}
vec2 randCircleSpline(vec2 p, float t) {
float t1 = floor(t);
t -= t1;
vec2 pa = randCircle(vec3(p, t1 - 1.0));
vec2 p0 = randCircle(vec3(p, t1));
vec2 p1 = randCircle(vec3(p, t1 + 1.0));
vec2 pb = randCircle(vec3(p, t1 + 2.0));
vec2 m0 = 0.5 * (p1 - pa);
vec2 m1 = 0.5 * (pb - p0);
vec2 c3 = 2.0 * p0 - 2.0 * p1 + m0 + m1;
vec2 c2 = -3.0 * p0 + 3.0 * p1 - 2.0 * m0 - m1;
vec2 c1 = m0;
vec2 c0 = p0;
return (((c3 * t + c2) * t + c1) * t + c0) * 0.8;
}
vec2 triPoint(vec2 p) {
float t0 = hash12(p);
return tri2cart * p + 0.45 * randCircleSpline(p, 0.15 * TIME + t0);
}
void tri_color(in vec2 p, in vec4 t0, in vec4 t1, in vec4 t2, in float scl, inout vec4 cw) {
vec2 p0 = p - t0.xy;
vec2 p10 = t1.xy - t0.xy;
vec2 p20 = t2.xy - t0.xy;
vec3 b = bary(p10, p20, p0);
float d10 = dseg(p0, p10);
float d20 = dseg(p0, p20);
float d21 = dseg(p - t1.xy, t2.xy - t1.xy);
float d = min(min(d10, d20), d21);
d *= -sign(min(b.x, min(b.y, b.z)));
if (d < 0.5 * scl) {
vec2 tsum = t0.zw + t1.zw + t2.zw;
vec3 h_tri = vec3(hash12(tsum + t0.zw), hash12(tsum + t1.zw), hash12(tsum + t2.zw));
vec2 pctr = (t0.xy + t1.xy + t2.xy) / 3.0;
float theta = 1.0 + 0.01 * TIME;
vec2 dir = vec2(cos(theta), sin(theta));
float grad_input = dot(pctr, dir) - sin(0.05 * TIME);
float h0 = sin(0.7 * grad_input) * 0.5 + 0.5;
h_tri = mix(vec3(h0), h_tri, 0.4);
float h = dot(h_tri, b);
vec3 c = pal(h);
float w = smoothstep(0.5 * scl, -0.5 * scl, d);
cw += vec4(w * c, w);
}
}
void fragment() {
vec2 p = (SCREEN_UV - 0.5) * 8.0;
float scl = 0.01;
vec2 tfloor = floor(cart2tri * p + 0.5);
vec2 pts[9];
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
pts[3 * i + j] = triPoint(tfloor + vec2(float(i - 1), float(j - 1)));
}
}
vec4 cw = vec4(0.0);
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
vec4 t00 = vec4(pts[3 * i + j], tfloor + vec2(float(i - 1), float(j - 1)));
vec4 t10 = vec4(pts[3 * i + j + 3], tfloor + vec2(float(i), float(j - 1)));
vec4 t01 = vec4(pts[3 * i + j + 1], tfloor + vec2(float(i - 1), float(j)));
vec4 t11 = vec4(pts[3 * i + j + 4], tfloor + vec2(float(i), float(j)));
tri_color(p, t00, t10, t11, scl, cw);
tri_color(p, t00, t11, t01, scl, cw);
}
}
if (cw.w > 0.0) {
COLOR = cw / cw.w;
} else {
COLOR = vec4(0.0, 0.0, 0.0, 1.0);
}
}

