Fix scanline comb artifact in road shader by using integer-rounded tile coordinates

This commit is contained in:
2026-11-09 21:41:38 +01:00
parent eaa847ac27
commit 882c83ba13
+23 -41
View File
@@ -169,9 +169,10 @@ float getRiverDistance(vec2 p, int mask) {
} }
} }
float getRoadDistance(vec2 p, int mask, vec2 tileCenterWorld, float amp) { float getRoadDistance(vec2 p, int mask, int ix, int iy, float amp) {
float h1 = fract(sin(dot(tileCenterWorld, vec2(12.9898, 78.233))) * 43758.5453); // Exact integer parity per hex tile: completely immune to floating-point interpolation jitter across scanlines!
float s1 = (h1 > 0.5) ? 1.0 : -1.0; int parity = (ix * 3 + iy * 5) & 1;
float s1 = (parity == 0) ? 1.0 : -1.0;
int edges[6]; int edges[6];
int count = 0; int count = 0;
@@ -363,63 +364,44 @@ void main() {
vec4 landColor = mix(nonSnowLand, snowColor, snowWeight); vec4 landColor = mix(nonSnowLand, snowColor, snowWeight);
// 6b. Road Texture Integration (blended directly into the tile surface texture) // 6b. Road Texture Integration (blended directly into the tile surface texture)
if (hasRoad == 1 && oceanWaterFac < 0.95) { if (hasRoad == 1) {
// Recover exact integer grid indices of the tile center (spacing is 0.75 in X and 0.43287992 in Y)
vec2 tileCenterWorld = vWorldPos.xy - fragVert.xy; vec2 tileCenterWorld = vWorldPos.xy - fragVert.xy;
int ix = int(floor(tileCenterWorld.x / 0.75 + 0.5));
int iy = int(floor(tileCenterWorld.y / 0.43287992 + 0.5));
float amp = max(0.0, roadMeander); float amp = max(0.0, roadMeander);
float rawRoadDist = getRoadDistance(fragVert.xy, roadMask, tileCenterWorld, amp);
float rScale = max(1.0, roadTextureScale); float rScale = max(1.0, roadTextureScale);
vec2 uvRoad = vWorldPos.xy * rScale; vec2 uvRoad = vWorldPos.xy * rScale;
// Sample textures in uniform control flow (before any fragment-dependent branch) to avoid mipmap derivative glitches
vec3 soilTex = texture(texSand, uvRoad).rgb; vec3 soilTex = texture(texSand, uvRoad).rgb;
vec3 stoneTex = texture(texRock, uvRoad * 1.25).rgb; vec3 saharaTex = texture(texSahara, uvRoad * 0.9).rgb;
float pebbleRelief = mix(soilTex.r, stoneTex.r, 0.5) - 0.5; float fineGrain = (mix(soilTex.r, saharaTex.r, 0.5) - 0.5) * 0.18;
// Very subtle micro-edge grain (0.0018) so the road has crisp, clean borders float rawRoadDist = getRoadDistance(fragVert.xy, roadMask, ix, iy, amp);
float dRoad = max(0.0, rawRoadDist + pebbleRelief * 0.0018);
float rCore = max(0.014, roadRadius); float rCore = max(0.012, roadRadius);
float rOuter = rCore + max(0.006, roadBorderWidth); float rOuter = rCore + max(0.005, roadBorderWidth);
// Crisp road deck and distinct dark casing/curb float roadCoreFac = 1.0 - smoothstep(rCore * 0.65, rCore, rawRoadDist);
float roadCoreFac = 1.0 - smoothstep(rCore * 0.80, rCore, dRoad); float roadTotalFac = (1.0 - smoothstep(rCore, rOuter, rawRoadDist)) * (1.0 - oceanWaterFac);
float roadTotalFac = 1.0 - smoothstep(rOuter * 0.82, rOuter, dRoad);
if (roadTotalFac > 0.001) { if (roadTotalFac > 0.001) {
vec3 roadSurf; vec3 centerTone = clamp(roadColor * (1.0 + fineGrain), 0.0, 1.0);
if (roadType == 1) { vec3 borderTone = clamp(roadBorderColor * (1.0 + fineGrain * 0.7), 0.0, 1.0);
// Stone / Cobblestone road: bright paved stone with crisp dark curb vec3 roadSurf = mix(borderTone, centerTone, roadCoreFac);
vec3 cobbleSurface = roadColor * (0.88 + 0.35 * pebbleRelief);
vec3 stoneCurb = roadBorderColor * (0.85 + 0.25 * pebbleRelief);
roadSurf = mix(stoneCurb, cobbleSurface, roadCoreFac);
} else if (roadType == 2) {
// Railroad: dark gravel ballast bed with steel twin rails
vec3 ballast = roadBorderColor * (0.85 + 0.35 * pebbleRelief);
float railLine = smoothstep(rCore * 0.38, rCore * 0.48, rawRoadDist)
* (1.0 - smoothstep(rCore * 0.56, rCore * 0.68, rawRoadDist));
vec3 steelRail = vec3(0.75, 0.78, 0.82);
vec3 innerBed = mix(roadColor * (0.85 + 0.30 * pebbleRelief), steelRail, railLine);
roadSurf = mix(ballast, innerBed, roadCoreFac);
} else {
// Dirt / Country road: warm sandy-ochre packed earth with two subtle cart wheel tracks & dark curb
float wheelRut = smoothstep(rCore * 0.30, rCore * 0.48, rawRoadDist)
* (1.0 - smoothstep(rCore * 0.55, rCore * 0.72, rawRoadDist));
vec3 brightDeck = roadColor * (0.92 + 0.32 * pebbleRelief);
vec3 darkCurb = roadBorderColor * (0.85 + 0.25 * pebbleRelief);
vec3 trackedDeck = mix(brightDeck, darkCurb, wheelRut * 0.35);
roadSurf = mix(darkCurb, trackedDeck, roadCoreFac);
}
// If crossing a river, render a crisp bridge deck and suppress river water under the bridge // If crossing a river, render a bridge deck and suppress river water under the bridge
if (riverWaterFac > 0.01) { if (riverWaterFac > 0.01) {
vec3 bridgeCol = (roadType >= 1) vec3 bridgeCol = (roadType >= 1)
? vec3(0.72, 0.70, 0.66) * (0.90 + 0.25 * pebbleRelief) ? vec3(0.72, 0.70, 0.66) * (1.0 + fineGrain)
: vec3(0.68, 0.48, 0.28) * (0.90 + 0.25 * pebbleRelief); : vec3(0.68, 0.48, 0.28) * (1.0 + fineGrain);
roadSurf = mix(roadSurf, bridgeCol, riverWaterFac); roadSurf = mix(roadSurf, bridgeCol, riverWaterFac);
waterFac *= (1.0 - roadTotalFac * 0.98); waterFac *= (1.0 - roadTotalFac * 0.98);
} }
float blendAlpha = roadTotalFac * clamp(roadOpacity, 0.0, 1.0) * (1.0 - oceanWaterFac); float blendAlpha = roadTotalFac * clamp(roadOpacity, 0.0, 1.0);
landColor.rgb = mix(landColor.rgb, roadSurf, blendAlpha); landColor.rgb = mix(landColor.rgb, roadSurf, blendAlpha);
} }
} }