diff --git a/examples/icon-sprite-webgl.js b/examples/icon-sprite-webgl.js index f54e2f3c80..936b761a18 100644 --- a/examples/icon-sprite-webgl.js +++ b/examples/icon-sprite-webgl.js @@ -79,6 +79,7 @@ class WebglPointsLayer extends VectorLayer { 'varying float v_year;', 'void main(void) {', + // color is interpolated based on year ' float ratio = clamp((v_year - 1950.0) / (2013.0 - 1950.0), 0.0, 1.1);', ' vec3 color = mix(vec3(' + formatColor(oldColor) + '),', @@ -88,6 +89,37 @@ class WebglPointsLayer extends VectorLayer { ' gl_FragColor.rgb *= gl_FragColor.a;', '}' ].join(' '), + hitVertexShader: [ + 'precision mediump float;', + + 'uniform mat4 u_projectionMatrix;', + 'uniform mat4 u_offsetScaleMatrix;', + 'uniform mat4 u_offsetRotateMatrix;', + 'attribute vec2 a_position;', + 'attribute float a_index;', + 'attribute vec4 a_hitColor;', + 'varying vec4 v_hitColor;', + + 'void main(void) {', + ' mat4 offsetMatrix = u_offsetScaleMatrix;', + ' float offsetX = a_index == 0.0 || a_index == 3.0 ? ', + ' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';', + ' float offsetY = a_index == 0.0 || a_index == 1.0 ? ', + ' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';', + ' vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);', + ' gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;', + ' v_hitColor = a_hitColor;', + '}' + ].join(' '), + hitFragmentShader: [ + 'precision mediump float;', + + 'varying vec4 v_hitColor;', + + 'void main(void) {', + ' gl_FragColor = v_hitColor;', + '}' + ].join(' '), texture: texture // FIXME: this doesn't work yet }); } diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index c07d20c72c..378c32fabd 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -18,74 +18,6 @@ import {create as createWebGLWorker} from '../../worker/webgl.js'; import {getUid} from '../../util.js'; import WebGLRenderTarget from '../../webgl/RenderTarget.js'; -const VERTEX_SHADER = ` - precision mediump float; - attribute vec2 a_position; - attribute vec2 a_texCoord; - attribute float a_rotateWithView; - attribute vec2 a_offsets; - attribute float a_opacity; - attribute vec4 a_color; - - uniform mat4 u_projectionMatrix; - uniform mat4 u_offsetScaleMatrix; - uniform mat4 u_offsetRotateMatrix; - - varying vec2 v_texCoord; - varying float v_opacity; - varying vec4 v_color; - - void main(void) { - mat4 offsetMatrix = u_offsetScaleMatrix; - if (a_rotateWithView == 1.0) { - offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; - } - vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0); - gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; - v_texCoord = a_texCoord; - v_opacity = a_opacity; - v_color = a_color; - }`; - -const FRAGMENT_SHADER = ` - precision mediump float; - - uniform sampler2D u_texture; - - varying vec2 v_texCoord; - varying float v_opacity; - varying vec4 v_color; - - void main(void) { - if (v_opacity == 0.0) { - discard; - } - vec4 textureColor = texture2D(u_texture, v_texCoord); - gl_FragColor = v_color * textureColor; - gl_FragColor.a *= v_opacity; - gl_FragColor.rgb *= gl_FragColor.a; - }`; - -const HIT_FRAGMENT_SHADER = ` - precision mediump float; - - uniform sampler2D u_texture; - - varying vec2 v_texCoord; - varying float v_opacity; - varying vec4 v_color; - - void main(void) { - if (v_opacity == 0.0) { - discard; - } - vec4 textureColor = texture2D(u_texture, v_texCoord); - if (textureColor.a < 0.1) { - discard; - } - gl_FragColor = v_color; - }`; - /** * @typedef {Object} CustomAttribute A description of a custom attribute to be passed on to the GPU, with a value different * for each feature. @@ -101,8 +33,10 @@ const HIT_FRAGMENT_SHADER = ` * * In the vertex shader as an `attribute` by prefixing it with `a_` * * In the fragment shader as a `varying` by prefixing it with `v_` * Please note that these can only be numerical values. - * @property {string} [vertexShader] Vertex shader source - * @property {string} [fragmentShader] Fragment shader source + * @property {string} vertexShader Vertex shader source, mandatory. + * @property {string} fragmentShader Fragment shader source, mandatory. + * @property {string} [hitVertexShader] Vertex shader source for hit detection rendering. + * @property {string} [hitFragmentShader] Fragment shader source for hit detection rendering. * @property {Object.} [uniforms] Uniform definitions for the post process steps * Please note that `u_texture` is reserved for the main texture slot. * @property {Array} [postProcesses] Post-processes definitions @@ -204,11 +138,9 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { /** * @param {import("../../layer/Vector.js").default} vectorLayer Vector layer. - * @param {Options=} [opt_options] Options. + * @param {Options=} options Options. */ - constructor(vectorLayer, opt_options) { - const options = opt_options || {}; - + constructor(vectorLayer, options) { const uniforms = options.uniforms || {}; uniforms.u_texture = getBlankImageData(); const projectionMatrixTransform = createTransform(); @@ -226,12 +158,12 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { this.indicesBuffer_ = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, DYNAMIC_DRAW); this.program_ = this.helper.getProgram( - options.fragmentShader || FRAGMENT_SHADER, - options.vertexShader || VERTEX_SHADER + options.fragmentShader, + options.vertexShader ); this.hitProgram_ = this.helper.getProgram( - HIT_FRAGMENT_SHADER, - VERTEX_SHADER + options.hitFragmentShader, + options.hitVertexShader ); const customAttributes = options.attributes ? @@ -271,7 +203,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { size: 1, type: AttributeType.FLOAT }, { - name: 'a_color', + name: 'a_hitColor', size: 4, type: AttributeType.FLOAT }, { @@ -429,6 +361,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { this.helper.makeProjectionTransform(frameState, projectionTransform); const features = vectorSource.getFeatures(); + + // here we anticipate the amount of render instructions that we well generate + // this can be done since we know that for normal render we only have x, y as base instructions, + // and x, y, r, g, b, a and featureUid for hit render instructions + // and we also know the amount of custom attributes to append to these const totalInstructionsCount = (2 + this.customAttributes.length) * features.length; if (!this.renderInstructions_ || this.renderInstructions_.length !== totalInstructionsCount) { this.renderInstructions_ = new Float32Array(totalInstructionsCount); diff --git a/src/ol/worker/webgl.js b/src/ol/worker/webgl.js index c88c294dcf..0a83cae266 100644 --- a/src/ol/worker/webgl.js +++ b/src/ol/worker/webgl.js @@ -14,7 +14,7 @@ const worker = self; worker.onmessage = event => { const received = event.data; if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) { - // This is specific to point features + // This is specific to point features (x, y, index) const baseVertexAttrsCount = 3; const baseInstructionsCount = 2; diff --git a/test/spec/ol/renderer/webgl/pointslayer.test.js b/test/spec/ol/renderer/webgl/pointslayer.test.js index e99ede9f34..5cae9957ea 100644 --- a/test/spec/ol/renderer/webgl/pointslayer.test.js +++ b/test/spec/ol/renderer/webgl/pointslayer.test.js @@ -7,7 +7,6 @@ import {get as getProjection} from '../../../../../src/ol/proj.js'; import ViewHint from '../../../../../src/ol/ViewHint.js'; import {WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js'; import {compose as composeTransform, create as createTransform} from '../../../../../src/ol/transform.js'; -import {getSymbolVertexShader} from '../../../../../src/ol/webgl/ShaderBuilder.js'; const baseFrameState = { viewHints: [], @@ -22,6 +21,54 @@ const baseFrameState = { pixelRatio: 1 }; +const simpleVertexShader = ` + precision mediump float; + uniform mat4 u_projectionMatrix; + uniform mat4 u_offsetScaleMatrix; + attribute vec2 a_position; + attribute float a_index; + + void main(void) { + mat4 offsetMatrix = u_offsetScaleMatrix; + float offsetX = a_index == 0.0 || a_index == 3.0 ? -2.0 : 2.0; + float offsetY = a_index == 0.0 || a_index == 1.0 ? -2.0 : 2.0; + vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0); + gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; + }`; +const simpleFragmentShader = ` + precision mediump float; + + void main(void) { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + }`; + +// these shaders support hit detection +// they have a built-in size value of 4 +const hitVertexShader = ` + precision mediump float; + uniform mat4 u_projectionMatrix; + uniform mat4 u_offsetScaleMatrix; + attribute vec2 a_position; + attribute float a_index; + attribute vec4 a_hitColor; + varying vec4 v_hitColor; + + void main(void) { + mat4 offsetMatrix = u_offsetScaleMatrix; + float offsetX = a_index == 0.0 || a_index == 3.0 ? -2.0 : 2.0; + float offsetY = a_index == 0.0 || a_index == 1.0 ? -2.0 : 2.0; + vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0); + gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; + v_hitColor = a_hitColor; + }`; +const hitFragmentShader = ` + precision mediump float; + varying vec4 v_hitColor; + + void main(void) { + gl_FragColor = v_hitColor; + }`; + describe('ol.renderer.webgl.PointsLayer', function() { describe('constructor', function() { @@ -43,7 +90,10 @@ describe('ol.renderer.webgl.PointsLayer', function() { const layer = new VectorLayer({ source: new VectorSource() }); - const renderer = new WebGLPointsLayerRenderer(layer); + const renderer = new WebGLPointsLayerRenderer(layer, { + vertexShader: simpleVertexShader, + fragmentShader: simpleFragmentShader + }); expect(renderer).to.be.a(WebGLPointsLayerRenderer); }); @@ -56,7 +106,10 @@ describe('ol.renderer.webgl.PointsLayer', function() { layer = new VectorLayer({ source: new VectorSource() }); - renderer = new WebGLPointsLayerRenderer(layer); + renderer = new WebGLPointsLayerRenderer(layer, { + vertexShader: simpleVertexShader, + fragmentShader: simpleFragmentShader + }); frameState = Object.assign({ size: [2, 2], extent: [-100, -100, 100, 100] @@ -95,6 +148,35 @@ describe('ol.renderer.webgl.PointsLayer', function() { }); }); + it('fills up the hit render buffer with 2 triangles per point', function(done) { + layer.getSource().addFeature(new Feature({ + geometry: new Point([10, 20]) + })); + layer.getSource().addFeature(new Feature({ + geometry: new Point([30, 40]) + })); + renderer.prepareFrame(frameState); + + const attributePerVertex = 8; + + renderer.worker_.addEventListener('message', function(event) { + if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) { + return; + } + if (!renderer.hitVerticesBuffer_.getArray()) { + return; + } + expect(renderer.hitVerticesBuffer_.getArray().length).to.eql(2 * 4 * attributePerVertex); + expect(renderer.indicesBuffer_.getArray().length).to.eql(2 * 6); + + expect(renderer.hitVerticesBuffer_.getArray()[0]).to.eql(10); + expect(renderer.hitVerticesBuffer_.getArray()[1]).to.eql(20); + expect(renderer.hitVerticesBuffer_.getArray()[4 * attributePerVertex + 0]).to.eql(30); + expect(renderer.hitVerticesBuffer_.getArray()[4 * attributePerVertex + 1]).to.eql(40); + done(); + }); + }); + it('clears the buffers when the features are gone', function(done) { const source = layer.getSource(); source.addFeature(new Feature({ @@ -163,9 +245,10 @@ describe('ol.renderer.webgl.PointsLayer', function() { }) }); renderer = new WebGLPointsLayerRenderer(layer, { - vertexShader: getSymbolVertexShader({ - size: 4 - }) + vertexShader: simpleVertexShader, + fragmentShader: simpleFragmentShader, + hitVertexShader: hitVertexShader, + hitFragmentShader: hitFragmentShader }); }); @@ -261,6 +344,8 @@ describe('ol.renderer.webgl.PointsLayer', function() { source: new VectorSource() }); const renderer = new WebGLPointsLayerRenderer(layer, { + vertexShader: simpleVertexShader, + fragmentShader: simpleFragmentShader }); const spyHelper = sinon.spy(renderer.helper, 'disposeInternal');