diff --git a/rendering/cases/heatmap-layer/expected.png b/rendering/cases/heatmap-layer/expected.png index d07e4989d2..6426ca6f3f 100644 Binary files a/rendering/cases/heatmap-layer/expected.png and b/rendering/cases/heatmap-layer/expected.png differ diff --git a/rendering/test.js b/rendering/test.js index 096b2b5564..d6d0e93b61 100755 --- a/rendering/test.js +++ b/rendering/test.js @@ -356,7 +356,7 @@ if (require.main === module) { option('puppeteer-args', { describe: 'Additional args for Puppeteer', type: 'array', - default: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-gpu-blacklist'] : [] + default: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [] }). parse(); diff --git a/src/ol/layer/Heatmap.js b/src/ol/layer/Heatmap.js index 15dfa21ecd..dac335798e 100644 --- a/src/ol/layer/Heatmap.js +++ b/src/ol/layer/Heatmap.js @@ -227,106 +227,31 @@ class Heatmap extends VectorLayer { }`, fragmentShader: ` precision mediump float; - uniform float u_opacity; uniform float u_resolution; - uniform float u_blur; + uniform float u_blurSlope; varying vec2 v_texCoord; varying float v_opacity; void main(void) { - gl_FragColor.rgb = vec3(1.0, 1.0, 1.0); vec2 texCoord = v_texCoord * 2.0 - vec2(1.0, 1.0); float sqRadius = texCoord.x * texCoord.x + texCoord.y * texCoord.y; - float alpha = 1.0 - sqRadius * sqRadius * v_opacity; - if (alpha <= 0.0) { - discard; - } - gl_FragColor.a = alpha * 0.30 + 1.0 / u_resolution; + float value = (1.0 - sqrt(sqRadius)) * u_blurSlope; + float alpha = smoothstep(0.0, 1.0, value) * v_opacity; + gl_FragColor = vec4(1.0, 1.0, 1.0, alpha); }`, uniforms: { u_size: function() { - return this.get(Property.RADIUS) * 10; + return (this.get(Property.RADIUS) + this.get(Property.BLUR)) * 2; + }.bind(this), + u_blurSlope: function() { + return this.get(Property.RADIUS) / Math.max(1, this.get(Property.BLUR)); }.bind(this), u_resolution: function(frameState) { return frameState.viewState.resolution; } }, postProcesses: [ - { - fragmentShader: ` - precision mediump float; - - uniform sampler2D u_image; - uniform sampler2D u_gradientTexture; - uniform vec2 u_blurSize; - - varying vec2 v_texCoord; - varying vec2 v_screenCoord; - - void main() { - float weights[9]; - weights[0] = weights[8] = 0.05; - weights[1] = weights[7] = 0.09; - weights[2] = weights[6] = 0.12; - weights[3] = weights[5] = 0.15; - weights[4] = 0.18; - vec4 sum = vec4(0.0); - vec2 offset; - vec4 center = texture2D(u_image, v_texCoord); - - // vertical blur - offset = vec2(0.0, u_blurSize.y * 1.0); - sum += texture2D(u_image, v_texCoord + offset) * weights[0]; - offset = vec2(0.0, u_blurSize.y * 0.75); - sum += texture2D(u_image, v_texCoord + offset) * weights[1]; - offset = vec2(0.0, u_blurSize.y * 0.5); - sum += texture2D(u_image, v_texCoord + offset) * weights[2]; - offset = vec2(0.0, u_blurSize.y * 0.25); - sum += texture2D(u_image, v_texCoord + offset) * weights[3]; - offset = vec2(0.0, u_blurSize.y * 0.0); - sum += texture2D(u_image, v_texCoord + offset) * weights[4]; - offset = vec2(0.0, u_blurSize.y * -0.25); - sum += texture2D(u_image, v_texCoord + offset) * weights[5]; - offset = vec2(0.0, u_blurSize.y * -0.5); - sum += texture2D(u_image, v_texCoord + offset) * weights[6]; - offset = vec2(0.0, u_blurSize.y * -0.75); - sum += texture2D(u_image, v_texCoord + offset) * weights[7]; - offset = vec2(0.0, u_blurSize.y * -1.0); - sum += center * weights[8]; - - // horizontal blur - offset = vec2(u_blurSize.x * 1.0, 0.0); - sum += texture2D(u_image, v_texCoord + offset) * weights[0]; - offset = vec2(u_blurSize.x * 0.75, 0.0); - sum += texture2D(u_image, v_texCoord + offset) * weights[1]; - offset = vec2(u_blurSize.x * 0.5, 0.0); - sum += texture2D(u_image, v_texCoord + offset) * weights[2]; - offset = vec2(u_blurSize.x * 0.25, 0.0); - sum += texture2D(u_image, v_texCoord + offset) * weights[3]; - offset = vec2(u_blurSize.x * 0.0, 0.0); - sum += texture2D(u_image, v_texCoord + offset) * weights[4]; - offset = vec2(u_blurSize.x * -0.25, 0.0); - sum += texture2D(u_image, v_texCoord + offset) * weights[5]; - offset = vec2(u_blurSize.x * -0.5, 0.0); - sum += texture2D(u_image, v_texCoord + offset) * weights[6]; - offset = vec2(u_blurSize.x * -0.75, 0.0); - sum += texture2D(u_image, v_texCoord + offset) * weights[7]; - offset = vec2(u_blurSize.x * -1.0, 0.0); - sum += center * weights[8]; - - gl_FragColor = sum * 0.5; - }`, - scaleRatio: 0.5, - uniforms: { - u_blurSize: function(frameState) { - return [ - this.get(Property.BLUR) / frameState.size[0], - this.get(Property.BLUR) / frameState.size[1] - ]; - }.bind(this) - } - }, { fragmentShader: ` precision mediump float; @@ -339,15 +264,16 @@ class Heatmap extends VectorLayer { void main() { vec4 color = texture2D(u_image, v_texCoord); - gl_FragColor.rgb = texture2D(u_gradientTexture, vec2(0.5, color.a)).rgb; gl_FragColor.a = color.a; + gl_FragColor.rgb = texture2D(u_gradientTexture, vec2(0.5, color.a)).rgb; + gl_FragColor.rgb *= gl_FragColor.a; }`, uniforms: { u_gradientTexture: this.gradient_ } } ], - sizeCallback: function(feature) { + opacityCallback: function(feature) { return this.weightFunction_(feature); }.bind(this) }); diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index a64f36f74b..02a69798c3 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -4,7 +4,7 @@ import LayerRenderer from '../Layer'; import WebGLArrayBuffer from '../../webgl/Buffer'; import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT} from '../../webgl'; -import WebGLHelper, {DefaultAttrib, DefaultUniform} from '../../webgl/Helper'; +import WebGLHelper, {DefaultAttrib} from '../../webgl/Helper'; import GeometryType from '../../geom/GeometryType'; const VERTEX_SHADER = ` @@ -35,14 +35,13 @@ const VERTEX_SHADER = ` const FRAGMENT_SHADER = ` precision mediump float; - uniform float u_opacity; varying vec2 v_texCoord; varying float v_opacity; void main(void) { gl_FragColor.rgb = vec3(1.0, 1.0, 1.0); - float alpha = u_opacity * v_opacity; + float alpha = v_opacity; if (alpha == 0.0) { discard; } @@ -61,10 +60,18 @@ const FRAGMENT_SHADER = ` /** * @typedef {Object} Options * @property {function(import("../../Feature").default):number} [sizeCallback] Will be called on every feature in the - * source to compute the size of the quad on screen (in pixels). This only done on source change. + * source to compute the size of the quad on screen (in pixels). This is only done on source change. * @property {function(import("../../Feature").default, number):number} [coordCallback] Will be called on every feature in the - * source to compute the coordinate of the quad center on screen (in pixels). This only done on source change. + * source to compute the coordinate of the quad center on screen (in pixels). This is only done on source change. * The second argument is 0 for `x` component and 1 for `y`. + * @property {function(import("../../Feature").default, number):number} [texCoordCallback] Will be called on every feature in the + * source to compute the texture coordinates of each corner of the quad. This is only done on source change. + * The second argument is 0 for `u0` component, 1 or `v0`, 2 for `u1`, and 3 for `v1`. + * @property {function(import("../../Feature").default):number} [opacityCallback] Will be called on every feature in the + * source to compute the opacity of the quad on screen (from 0 to 1). This is only done on source change. + * @property {function(import("../../Feature").default):boolean} [rotateWithViewCallback] Will be called on every feature in the + * source to compute whether the quad on screen must stay upwards (`false`) or follow the view rotation (`true`). + * This is only done on source change. * @property {string} [vertexShader] Vertex shader source * @property {string} [fragmentShader] Fragment shader source * @property {Object.} [uniforms] Uniform definitions for the post process steps @@ -119,14 +126,13 @@ const FRAGMENT_SHADER = ` * * Fragment shader: * ``` * precision mediump float; - * uniform float u_opacity; * * varying vec2 v_texCoord; * varying float v_opacity; * * void main(void) { * gl_FragColor.rgb = vec3(1.0, 1.0, 1.0); - * float alpha = u_opacity * v_opacity; + * float alpha = v_opacity; * if (alpha == 0.0) { * discard; * } @@ -164,13 +170,22 @@ class WebGLPointsLayerRenderer extends LayerRenderer { this.helper_.useProgram(this.program_); - this.sizeCallback_ = options.sizeCallback || function(feature) { + this.sizeCallback_ = options.sizeCallback || function() { return 1; }; this.coordCallback_ = options.coordCallback || function(feature, index) { const geom = /** @type {import("../../geom/Point").default} */ (feature.getGeometry()); return geom.getCoordinates()[index]; }; + this.opacityCallback_ = options.opacityCallback || function() { + return 1; + }; + this.texCoordCallback_ = options.texCoordCallback || function(feature, index) { + return index < 2 ? 0 : 1; + }; + this.rotateWithViewCallback_ = options.rotateWithViewCallback || function() { + return false; + }; } /** @@ -184,10 +199,16 @@ class WebGLPointsLayerRenderer extends LayerRenderer { * @inheritDoc */ renderFrame(frameState, layerState) { - this.helper_.setUniformFloatValue(DefaultUniform.OPACITY, layerState.opacity); this.helper_.drawElements(0, this.indicesBuffer_.getArray().length); this.helper_.finalizeDraw(frameState); - return this.helper_.getCanvas(); + const canvas = this.helper_.getCanvas(); + + const opacity = layerState.opacity; + if (opacity !== canvas.style.opacity) { + canvas.style.opacity = opacity; + } + + return canvas; } /** @@ -214,15 +235,21 @@ class WebGLPointsLayerRenderer extends LayerRenderer { } const x = this.coordCallback_(feature, 0); const y = this.coordCallback_(feature, 1); + const u0 = this.texCoordCallback_(feature, 0); + const v0 = this.texCoordCallback_(feature, 1); + const u1 = this.texCoordCallback_(feature, 2); + const v1 = this.texCoordCallback_(feature, 3); const size = this.sizeCallback_(feature); + const opacity = this.opacityCallback_(feature); + const rotateWithView = this.rotateWithViewCallback_(feature) ? 1 : 0; const stride = 8; const baseIndex = this.verticesBuffer_.getArray().length / stride; this.verticesBuffer_.getArray().push( - x, y, -size / 2, -size / 2, 0, 0, 1, 1, - x, y, +size / 2, -size / 2, 1, 0, 1, 1, - x, y, +size / 2, +size / 2, 1, 1, 1, 1, - x, y, -size / 2, +size / 2, 0, 1, 1, 1 + x, y, -size / 2, -size / 2, u0, v0, opacity, rotateWithView, + x, y, +size / 2, -size / 2, u1, v0, opacity, rotateWithView, + x, y, +size / 2, +size / 2, u1, v1, opacity, rotateWithView, + x, y, -size / 2, +size / 2, u0, v1, opacity, rotateWithView ); this.indicesBuffer_.getArray().push( baseIndex, baseIndex + 1, baseIndex + 3, diff --git a/src/ol/webgl/Helper.js b/src/ol/webgl/Helper.js index b0c8039e87..af5daa0fe7 100644 --- a/src/ol/webgl/Helper.js +++ b/src/ol/webgl/Helper.js @@ -43,8 +43,7 @@ export const ShaderType = { export const DefaultUniform = { PROJECTION_MATRIX: 'u_projectionMatrix', OFFSET_SCALE_MATRIX: 'u_offsetScaleMatrix', - OFFSET_ROTATION_MATRIX: 'u_offsetRotateMatrix', - OPACITY: 'u_opacity' + OFFSET_ROTATION_MATRIX: 'u_offsetRotateMatrix' }; /** @@ -89,6 +88,7 @@ export const DefaultAttrib = { /** * @typedef {Object} UniformInternalDescription * @property {string} name Name + * @property {UniformLiteralValue=} value Value * @property {WebGLTexture} [texture] Texture * @private */ @@ -129,7 +129,7 @@ export const DefaultAttrib = { * Uniforms are defined using the `uniforms` option and can either be explicit values or callbacks taking the frame state as argument. * You can also change their value along the way like so: * ```js - * this.context.setUniformFloatValue(DefaultUniform.OPACITY, layerState.opacity); + * this.context.setUniformFloatValue('u_value', valueAsNumber); * ``` * * ### Defining post processing passes @@ -290,13 +290,13 @@ class WebGLHelper extends Disposable { * @private * @type {Object.} */ - this.uniformLocations_; + this.uniformLocations_ = {}; /** * @private * @type {Object.} */ - this.attribLocations_; + this.attribLocations_ = {}; /** * Holds info about custom uniforms used in the post processing pass. @@ -544,7 +544,7 @@ class WebGLHelper extends Disposable { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, value); } - // fill texture slots + // fill texture slots by increasing index gl.uniform1i(this.getUniformLocation(uniform.name), textureSlot++); } else if (Array.isArray(value)) { @@ -701,8 +701,13 @@ class WebGLHelper extends Disposable { * @api */ enableAttributeArray(attribName, size, type, stride, offset) { - this.getGL().enableVertexAttribArray(this.getAttributeLocation(attribName)); - this.getGL().vertexAttribPointer(this.getAttributeLocation(attribName), size, type, + const location = this.getAttributeLocation(attribName); + // the attribute has not been found in the shaders; do not enable it + if (location < 0) { + return; + } + this.getGL().enableVertexAttribArray(location); + this.getGL().vertexAttribPointer(location, size, type, false, stride, offset); } diff --git a/src/ol/webgl/PostProcessingPass.js b/src/ol/webgl/PostProcessingPass.js index 024ea651f8..1563079605 100644 --- a/src/ol/webgl/PostProcessingPass.js +++ b/src/ol/webgl/PostProcessingPass.js @@ -28,6 +28,7 @@ const DEFAULT_FRAGMENT_SHADER = ` void main() { gl_FragColor = texture2D(u_image, v_texCoord); + gl_FragColor.rgb *= gl_FragColor.a; } `; @@ -54,6 +55,10 @@ const DEFAULT_FRAGMENT_SHADER = ` * This class is used to define Post Processing passes with custom shaders and uniforms. * This is used internally by {@link module:ol/webgl/Helper~WebGLHelper}. * + * Please note that the final output on the DOM canvas is expected to have premultiplied alpha, which means that + * a pixel which is 100% red with an opacity of 50% must have a color of (r=0.5, g=0, b=0, a=0.5). + * Failing to provide pixel colors with premultiplied alpha will result in render anomalies. + * * Default shaders are shown hereafter: * * * Vertex shader: @@ -86,6 +91,7 @@ const DEFAULT_FRAGMENT_SHADER = ` * * void main() { * gl_FragColor = texture2D(u_image, v_texCoord); + * gl_FragColor.rgb *= gl_FragColor.a; * } * ``` * @@ -220,7 +226,7 @@ class WebGLPostProcessingPass { gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.clear(gl.COLOR_BUFFER_BIT); gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); gl.viewport(0, 0, canvas.width, canvas.height); gl.bindBuffer(gl.ARRAY_BUFFER, this.renderTargetVerticesBuffer_);