fix(terrain): hide chunk grid by tiling textures 4× per chunk

Two changes that work together:

1. terrain_mesh.cpp: bump texture-coord scale from 1× to 4× per
   chunk so the texture's own pattern repeats every ~8 yards
   instead of every ~33 yards. At 1×, the texture's repeat
   frequency syncs with the chunk grid and any per-chunk alpha
   difference reads as a hard 33-yard square. At 4× the pattern
   noise breaks up the boundary line and the eye stops locking
   onto the grid.

2. terrain.frag.glsl: widen the alpha-edge feather from 3 to 8
   texels and use 9 taps instead of 5 so per-chunk alpha values
   bleed across the chunk boundary instead of stepping. Hard
   alpha steps were the second contributor to visible chunk
   tiles in painted regions.

Reported by user via screenshot showing obvious chunk-grid
artifacts in painted areas of the texture-paint editor.
This commit is contained in:
Kelsi 2026-05-08 12:34:16 -07:00
parent 4acf4612af
commit 163077fef0
2 changed files with 16 additions and 5 deletions

View file

@ -50,11 +50,14 @@ float sampleShadowPCF(sampler2DShadow smap, vec3 coords) {
} }
float sampleAlpha(sampler2D tex, vec2 uv) { float sampleAlpha(sampler2D tex, vec2 uv) {
// Smooth 5-tap box near chunk edges to hide alpha-map seams; // Smooth 9-tap box near chunk edges to hide alpha-map seams;
// blends gradually to avoid a visible ring at the transition. // blends gradually to avoid a visible ring at the transition.
// Wider feather (8 texels) makes per-chunk alpha differences
// bleed across the boundary so the chunk grid stops reading
// as a hard step.
vec2 edge = min(uv, 1.0 - uv); vec2 edge = min(uv, 1.0 - uv);
float border = min(edge.x, edge.y); float border = min(edge.x, edge.y);
float blurWeight = 1.0 - smoothstep(0.5 / 64.0, 3.0 / 64.0, border); float blurWeight = 1.0 - smoothstep(1.0 / 64.0, 8.0 / 64.0, border);
float center = texture(tex, uv).r; float center = texture(tex, uv).r;
if (blurWeight < 0.001) return center; if (blurWeight < 0.001) return center;
vec2 texel = vec2(1.0 / 64.0); vec2 texel = vec2(1.0 / 64.0);
@ -63,7 +66,11 @@ float sampleAlpha(sampler2D tex, vec2 uv) {
avg += texture(tex, uv + vec2( texel.x, 0.0)).r; avg += texture(tex, uv + vec2( texel.x, 0.0)).r;
avg += texture(tex, uv + vec2(0.0, -texel.y)).r; avg += texture(tex, uv + vec2(0.0, -texel.y)).r;
avg += texture(tex, uv + vec2(0.0, texel.y)).r; avg += texture(tex, uv + vec2(0.0, texel.y)).r;
avg *= 0.2; avg += texture(tex, uv + vec2(-texel.x, -texel.y)).r;
avg += texture(tex, uv + vec2( texel.x, -texel.y)).r;
avg += texture(tex, uv + vec2(-texel.x, texel.y)).r;
avg += texture(tex, uv + vec2( texel.x, texel.y)).r;
avg *= 1.0 / 9.0;
return mix(center, avg, blurWeight); return mix(center, avg, blurWeight);
} }

View file

@ -237,8 +237,12 @@ std::vector<TerrainVertex> TerrainMeshGenerator::generateVertices(const MapChunk
} }
// Texture coordinates: world-aligned so patterns don't reset per chunk. // Texture coordinates: world-aligned so patterns don't reset per chunk.
// Keep one texture repeat per chunk (matches prior scale). // Tile each texture 4× per chunk (one repeat every ~8 yards) so the
constexpr float texScale = 1.0f / CHUNK_SIZE; // texture's own pattern noise breaks up the chunk grid rather than
// syncing with it. At 1 repeat/chunk the per-chunk alpha differences
// read as obvious 33-yard squares; at 4× the pattern is small enough
// that the eye no longer locks onto the chunk boundary.
constexpr float texScale = 4.0f / CHUNK_SIZE;
vertex.texCoord[0] = -vertex.position[1] * texScale; vertex.texCoord[0] = -vertex.position[1] * texScale;
vertex.texCoord[1] = -vertex.position[0] * texScale; vertex.texCoord[1] = -vertex.position[0] * texScale;