#version 330 core in vec3 vNormal; in vec3 vWorldPos; in vec2 vUV; out vec4 FragColor; uniform vec3 uCameraPos; uniform vec3 uLightDir; uniform sampler2D uHeightTex; uniform float uMinV; uniform float uMaxV; uniform vec2 uTexelSize; uniform vec3 uColorZero; uniform vec3 uColorLow; uniform vec3 uColorMid; uniform vec3 uColorHigh; float saturate(float x) { return clamp(x, 0.0, 1.0); } float value01(float v) { return saturate((v - uMinV) / max(uMaxV - uMinV, 1e-6)); } vec3 colorRamp(float t) { if (t < 0.5) { return mix(uColorLow, uColorMid, t / 0.5); } return mix(uColorMid, uColorHigh, (t - 0.5) / 0.5); } float maxNeighborValue(vec2 uv) { vec2 texSizeF = 1.0 / uTexelSize; ivec2 texSizeI = ivec2(max(vec2(1.0), floor(texSizeF + 0.5))); ivec2 maxCoord = texSizeI - ivec2(1); vec2 texelF = clamp(uv * texSizeF, vec2(0.0), vec2(maxCoord)); ivec2 base = ivec2(floor(texelF)); ivec2 base10 = min(base + ivec2(1, 0), maxCoord); ivec2 base01 = min(base + ivec2(0, 1), maxCoord); ivec2 base11 = min(base + ivec2(1, 1), maxCoord); float v00 = texelFetch(uHeightTex, base, 0).r; float v10 = texelFetch(uHeightTex, base10, 0).r; float v01 = texelFetch(uHeightTex, base01, 0).r; float v11 = texelFetch(uHeightTex, base11, 0).r; return max(max(v00, v10), max(v01, v11)); } void main() { vec3 N = normalize(vNormal); vec3 L = normalize(uLightDir); vec3 V = normalize(uCameraPos - vWorldPos); vec3 H = normalize(L + V); float diff = saturate(dot(N, L)); float spec = pow(saturate(dot(N, H)), 24.0); float vC = texture(uHeightTex, vUV).r; bool isZero = abs(vC) <= 1e-6; float t = value01(vC); float m = maxNeighborValue(vUV); float eps = max(1e-4, 0.001 * (uMaxV - uMinV)); float force = smoothstep(uMaxV - eps, uMaxV, m); t = max(t, force); vec3 base = isZero ? uColorZero : colorRamp(t); vec3 col = base * (0.35 + 0.65 * diff); col += vec3(1.0) * spec * 0.10; FragColor = vec4(clamp(col, 0.0, 1.0), 1.0); }