uniform sampler2D m_TexFlat; uniform sampler2D m_TexSteep; #ifdef HAS_SPLAT uniform sampler2D m_SplatMap; uniform sampler2D m_TexFlat2; uniform sampler2D m_TexFlat3; uniform sampler2D m_TexFlat4; #endif uniform float m_TexScale; #ifdef HAS_NM_FLAT uniform sampler2D m_NormalMapFlat; #endif #ifdef HAS_NM_STEEP uniform sampler2D m_NormalMapSteep; #endif in vec3 vWorldPos; in vec3 vNormal; out vec4 outColor; #ifdef HAS_LIGHTDIR uniform vec3 m_LightDir; #endif #ifdef HAS_SCENE_LIGHT uniform vec3 m_SunColor; uniform vec3 m_AmbientColor; #endif vec4 triplanar(sampler2D tex, vec2 uvX, vec2 uvY, vec2 uvZ, vec3 bw) { return texture(tex, uvX) * bw.x + texture(tex, uvY) * bw.y + texture(tex, uvZ) * bw.z; } // Triplanare Normal-Map-Mischung (Whiteout-Blending, world-space Ausgabe). // N = geometrische Weltoberflächen-Normale, bw = triplanare Gewichte. vec3 triplanarNormal(sampler2D nmap, vec2 uvX, vec2 uvY, vec2 uvZ, vec3 bw, vec3 N) { vec3 tnX = texture(nmap, uvX).rgb * 2.0 - 1.0; vec3 tnY = texture(nmap, uvY).rgb * 2.0 - 1.0; vec3 tnZ = texture(nmap, uvZ).rgb * 2.0 - 1.0; // Reorientiertes Normal-Mapping (RNM / Whiteout): Tangent → World tnX = vec3(tnX.xy + N.zy, abs(tnX.z) * N.x); tnY = vec3(tnY.xy + N.xz, abs(tnY.z) * N.y); tnZ = vec3(tnZ.xy + N.xy, abs(tnZ.z) * N.z); return normalize(tnX.zyx * bw.x + tnY.xzy * bw.y + tnZ.xyz * bw.z); } void main() { vec3 bw = pow(abs(vNormal), vec3(4.0)); bw /= (bw.x + bw.y + bw.z + 0.001); vec2 uvX = vWorldPos.yz / m_TexScale; vec2 uvY = vWorldPos.xz / m_TexScale; vec2 uvZ = vWorldPos.xy / m_TexScale; // Flach bis ~45° Gefälle. LOD0-Smoothing (3 Passes, 0.3 Stärke) kann die Normalen // an Rampenfüßen (14°-Rampe, ideal normal.y≈0.97) auf 0.70-0.85 herunterziehen. // Untere Grenze 0.70 (≈45°) – echte Klippen (normal.y<0.70) zeigen weiter Gebirge. float flatBlend = smoothstep(0.70, 0.90, vNormal.y); float steepBlend = 1.0 - flatBlend; // Flat: reines XZ-UV wie das Terrain (uvY = worldPos.xz / texScale), kein Triplanar. // Steep: Triplanar für alle nicht-flachen Flächen inkl. Decken und Tunnelwände. #ifdef HAS_SPLAT // Splatmap-UV: worldPos.xz linear auf [0,1] (kein Flip nötig – Konvention im Editor: pz=0 → worldZ=-2048) vec2 splatUV = (vWorldPos.xz + 2048.0) / 4096.0; vec4 splat = texture(m_SplatMap, splatUV); float w2 = splat.g; float w3 = splat.b; float w4 = splat.a; float w1 = max(0.0, 1.0 - w2 - w3 - w4); vec4 flatCol = texture(m_TexFlat, uvY) * w1 + texture(m_TexFlat2, uvY) * w2 + texture(m_TexFlat3, uvY) * w3 + texture(m_TexFlat4, uvY) * w4; #else vec4 flatCol = texture(m_TexFlat, uvY); #endif vec4 col = flatCol * flatBlend + triplanar(m_TexSteep, uvX, uvY, uvZ, bw) * steepBlend; // Geometrie-Normale für Beleuchtung, ggf. durch Normal-Map ersetzt. vec3 N = normalize(vNormal); #if defined(HAS_NM_FLAT) || defined(HAS_NM_STEEP) vec3 pertN = vec3(0.0); float totalBlend = 0.0; #ifdef HAS_NM_FLAT if (flatBlend > 0.001) { vec3 nmFlat = texture(m_NormalMapFlat, uvY).rgb * 2.0 - 1.0; nmFlat = vec3(nmFlat.xy + N.xz, abs(nmFlat.z) * N.y); pertN += normalize(nmFlat.xzy) * flatBlend; totalBlend += flatBlend; } #endif #ifdef HAS_NM_STEEP if (steepBlend > 0.001) { pertN += triplanarNormal(m_NormalMapSteep, uvX, uvY, uvZ, bw, N) * steepBlend; totalBlend += steepBlend; } #endif if (totalBlend > 0.001) { N = normalize(pertN / totalBlend); } #endif // Flache Voxel-Flächen Richtung (0,1,0) korrigieren, um Abweichungen der // Marching-Cubes-Normalen auszugleichen und das Terrain-Lighting zu matchen. N = normalize(mix(N, vec3(0.0, 1.0, 0.0), flatBlend)); #ifdef HAS_LIGHTDIR vec3 lightDir = normalize(m_LightDir); #else vec3 lightDir = normalize(vec3(0.6, 1.0, 0.4)); #endif float diff = max(dot(N, lightDir), 0.0); #ifdef HAS_SCENE_LIGHT vec3 light = m_AmbientColor + m_SunColor * diff; #else vec3 light = vec3(diff * 0.65 + 0.35); #endif const float BRIGHTNESS = 0.80; #ifdef DEBUG_NO_LIGHT outColor = vec4(col.rgb * BRIGHTNESS, col.a); #else outColor = vec4(col.rgb * light * BRIGHTNESS, col.a); #endif if (outColor.a < 0.1) discard; }