diff --git a/shaders/simple.frag b/shaders/simple.frag index 317459d..48ebcb1 100644 --- a/shaders/simple.frag +++ b/shaders/simple.frag @@ -169,9 +169,10 @@ float getRiverDistance(vec2 p, int mask) { } } -float getRoadDistance(vec2 p, int mask, vec2 tileCenterWorld, float amp) { - float h1 = fract(sin(dot(tileCenterWorld, vec2(12.9898, 78.233))) * 43758.5453); - float s1 = (h1 > 0.5) ? 1.0 : -1.0; +float getRoadDistance(vec2 p, int mask, int ix, int iy, float amp) { + // Exact integer parity per hex tile: completely immune to floating-point interpolation jitter across scanlines! + int parity = (ix * 3 + iy * 5) & 1; + float s1 = (parity == 0) ? 1.0 : -1.0; int edges[6]; int count = 0; @@ -363,63 +364,44 @@ void main() { vec4 landColor = mix(nonSnowLand, snowColor, snowWeight); // 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; + 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 rawRoadDist = getRoadDistance(fragVert.xy, roadMask, tileCenterWorld, amp); float rScale = max(1.0, roadTextureScale); 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 stoneTex = texture(texRock, uvRoad * 1.25).rgb; - float pebbleRelief = mix(soilTex.r, stoneTex.r, 0.5) - 0.5; + vec3 saharaTex = texture(texSahara, uvRoad * 0.9).rgb; + 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 dRoad = max(0.0, rawRoadDist + pebbleRelief * 0.0018); + float rawRoadDist = getRoadDistance(fragVert.xy, roadMask, ix, iy, amp); - float rCore = max(0.014, roadRadius); - float rOuter = rCore + max(0.006, roadBorderWidth); + float rCore = max(0.012, roadRadius); + float rOuter = rCore + max(0.005, roadBorderWidth); - // Crisp road deck and distinct dark casing/curb - float roadCoreFac = 1.0 - smoothstep(rCore * 0.80, rCore, dRoad); - float roadTotalFac = 1.0 - smoothstep(rOuter * 0.82, rOuter, dRoad); + float roadCoreFac = 1.0 - smoothstep(rCore * 0.65, rCore, rawRoadDist); + float roadTotalFac = (1.0 - smoothstep(rCore, rOuter, rawRoadDist)) * (1.0 - oceanWaterFac); if (roadTotalFac > 0.001) { - vec3 roadSurf; - if (roadType == 1) { - // Stone / Cobblestone road: bright paved stone with crisp dark curb - 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); - } + vec3 centerTone = clamp(roadColor * (1.0 + fineGrain), 0.0, 1.0); + vec3 borderTone = clamp(roadBorderColor * (1.0 + fineGrain * 0.7), 0.0, 1.0); + vec3 roadSurf = mix(borderTone, centerTone, 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) { vec3 bridgeCol = (roadType >= 1) - ? vec3(0.72, 0.70, 0.66) * (0.90 + 0.25 * pebbleRelief) - : vec3(0.68, 0.48, 0.28) * (0.90 + 0.25 * pebbleRelief); + ? vec3(0.72, 0.70, 0.66) * (1.0 + fineGrain) + : vec3(0.68, 0.48, 0.28) * (1.0 + fineGrain); roadSurf = mix(roadSurf, bridgeCol, riverWaterFac); 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); } }