Variable terrainFragmentShaderConst
terrainFragmentShader: "\n uniform vec3 biomeColors[7];\n uniform vec2 biomeCenters[7];\n uniform float biomeRadii[7];\n uniform int biomeTypes[7]; // 0=marsh, 1=forest, 2=desert, 3=tundra, 4=savanna, 5=mountain, 6=scrubland\n \n // PBR texture samplers (one set per biome)\n uniform sampler2D marshAlbedo;\n uniform sampler2D marshNormal;\n uniform sampler2D marshRoughness;\n uniform sampler2D marshAO;\n \n uniform sampler2D forestAlbedo;\n uniform sampler2D forestNormal;\n uniform sampler2D forestRoughness;\n uniform sampler2D forestAO;\n \n uniform sampler2D desertAlbedo;\n uniform sampler2D desertNormal;\n uniform sampler2D desertRoughness;\n uniform sampler2D desertAO;\n \n uniform sampler2D tundraAlbedo;\n uniform sampler2D tundraNormal;\n uniform sampler2D tundraRoughness;\n uniform sampler2D tundraAO;\n \n uniform sampler2D mountainAlbedo;\n uniform sampler2D mountainNormal;\n uniform sampler2D mountainRoughness;\n uniform sampler2D mountainAO;\n \n uniform bool useTextures; // Toggle between textured and procedural\n \n varying vec2 vUv;\n varying vec3 vPos;\n varying vec3 vWorldPos;\n varying float vElevation;\n varying float vSlope;\n varying vec3 vNormal;\n varying vec3 vTriplanarPos;\n \n // Simple hash noise\n float hash(vec2 p) { \n return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); \n }\n \n // Triplanar texture sampling\n vec4 triplanarSample(sampler2D tex, vec3 pos, vec3 normal) {\n // Calculate blend weights based on surface normal\n vec3 blendWeights = abs(normal);\n blendWeights = blendWeights / (blendWeights.x + blendWeights.y + blendWeights.z);\n \n // Sample texture from three planes\n float scale = 0.1; // Texture tiling scale\n vec4 xSample = texture2D(tex, pos.yz * scale);\n vec4 ySample = texture2D(tex, pos.xz * scale);\n vec4 zSample = texture2D(tex, pos.xy * scale);\n \n // Blend samples based on normal\n return xSample * blendWeights.x + ySample * blendWeights.y + zSample * blendWeights.z;\n }\n \n // Sample complete PBR material using triplanar mapping\n struct PBRMaterial {\n vec3 albedo;\n vec3 normal;\n float roughness;\n float ao;\n };\n \n PBRMaterial sampleBiomeMaterial(int biomeType, vec3 pos, vec3 normal) {\n PBRMaterial mat;\n \n if (biomeType == 0) { // Marsh\n mat.albedo = triplanarSample(marshAlbedo, pos, normal).rgb;\n mat.normal = triplanarSample(marshNormal, pos, normal).rgb;\n mat.roughness = triplanarSample(marshRoughness, pos, normal).r;\n mat.ao = triplanarSample(marshAO, pos, normal).r;\n } else if (biomeType == 1) { // Forest\n mat.albedo = triplanarSample(forestAlbedo, pos, normal).rgb;\n mat.normal = triplanarSample(forestNormal, pos, normal).rgb;\n mat.roughness = triplanarSample(forestRoughness, pos, normal).r;\n mat.ao = triplanarSample(forestAO, pos, normal).r;\n } else if (biomeType == 2) { // Desert\n mat.albedo = triplanarSample(desertAlbedo, pos, normal).rgb;\n mat.normal = triplanarSample(desertNormal, pos, normal).rgb;\n mat.roughness = triplanarSample(desertRoughness, pos, normal).r;\n mat.ao = triplanarSample(desertAO, pos, normal).r;\n } else if (biomeType == 3) { // Tundra\n mat.albedo = triplanarSample(tundraAlbedo, pos, normal).rgb;\n mat.normal = triplanarSample(tundraNormal, pos, normal).rgb;\n mat.roughness = triplanarSample(tundraRoughness, pos, normal).r;\n mat.ao = triplanarSample(tundraAO, pos, normal).r;\n } else if (biomeType == 5) { // Mountain\n mat.albedo = triplanarSample(mountainAlbedo, pos, normal).rgb;\n mat.normal = triplanarSample(mountainNormal, pos, normal).rgb;\n mat.roughness = triplanarSample(mountainRoughness, pos, normal).r;\n mat.ao = triplanarSample(mountainAO, pos, normal).r;\n } else {\n // Fallback for savanna/scrubland (use forest textures)\n mat.albedo = triplanarSample(forestAlbedo, pos, normal).rgb;\n mat.normal = triplanarSample(forestNormal, pos, normal).rgb;\n mat.roughness = triplanarSample(forestRoughness, pos, normal).r;\n mat.ao = triplanarSample(forestAO, pos, normal).r;\n }\n \n return mat;\n }\n \n // Triplanar detail mapping (procedural fallback)\n float triplanarDetail(vec3 pos) {\n // Sample detail from three planes\n float detailX = hash(pos.yz * 8.0);\n float detailY = hash(pos.xz * 8.0);\n float detailZ = hash(pos.xy * 8.0);\n \n // Calculate blend weights based on surface normal approximation\n vec3 blendWeights = vec3(0.1, 0.8, 0.1);\n blendWeights = blendWeights / (blendWeights.x + blendWeights.y + blendWeights.z);\n \n return detailX * blendWeights.x + detailY * blendWeights.y + detailZ * blendWeights.z;\n }\n \n int getBiomeType(vec2 pos) {\n int closestIdx = 0;\n float closestDist = distance(pos, biomeCenters[0]);\n \n for (int i = 1; i < 7; i++) {\n float dist = distance(pos, biomeCenters[i]);\n if (dist < closestDist) {\n closestDist = dist;\n closestIdx = i;\n }\n }\n \n return biomeTypes[closestIdx];\n }\n \n vec3 getBiomeColor(vec2 pos) {\n // Find closest biome\n int closestIdx = 0;\n float closestDist = distance(pos, biomeCenters[0]);\n \n for (int i = 1; i < 7; i++) {\n float dist = distance(pos, biomeCenters[i]);\n if (dist < closestDist) {\n closestDist = dist;\n closestIdx = i;\n }\n }\n \n vec3 baseColor = biomeColors[closestIdx];\n \n // Blend with adjacent biomes at boundaries\n vec3 blendedColor = baseColor;\n float totalWeight = 1.0;\n \n for (int i = 0; i < 7; i++) {\n if (i != closestIdx) {\n float dist = distance(pos, biomeCenters[i]);\n float blendRadius = biomeRadii[i] * 0.3; // 30% blend zone\n float weight = smoothstep(blendRadius, 0.0, dist - biomeRadii[i]);\n blendedColor += biomeColors[i] * weight;\n totalWeight += weight;\n }\n }\n \n return blendedColor / totalWeight;\n }\n \n void main() {\n vec2 worldXZ = vWorldPos.xz;\n int biomeType = getBiomeType(worldXZ);\n \n vec3 finalColor;\n \n if (useTextures) {\n // Use PBR textures with triplanar mapping\n PBRMaterial mat = sampleBiomeMaterial(biomeType, vTriplanarPos, vNormal);\n \n // Start with albedo\n finalColor = mat.albedo;\n \n // Apply ambient occlusion\n finalColor *= mat.ao;\n \n // Simple lighting based on normal map\n // Convert normal map from [0,1] to [-1,1]\n vec3 normalTS = mat.normal * 2.0 - 1.0;\n float lighting = max(dot(normalTS, vec3(0.0, 1.0, 0.0)), 0.3);\n finalColor *= lighting;\n \n // Biome-specific effects\n if (biomeType == 3) { // Tundra: Add snow sparkle\n float sparkle = hash(vPos.xz * 10.0);\n finalColor = mix(finalColor, vec3(1.0, 1.0, 1.0), sparkle * 0.2);\n \n // Snow on elevated areas\n if (vElevation > 0.5) {\n finalColor = mix(finalColor, vec3(0.95, 0.95, 1.0), 0.4);\n }\n }\n \n if (biomeType == 5) { // Mountain: Snow caps on peaks\n if (vElevation > 18.0) {\n finalColor = mix(finalColor, vec3(0.9, 0.9, 0.95), 0.6);\n }\n }\n } else {\n // Fallback to procedural rendering\n vec3 baseColor = getBiomeColor(worldXZ);\n float detail = triplanarDetail(vWorldPos);\n \n // Tundra: Add snow shader effect\n if (biomeType == 3) {\n float sparkle = hash(vPos.xz * 10.0);\n baseColor = mix(baseColor, vec3(1.0, 1.0, 1.0), sparkle * 0.3);\n \n if (vElevation > 0.5) {\n baseColor = mix(baseColor, vec3(0.95, 0.95, 1.0), 0.6);\n }\n \n baseColor += vec3(detail * 0.2);\n }\n \n // Mountain: Rocky appearance with elevation-based coloring\n if (biomeType == 5) {\n float rockFactor = smoothstep(5.0, 15.0, vElevation);\n vec3 rockColor = vec3(0.3, 0.3, 0.35);\n baseColor = mix(baseColor, rockColor, rockFactor);\n \n if (vElevation > 18.0) {\n baseColor = mix(baseColor, vec3(0.9, 0.9, 0.95), 0.8);\n }\n \n baseColor = mix(baseColor, baseColor * detail, 0.3);\n }\n \n // Add base noise variation\n float n = hash(vPos.xz * 0.5);\n finalColor = mix(baseColor * 0.8, baseColor * 1.2, n);\n \n // Apply detail normal map effect\n finalColor = mix(finalColor, finalColor * (0.8 + detail * 0.4), 0.5);\n }\n \n // Distance-based darkening (vignette on floor)\n float dist = length(vPos.xz);\n finalColor *= smoothstep(100.0, 30.0, dist);\n \n gl_FragColor = vec4(finalColor, 1.0);\n }\n" = ...