Files
MapLibreParticleLab/shaders/hillshade.fragment.glsl

188 lines
7.2 KiB
GLSL

in vec2 v_pos;
uniform sampler2D u_image;
layout(std140) uniform HillshadeTilePropsUBO {
highp vec2 u_latrange;
highp float u_exaggeration;
highp int u_method; // Hillshade method (0: STANDARD, 1: COMBINED, 2: IGOR, 3: MULTIDIRECTIONAL, 4: BASIC)
highp int u_num_lights; // Number of light sources (1-4)
highp float u_pad0;
highp float u_pad1;
highp float u_pad2;
};
layout(std140) uniform HillshadeEvaluatedPropsUBO {
highp vec4 u_accent;
highp vec4 u_altitudes; // Up to 4 altitude values (in radians)
highp vec4 u_azimuths; // Up to 4 azimuth values (in radians)
highp vec4 u_shadows[4]; // Shadow colors (up to 4 lights)
highp vec4 u_highlights[4]; // Highlight colors (up to 4 lights)
};
#define PI 3.141592653589793
#define STANDARD 0
#define COMBINED 1
#define IGOR 2
#define MULTIDIRECTIONAL 3
#define BASIC 4
float get_aspect(vec2 deriv) {
return deriv.x != 0.0 ?
atan(deriv.y, -deriv.x) : PI / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0);
}
// MapLibre's legacy hillshade algorithm (Method 0)
void standard_hillshade(vec2 deriv) {
float azimuth = u_azimuths.x + PI;
float slope = atan(0.625 * length(deriv));
float aspect = get_aspect(deriv);
// Note: This implementation uses u_exaggeration as intensity, though it typically should be derived from u_altitudes.x.
float intensity = u_exaggeration;
// Scale the slope exponentially based on intensity
float base = 1.875 - intensity * 1.75;
float maxValue = 0.5 * PI;
float scaledSlope = abs(intensity - 0.5) > 1e-6 ? ((pow(base, slope) - 1.0) / (pow(base, maxValue) - 1.0)) * maxValue : slope;
float accent = cos(scaledSlope);
vec4 accent_color = (1.0 - accent) * u_accent * clamp(intensity * 2.0, 0.0, 1.0);
// Calculate shadow/highlight based on aspect
float shade = abs(mod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0);
vec4 shade_color = mix(u_shadows[0], u_highlights[0], shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0);
fragColor = accent_color * (1.0 - shade_color.a) + shade_color;
}
// Basic directional hillshade (Method 4)
void basic_hillshade(vec2 deriv) {
deriv = deriv * u_exaggeration * 2.0; // Exaggerate the slope derivative
float azimuth = u_azimuths.x + PI;
float cos_az = cos(azimuth);
float sin_az = sin(azimuth);
float cos_alt = cos(u_altitudes.x);
float sin_alt = sin(u_altitudes.x);
// Calculate the cosine of the angle between the light vector and the surface normal
float cang = (sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(deriv, deriv));
float shade = clamp(cang, 0.0, 1.0); // cang is the hillshade intensity [0, 1]
// Blend shadow and highlight based on intensity
if (shade > 0.5) {
fragColor = u_highlights[0] * (2.0 * shade - 1.0); // Highlight strength [0, 1]
} else {
fragColor = u_shadows[0] * (1.0 - 2.0 * shade); // Shadow strength [0, 1]
}
}
// Multidirectional hillshade (Method 3)
void multidirectional_hillshade(vec2 deriv) {
deriv = deriv * u_exaggeration * 2.0; // Exaggerate the slope derivative
fragColor = vec4(0, 0, 0, 0);
// Iterate through all light sources (up to u_num_lights, max 4)
for (int i = 0; i < 4; i++) {
if (i >= u_num_lights) break;
// Access altitude and azimuth from vec4 UBOs
float altitude = (i == 0) ? u_altitudes.x : (i == 1) ? u_altitudes.y : (i == 2) ? u_altitudes.z : u_altitudes.w;
float azimuth = (i == 0) ? u_azimuths.x : (i == 1) ? u_azimuths.y : (i == 2) ? u_azimuths.z : u_azimuths.w;
float cos_alt = cos(altitude);
float sin_alt = sin(altitude);
// Negate cos/sin azimuth for correct light direction in the normal calculation
float cos_az = -cos(azimuth);
float sin_az = -sin(azimuth);
// Calculate the cosine of the angle between the light vector and the surface normal
float cang = (sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) /
sqrt(1.0 + dot(deriv, deriv));
float shade = clamp(cang, 0.0, 1.0); // cang is the hillshade intensity [0, 1]
// Accumulate shadow/highlight contribution from each light
if (shade > 0.5) {
fragColor += u_highlights[i] * (2.0 * shade - 1.0) / float(u_num_lights);
} else {
fragColor += u_shadows[i] * (1.0 - 2.0 * shade) / float(u_num_lights);
}
}
}
// Combined shadow and highlight method (Method 1)
void combined_hillshade(vec2 deriv) {
// Only supports one light source (index 0)
deriv = deriv * u_exaggeration * 2.0;
float azimuth = u_azimuths.x + PI;
float cos_az = cos(azimuth);
float sin_az = sin(azimuth);
float cos_alt = cos(u_altitudes.x);
float sin_alt = sin(u_altitudes.x);
// Calculate the angle between the light vector and the surface normal
float cang = acos((sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) /
sqrt(1.0 + dot(deriv, deriv)));
cang = clamp(cang, 0.0, PI / 2.0); // Clamp angle to 90 degrees (half hemisphere)
// The "shade" and "highlight" components are calculated from cang (angle) and the magnitude of the slope
float shade = cang * atan(length(deriv)) * 4.0 / PI / PI;
float highlight = (PI / 2.0 - cang) * atan(length(deriv)) * 4.0 / PI / PI;
fragColor = u_shadows[0] * shade + u_highlights[0] * highlight;
}
// Igor's shadow/highlight method (Method 2)
void igor_hillshade(vec2 deriv) {
// Only supports one light source (index 0)
deriv = deriv * u_exaggeration * 2.0;
float aspect = get_aspect(deriv);
float azimuth = u_azimuths.x + PI;
// Slope strength is magnitude of slope vector, normalized to [0, 1]
float slope_strength = atan(length(deriv)) * 2.0 / PI;
// Aspect strength is difference between aspect and light azimuth, normalized to [0, 1]
float aspect_strength = 1.0 - abs(mod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0);
float shadow_strength = slope_strength * aspect_strength;
float highlight_strength = slope_strength * (1.0 - aspect_strength);
fragColor = u_shadows[0] * shadow_strength + u_highlights[0] * highlight_strength;
}
void main() {
vec4 pixel = texture(u_image, v_pos);
// Scale the derivative based on the mercator distortion at this latitude (v_pos.y)
float scaleFactor = cos(radians((u_latrange[0] - u_latrange[1]) * (1.0 - v_pos.y) + u_latrange[1]));
// The derivative is scaled back from [0, 1] texture range to world-space slope
// Texture range [0, 1] corresponds to slope range [-4, 4] (8.0 * 0.5 * deriv)
vec2 deriv = ((pixel.rg * 8.0) - 4.0) / scaleFactor;
// Dispatch to the selected hillshade method
switch (u_method) {
case BASIC:
basic_hillshade(deriv);
break;
case COMBINED:
combined_hillshade(deriv);
break;
case IGOR:
igor_hillshade(deriv);
break;
case MULTIDIRECTIONAL:
multidirectional_hillshade(deriv);
break;
case STANDARD:
default:
standard_hillshade(deriv);
break;
}
#ifdef OVERDRAW_INSPECTOR
fragColor = vec4(1.0);
#endif
}