Webgl points renderer / fix hit detection
Due to the fact that the points renderer does not know for sure which attributes will be used for rendering, it is now mandatory to provide both vertex/fragment shaders for rendering AND hit detection. The hit detection shaders should expect having an `a_hitColor` that they should return to allow matching the feature uid. This will be all one eventually by shader builders under the hood.
This commit is contained in:
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps
|
||||
* Please note that `u_texture` is reserved for the main texture slot.
|
||||
* @property {Array<import("./Layer").PostProcessesOptions>} [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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user