Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hillshading accent color #725

Closed
utagawal opened this issue Nov 17, 2022 · 10 comments
Closed

Hillshading accent color #725

utagawal opened this issue Nov 17, 2022 · 10 comments

Comments

@utagawal
Copy link
Contributor

For the accent color implementation in hillshading , maybe usefull to take inspiration from here : src/shaders/hillshade.fragment.glsl
from https://github.com/mapbox/mapbox-gl-js/pull/5286/files#diff-a826d96571ff241535a6bac52167defaabc13c91883e2529cf84608c70ca9b79
(but it's using webGL)

@utagawal
Copy link
Contributor Author

utagawal commented Dec 4, 2022

In the current code - didn't find also where the highlight and shadow colors colors are gathered and applied.

It should probably be in apply.js around
const hillshadeLayer = setupHillshadeLayer(...

For the accent color implementation the example code from maplibre is extracted below :

// we multiply the slope by a scale factor based on the cosin of the pixel's approximate latitude
    float scaleFactor = cos(radians((u_latrange[0] - u_latrange[1]) * (1.0 - v_pos.y) + u_latrange[1]));
    float slope = atan(1.25 * length(deriv)) / scaleFactor;
    float aspect = deriv.x != 0.0 ? atan(deriv.y, -deriv.x) : PI / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0);

    float intensity = u_light.x;
    // We add PI to make this property match the global light object, which adds PI/2 to the light's azimuthal
    // position property to account for 0deg corresponding to north/the top of the viewport in the style spec
    // and the original shader was written to accept (-illuminationDirection - 90) as the azimuthal.
    float azimuth = u_light.y + PI;
    float polar = u_light.z;

    float accent = cos(slope);
    vec4 accent_color = intensity * clamp((1.0 - accent) * 2.0, 0.0, 1.0) * u_accent;
    float shade = abs(mod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0);
    vec4 shade_color = mix(u_shadow, u_highlight, shade) * slope * sin(polar) * mix(0.0, 2.0, intensity);
    gl_FragColor = accent_color * (1.0 - shade_color.a) + shade_color;

@ahocevar
Copy link
Member

ahocevar commented Dec 4, 2022

The current implementation uses the same technique as the https://openlayers.org/en/latest/examples/shaded-relief.html example. No colors can be configured. To make the result look good, a version of OpenLayers that includes openlayers/openlayers#14282 is needed. That was a big change and makes a big difference.

If someone can provide a pull request that implements the color options, it would be greatly appreciated.

@ahocevar
Copy link
Member

ahocevar commented Dec 4, 2022

Implementation hints:

The rendering transformation takes place here:

/**
* Generates a shaded relief image given elevation data. Uses a 3x3
* neighborhood for determining slope and aspect.
* @param {Array<ImageData>} inputs Array of input images.
* @param {Object} data Data added in the "beforeoperations" event.
* @return {ImageData} Output image.
*/
export function hillshade(inputs, data) {
const elevationImage = inputs[0];
const width = elevationImage.width;
const height = elevationImage.height;
const elevationData = elevationImage.data;
const shadeData = new Uint8ClampedArray(elevationData.length);
const dp = data.resolution * 2;
const maxX = width - 1;
const maxY = height - 1;
const pixel = [0, 0, 0, 0];
const twoPi = 2 * Math.PI;
const halfPi = Math.PI / 2;
const sunEl = (Math.PI * data.sunEl) / 180;
const sunAz = (Math.PI * data.sunAz) / 180;
const cosSunEl = Math.cos(sunEl);
const sinSunEl = Math.sin(sunEl);
let pixelX,
pixelY,
x0,
x1,
y0,
y1,
offset,
z0,
z1,
dzdx,
dzdy,
slope,
aspect,
cosIncidence,
scaled;
function calculateElevation(pixel) {
// The method used to extract elevations from the DEM.
// In this case the format used is
// red + green * 2 + blue * 3
//
// Other frequently used methods include the Mapbox format
// (red * 256 * 256 + green * 256 + blue) * 0.1 - 10000
// and the Terrarium format
// (red * 256 + green + blue / 256) - 32768
//
return (pixel[0] * 256 * 256 + pixel[1] * 256 + pixel[2]) * 0.1 - 10000;
}
for (pixelY = 0; pixelY <= maxY; ++pixelY) {
y0 = pixelY === 0 ? 0 : pixelY - 1;
y1 = pixelY === maxY ? maxY : pixelY + 1;
for (pixelX = 0; pixelX <= maxX; ++pixelX) {
x0 = pixelX === 0 ? 0 : pixelX - 1;
x1 = pixelX === maxX ? maxX : pixelX + 1;
// determine elevation for (x0, pixelY)
offset = (pixelY * width + x0) * 4;
pixel[0] = elevationData[offset];
pixel[1] = elevationData[offset + 1];
pixel[2] = elevationData[offset + 2];
pixel[3] = elevationData[offset + 3];
z0 = data.vert * calculateElevation(pixel);
// determine elevation for (x1, pixelY)
offset = (pixelY * width + x1) * 4;
pixel[0] = elevationData[offset];
pixel[1] = elevationData[offset + 1];
pixel[2] = elevationData[offset + 2];
pixel[3] = elevationData[offset + 3];
z1 = data.vert * calculateElevation(pixel);
dzdx = (z1 - z0) / dp;
// determine elevation for (pixelX, y0)
offset = (y0 * width + pixelX) * 4;
pixel[0] = elevationData[offset];
pixel[1] = elevationData[offset + 1];
pixel[2] = elevationData[offset + 2];
pixel[3] = elevationData[offset + 3];
z0 = data.vert * calculateElevation(pixel);
// determine elevation for (pixelX, y1)
offset = (y1 * width + pixelX) * 4;
pixel[0] = elevationData[offset];
pixel[1] = elevationData[offset + 1];
pixel[2] = elevationData[offset + 2];
pixel[3] = elevationData[offset + 3];
z1 = data.vert * calculateElevation(pixel);
dzdy = (z1 - z0) / dp;
slope = Math.atan(Math.sqrt(dzdx * dzdx + dzdy * dzdy));
aspect = Math.atan2(dzdy, -dzdx);
if (aspect < 0) {
aspect = halfPi - aspect;
} else if (aspect > halfPi) {
aspect = twoPi - aspect + halfPi;
} else {
aspect = halfPi - aspect;
}
cosIncidence =
sinSunEl * Math.cos(slope) +
cosSunEl * Math.sin(slope) * Math.cos(sunAz - aspect);
offset = (pixelY * width + pixelX) * 4;
scaled = 255 * cosIncidence;
shadeData[offset] = scaled;
shadeData[offset + 1] = scaled;
shadeData[offset + 2] = scaled;
shadeData[offset + 3] = elevationData[offset + 3] * data.opacity;
}
}
return new ImageData(shadeData, width, height);
}

The data passed to that function is calculated here:

hillshadeLayer.getSource().on('beforeoperations', function (event) {
const data = event.data;
data.resolution = event.resolution;
const zoom = getZoomForResolution(
event.resolution,
options.resolutions || defaultResolutions
);
data.vert =
5 *
getValue(
glLayer,
'paint',
'hillshade-exaggeration',
zoom,
emptyObj,
functionCache
);
data.sunAz = getValue(
glLayer,
'paint',
'hillshade-illumination-direction',
zoom,
emptyObj,
functionCache
);
data.sunEl = 35;
data.opacity = 0.15;
});

This is where the *-color options would have to be added to the data object.

@utagawal
Copy link
Contributor Author

utagawal commented Dec 6, 2022

Merci beaucoup Virgil ! If you have some additional bandwidth we are also looking at the line-pattern implementation #713

@V-Roger
Copy link
Contributor

V-Roger commented Dec 6, 2022

@ahocevar Hey there, I took the liberty of jumping in to rescue @utagawal :)

I couldn't find a contribution guide here, so hope the PR is alright. Feel free to request changes!

@utagawal
Copy link
Contributor Author

Should be ready to be published in a stable release version

@utagawal
Copy link
Contributor Author

utagawal commented Dec 21, 2022

@ahocevar We'd like to use the functionality on our website, any chance you could include it in a new release version of the plugin soon ?

@ahocevar
Copy link
Member

@utagawal Please be patient. I have a lot of work that pays my bills to finish before I can maintain this and other libraries in my free time. Speaking of, I'm grateful for your one-time sponsoring a while ago. I used those funds for adding the first iteration of the hillshading capability, which was quite an involved change that also required changes to the OpenLayers core library.

@ahocevar
Copy link
Member

@utagawal I published v9.3.0.

@utagawal
Copy link
Contributor Author

Many thanks @ahocevar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants