Merge pull request #9090 from jahow/add-webgl-points-example
Add texture & color to the WebGL points renderer
This commit is contained in:
@@ -14,6 +14,7 @@ const VERTEX_SHADER = `
|
||||
attribute float a_rotateWithView;
|
||||
attribute vec2 a_offsets;
|
||||
attribute float a_opacity;
|
||||
attribute vec4 a_color;
|
||||
|
||||
uniform mat4 u_projectionMatrix;
|
||||
uniform mat4 u_offsetScaleMatrix;
|
||||
@@ -21,6 +22,7 @@ const VERTEX_SHADER = `
|
||||
|
||||
varying vec2 v_texCoord;
|
||||
varying float v_opacity;
|
||||
varying vec4 v_color;
|
||||
|
||||
void main(void) {
|
||||
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||
@@ -31,21 +33,26 @@ const VERTEX_SHADER = `
|
||||
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) {
|
||||
gl_FragColor.rgb = vec3(1.0, 1.0, 1.0);
|
||||
float alpha = v_opacity;
|
||||
if (alpha == 0.0) {
|
||||
if (v_opacity == 0.0) {
|
||||
discard;
|
||||
}
|
||||
gl_FragColor.a = alpha;
|
||||
vec4 textureColor = texture2D(u_texture, v_texCoord);
|
||||
gl_FragColor = v_color * textureColor;
|
||||
gl_FragColor.a *= v_opacity;
|
||||
gl_FragColor.rgb *= gl_FragColor.a;
|
||||
}`;
|
||||
|
||||
/**
|
||||
@@ -65,16 +72,25 @@ const FRAGMENT_SHADER = `
|
||||
* 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`.
|
||||
* source to compute the texture coordinates of each corner of the quad (without effect if no `texture` option defined). This is only done on source change.
|
||||
* The second argument is 0 for `u0` component, 1 for `v0`, 2 for `u1`, and 3 for `v1`.
|
||||
* @property {function(import("../../Feature").default, number, number):number} [colorCallback] Will be called on every feature in the
|
||||
* source to compute the color of each corner of the quad. This is only done on source change.
|
||||
* The second argument is 0 for bottom left, 1 for bottom right, 2 for top right and 3 for top left
|
||||
* The third argument is 0 for red, 1 for green, 2 for blue and 3 for alpha
|
||||
* The return value should be between 0 and 1.
|
||||
* @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.
|
||||
* Note: this is multiplied with the color of the point which can also have an alpha value < 1.
|
||||
* @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 {HTMLCanvasElement|HTMLImageElement|ImageData} [texture] Texture to use on points. `texCoordCallback` and `sizeCallback`
|
||||
* must be defined for this to have any effect.
|
||||
* @property {string} [vertexShader] Vertex shader source
|
||||
* @property {string} [fragmentShader] Fragment shader source
|
||||
* @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<PostProcessesOptions>} [postProcesses] Post-processes definitions
|
||||
*/
|
||||
|
||||
@@ -90,6 +106,25 @@ const FRAGMENT_SHADER = `
|
||||
* * `vec2 a_offsets`
|
||||
* * `float a_rotateWithView`
|
||||
* * `float a_opacity`
|
||||
* * `float a_color`
|
||||
*
|
||||
* The following uniform is used for the main texture: `u_texture`.
|
||||
*
|
||||
* Please note that the main shader output should have premultiplied alpha, otherwise the colors will be blended
|
||||
* additively.
|
||||
*
|
||||
* Points are rendered as quads with the following structure:
|
||||
*
|
||||
* (u0, v1) (u1, v1)
|
||||
* [3]----------[2]
|
||||
* |` |
|
||||
* | ` |
|
||||
* | ` |
|
||||
* | ` |
|
||||
* | ` |
|
||||
* | ` |
|
||||
* [0]----------[1]
|
||||
* (u0, v0) (u1, v0)
|
||||
*
|
||||
* This uses {@link module:ol/webgl/Helper~WebGLHelper} internally.
|
||||
*
|
||||
@@ -98,11 +133,13 @@ const FRAGMENT_SHADER = `
|
||||
* * 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;
|
||||
@@ -110,6 +147,7 @@ const FRAGMENT_SHADER = `
|
||||
*
|
||||
* varying vec2 v_texCoord;
|
||||
* varying float v_opacity;
|
||||
* varying vec4 v_color;
|
||||
*
|
||||
* void main(void) {
|
||||
* mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||
@@ -120,6 +158,7 @@ const FRAGMENT_SHADER = `
|
||||
* gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
||||
* v_texCoord = a_texCoord;
|
||||
* v_opacity = a_opacity;
|
||||
* v_color = a_color;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
@@ -127,16 +166,20 @@ 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) {
|
||||
* gl_FragColor.rgb = vec3(1.0, 1.0, 1.0);
|
||||
* float alpha = v_opacity;
|
||||
* if (alpha == 0.0) {
|
||||
* if (v_opacity == 0.0) {
|
||||
* discard;
|
||||
* }
|
||||
* gl_FragColor.a = alpha;
|
||||
* vec4 textureColor = texture2D(u_texture, v_texCoord);
|
||||
* gl_FragColor = v_color * textureColor;
|
||||
* gl_FragColor.a *= v_opacity;
|
||||
* gl_FragColor.rgb *= gl_FragColor.a;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
@@ -153,9 +196,11 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
|
||||
|
||||
const options = opt_options || {};
|
||||
|
||||
const uniforms = options.uniforms || {};
|
||||
uniforms.u_texture = options.texture || this.getDefaultTexture();
|
||||
this.helper_ = new WebGLHelper({
|
||||
postProcesses: options.postProcesses,
|
||||
uniforms: options.uniforms
|
||||
uniforms: uniforms
|
||||
});
|
||||
|
||||
this.sourceRevision_ = -1;
|
||||
@@ -183,6 +228,9 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
|
||||
this.texCoordCallback_ = options.texCoordCallback || function(feature, index) {
|
||||
return index < 2 ? 0 : 1;
|
||||
};
|
||||
this.colorCallback_ = options.colorCallback || function(feature, index, component) {
|
||||
return 1;
|
||||
};
|
||||
this.rotateWithViewCallback_ = options.rotateWithViewCallback || function() {
|
||||
return false;
|
||||
};
|
||||
@@ -218,6 +266,8 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
|
||||
const vectorLayer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer());
|
||||
const vectorSource = /** @type {import("../../source/Vector.js").default} */ (vectorLayer.getSource());
|
||||
|
||||
const stride = 12;
|
||||
|
||||
this.helper_.prepareDraw(frameState);
|
||||
|
||||
if (this.sourceRevision_ < vectorSource.getRevision()) {
|
||||
@@ -242,14 +292,29 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
|
||||
const size = this.sizeCallback_(feature);
|
||||
const opacity = this.opacityCallback_(feature);
|
||||
const rotateWithView = this.rotateWithViewCallback_(feature) ? 1 : 0;
|
||||
const stride = 8;
|
||||
const v0_r = this.colorCallback_(feature, 0, 0);
|
||||
const v0_g = this.colorCallback_(feature, 0, 1);
|
||||
const v0_b = this.colorCallback_(feature, 0, 2);
|
||||
const v0_a = this.colorCallback_(feature, 0, 3);
|
||||
const v1_r = this.colorCallback_(feature, 1, 0);
|
||||
const v1_g = this.colorCallback_(feature, 1, 1);
|
||||
const v1_b = this.colorCallback_(feature, 1, 2);
|
||||
const v1_a = this.colorCallback_(feature, 1, 3);
|
||||
const v2_r = this.colorCallback_(feature, 2, 0);
|
||||
const v2_g = this.colorCallback_(feature, 2, 1);
|
||||
const v2_b = this.colorCallback_(feature, 2, 2);
|
||||
const v2_a = this.colorCallback_(feature, 2, 3);
|
||||
const v3_r = this.colorCallback_(feature, 3, 0);
|
||||
const v3_g = this.colorCallback_(feature, 3, 1);
|
||||
const v3_b = this.colorCallback_(feature, 3, 2);
|
||||
const v3_a = this.colorCallback_(feature, 3, 3);
|
||||
const baseIndex = this.verticesBuffer_.getArray().length / stride;
|
||||
|
||||
this.verticesBuffer_.getArray().push(
|
||||
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
|
||||
x, y, -size / 2, -size / 2, u0, v0, opacity, rotateWithView, v0_r, v0_g, v0_b, v0_a,
|
||||
x, y, +size / 2, -size / 2, u1, v0, opacity, rotateWithView, v1_r, v1_g, v1_b, v1_a,
|
||||
x, y, +size / 2, +size / 2, u1, v1, opacity, rotateWithView, v2_r, v2_g, v2_b, v2_a,
|
||||
x, y, -size / 2, +size / 2, u0, v1, opacity, rotateWithView, v3_r, v3_g, v3_b, v3_a
|
||||
);
|
||||
this.indicesBuffer_.getArray().push(
|
||||
baseIndex, baseIndex + 1, baseIndex + 3,
|
||||
@@ -263,11 +328,12 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
|
||||
this.helper_.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
|
||||
|
||||
const bytesPerFloat = Float32Array.BYTES_PER_ELEMENT;
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * 8, 0);
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.OFFSETS, 2, FLOAT, bytesPerFloat * 8, bytesPerFloat * 2);
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.TEX_COORD, 2, FLOAT, bytesPerFloat * 8, bytesPerFloat * 4);
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.OPACITY, 1, FLOAT, bytesPerFloat * 8, bytesPerFloat * 6);
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.ROTATE_WITH_VIEW, 1, FLOAT, bytesPerFloat * 8, bytesPerFloat * 7);
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * stride, 0);
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.OFFSETS, 2, FLOAT, bytesPerFloat * stride, bytesPerFloat * 2);
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.TEX_COORD, 2, FLOAT, bytesPerFloat * stride, bytesPerFloat * 4);
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.OPACITY, 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 6);
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.ROTATE_WITH_VIEW, 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 7);
|
||||
this.helper_.enableAttributeArray(DefaultAttrib.COLOR, 4, FLOAT, bytesPerFloat * stride, bytesPerFloat * 8);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -280,6 +346,18 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
|
||||
getShaderCompileErrors() {
|
||||
return this.helper_.getShaderCompileErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a texture of 1x1 pixel, white
|
||||
* @private
|
||||
* @return {ImageData} Image data.
|
||||
*/
|
||||
getDefaultTexture() {
|
||||
const canvas = document.createElement('canvas');
|
||||
const image = canvas.getContext('2d').createImageData(1, 1);
|
||||
image.data[0] = image.data[1] = image.data[2] = image.data[3] = 255;
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
export default WebGLPointsLayerRenderer;
|
||||
|
||||
@@ -57,11 +57,12 @@ export const DefaultAttrib = {
|
||||
TEX_COORD: 'a_texCoord',
|
||||
OPACITY: 'a_opacity',
|
||||
ROTATE_WITH_VIEW: 'a_rotateWithView',
|
||||
OFFSETS: 'a_offsets'
|
||||
OFFSETS: 'a_offsets',
|
||||
COLOR: 'a_color'
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {number|Array<number>|HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} UniformLiteralValue
|
||||
* @typedef {number|Array<number>|HTMLCanvasElement|HTMLImageElement|ImageData} UniformLiteralValue
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -527,7 +528,7 @@ class WebGLHelper extends Disposable {
|
||||
value = typeof uniform.value === 'function' ? uniform.value(frameState) : uniform.value;
|
||||
|
||||
// apply value based on type
|
||||
if (value instanceof HTMLCanvasElement || value instanceof ImageData) {
|
||||
if (value instanceof HTMLCanvasElement || value instanceof HTMLImageElement || value instanceof ImageData) {
|
||||
// create a texture & put data
|
||||
if (!uniform.texture) {
|
||||
uniform.texture = gl.createTexture();
|
||||
@@ -537,13 +538,7 @@ class WebGLHelper extends Disposable {
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
|
||||
if (value instanceof ImageData) {
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, value.width, value.height, 0,
|
||||
gl.UNSIGNED_BYTE, new Uint8Array(value.data));
|
||||
} else {
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, value);
|
||||
}
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, value);
|
||||
|
||||
// fill texture slots by increasing index
|
||||
gl.uniform1i(this.getUniformLocation(uniform.name), textureSlot++);
|
||||
|
||||
Reference in New Issue
Block a user