Skip to content

Instantly share code, notes, and snippets.

@belzecue
Created February 15, 2025 13:25
Show Gist options
  • Save belzecue/e067ec00ed7cb87ad0adc5e3a03ec248 to your computer and use it in GitHub Desktop.
Save belzecue/e067ec00ed7cb87ad0adc5e3a03ec248 to your computer and use it in GitHub Desktop.
Fractal texturing lets you use one texture to efficiently cover a large mesh and will stay sharp at any distance, especially close up. It also solves the problem of texture repetition really well. Give it a try below. I've even used it with a 64-pixel texture and it works great.
/*
Godot 3 shader version by Belzecue [https://bsky.app/profile/belzecue.bsky.social].
Based on XorDev's ShaderToy.
"Fractal Texturing" by @XorDev
While creating a 3D game (https://twitter.com/XorDev/status/1578947873550389248),
I came across a problem with my texture quality. I needed something that looked good up close
or far away. That's when I developed what I call "fractal texturing". I'm sure it's been done
before, but it's new to me and I thought it was quite neat so I'm sharing it here.
The concept is quite simple:
Instead of sampling a texture at one scale for all pixels, we'll sample at a different scale
depending on the pixel's depth. Then by blending smoothly between the scales, we can produce
a consistent level of detail. This isn't perfect for all textures (e.g. struggles with bricks)
but it's perfect for many natural textures like dirt or grass and works well for my needs.
Maybe you'll find a use for it also!
Tutorial: https://mini.gmshaders.com/p/gm-shaders-mini-fractal-texturing-1408552
*/
shader_type spatial;
render_mode async_visible,blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx;
const float exp1 = 2.718281828459;
const vec4 world_camera_vec4 = vec4(vec3(0.0), 1.0);
uniform sampler2D tex_main : hint_albedo;
uniform float scale = 1.0;
varying vec3 world_camera;
/*
//Samples at three scales, interpolating between them
vec4 fractal_texture(sampler2D tex, vec2 uv, float depth)
{
//Find the pixel level of detail
float LOD = log(depth);
//Round LOD down
float LOD_floor = floor(LOD);
//Compute the fract part for interpolating
float LOD_fract = LOD - LOD_floor;
//Compute scaled uvs
vec2 uv1 = uv / exp(LOD_floor - 1.0);
vec2 uv2 = uv / exp(LOD_floor + 0.0);
vec2 uv3 = uv / exp(LOD_floor + 1.0);
//Sample at 3 scales
vec4 tex0 = texture(tex, uv1);
vec4 tex1 = texture(tex, uv2);
vec4 tex2 = texture(tex, uv3);
//Blend samples together
return (tex1 + mix(tex0, tex2, LOD_fract)) * 0.5;
}
*/
//Samples at three scales, interpolating between them (with mipmapping)
vec4 fractal_texture_mip(sampler2D tex, vec2 uv, float depth)
{
//Find the pixel level of detail
float LOD = log(depth);
//Round LOD down
float LOD_floor = floor(LOD);
//Compute the fract part for interpolating
float LOD_fract = LOD - LOD_floor;
//Compute scaled uvs
vec2 uv1 = uv / exp(LOD_floor - 1.0);
vec2 uv2 = uv / exp(LOD_floor + 0.0);
vec2 uv3 = uv / exp(LOD_floor + 1.0);
//Compute continous derivitives
vec2 dx = dFdx(uv) / depth * exp1;
vec2 dy = dFdy(uv) / depth * exp1;
//Sample at 3 scales
vec4 tex0 = textureGrad(tex, uv1, dx, dy);
vec4 tex1 = textureGrad(tex, uv2, dx, dy);
vec4 tex2 = textureGrad(tex, uv3, dx, dy);
//Blend samples together
return (tex1 + mix(tex0, tex2, LOD_fract)) * 0.5;
}
void vertex()
{
world_camera = CAMERA_POSITION_WORLD;
}
void fragment()
{
//Center coordinates
vec2 uv = UV-0.5;
// Apply scale
vec2 coords = uv * scale;
//Compute pixel depth
vec4 a = CAMERA_MATRIX * vec4(VERTEX, 1.0);
float scale2 = distance(world_camera, a.xyz);
float depth = length(vec3(uv, 1.0)) * scale2;
vec4 col = fractal_texture_mip(tex_main, coords, depth);
//Output results
ALBEDO = col.rgb;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment