From 52279967c4a744e4faf8018d1adecdd4ab097d0e Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Thu, 9 Jun 2022 13:06:08 +0200 Subject: [PATCH] WebGL / Reorganize VectorLayerRenderer options, update example Now different attributes can be provided for each type of geometry. Also updated the example to accomodate for this and use the default shaders. --- examples/webgl-vector-layer.js | 172 +++++---------------------- src/ol/renderer/webgl/VectorLayer.js | 117 +++++++++++++----- 2 files changed, 114 insertions(+), 175 deletions(-) diff --git a/examples/webgl-vector-layer.js b/examples/webgl-vector-layer.js index 320faecaac..c1fee56403 100644 --- a/examples/webgl-vector-layer.js +++ b/examples/webgl-vector-layer.js @@ -6,159 +6,43 @@ import TileLayer from '../src/ol/layer/WebGLTile.js'; import VectorSource from '../src/ol/source/Vector.js'; import View from '../src/ol/View.js'; import WebGLVectorLayerRenderer from '../src/ol/renderer/webgl/VectorLayer.js'; +import { + DefaultAttributes, + packColor, +} from '../src/ol/renderer/webgl/shaders.js'; import {asArray} from '../src/ol/color.js'; class WebGLLayer extends Layer { createRenderer() { return new WebGLVectorLayerRenderer(this, { className: this.getClassName(), - polygonVertexShader: ` - precision mediump float; - uniform mat4 u_projectionMatrix; - attribute vec2 a_position; - attribute float a_color; - - varying vec3 v_color; - - void main(void) { - gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0); - v_color = vec3( - floor(a_color / 256.0 / 256.0) / 256.0, - fract(floor(a_color / 256.0) / 256.0), - fract(a_color / 256.0) - ); - }`, - polygonFragmentShader: ` - precision mediump float; - varying vec3 v_color; - - void main(void) { - gl_FragColor = vec4(v_color.rgb, 1.0); - gl_FragColor *= 0.75; - }`, - lineStringVertexShader: ` - precision mediump float; - uniform mat4 u_projectionMatrix; - uniform vec2 u_sizePx; - attribute vec2 a_segmentStart; - attribute vec2 a_segmentEnd; - attribute float a_parameters; - attribute float a_color; - varying vec2 v_segmentStart; - varying vec2 v_segmentEnd; - varying float v_angleStart; - varying float v_angleEnd; - - varying vec3 v_color; - - vec2 worldToPx(vec2 worldPos) { - vec4 screenPos = u_projectionMatrix * vec4(worldPos, 0.0, 1.0); - return (0.5 * screenPos.xy + 0.5) * u_sizePx; - } - - vec4 pxToScreen(vec2 pxPos) { - vec2 screenPos = pxPos * 4.0 / u_sizePx; - return vec4(screenPos.xy, 0.0, 0.0); - } - - vec2 getOffsetDirection(vec2 normalPx, vec2 tangentPx, float joinAngle) { - if (cos(joinAngle) > 0.93) return normalPx - tangentPx; - float halfAngle = joinAngle / 2.0; - vec2 angleBisectorNormal = vec2( - sin(halfAngle) * normalPx.x + cos(halfAngle) * normalPx.y, - -cos(halfAngle) * normalPx.x + sin(halfAngle) * normalPx.y - ); - float length = 1.0 / sin(halfAngle); - return angleBisectorNormal * length; - } - - float lineWidth = 2.0; - - void main(void) { - float anglePrecision = 1500.0; - float paramShift = 10000.0; - v_angleStart = fract(a_parameters / paramShift) * paramShift / anglePrecision; - v_angleEnd = fract(floor(a_parameters / paramShift + 0.5) / paramShift) * paramShift / anglePrecision; - float vertexNumber = floor(a_parameters / paramShift / paramShift + 0.0001); - vec2 tangentPx = worldToPx(a_segmentEnd) - worldToPx(a_segmentStart); - tangentPx = normalize(tangentPx); - vec2 normalPx = vec2(-tangentPx.y, tangentPx.x); - float normalDir = vertexNumber < 0.5 || (vertexNumber > 1.5 && vertexNumber < 2.5) ? 1.0 : -1.0; - float tangentDir = vertexNumber < 1.5 ? 1.0 : -1.0; - float angle = vertexNumber < 1.5 ? v_angleStart : v_angleEnd; - vec2 offsetPx = getOffsetDirection(normalPx * normalDir, tangentDir * tangentPx, angle) * lineWidth * 0.5; - vec2 position = vertexNumber < 1.5 ? a_segmentStart : a_segmentEnd; - gl_Position = u_projectionMatrix * vec4(position, 0.0, 1.0) + pxToScreen(offsetPx); - v_segmentStart = worldToPx(a_segmentStart); - v_segmentEnd = worldToPx(a_segmentEnd); - - v_color = vec3( - floor(a_color / 256.0 / 256.0) / 256.0, - fract(floor(a_color / 256.0) / 256.0), - fract(a_color / 256.0) - ); - }`, - lineStringFragmentShader: ` - precision mediump float; - uniform float u_pixelRatio; - varying vec2 v_segmentStart; - varying vec2 v_segmentEnd; - varying float v_angleStart; - varying float v_angleEnd; - - varying vec3 v_color; - - float segmentDistanceField(vec2 point, vec2 start, vec2 end, float radius) { - vec2 startToPoint = point - start; - vec2 startToEnd = end - start; - float ratio = clamp(dot(startToPoint, startToEnd) / dot(startToEnd, startToEnd), 0.0, 1.0); - float dist = length(startToPoint - ratio * startToEnd); - return 1.0 - smoothstep(radius - 1.0, radius, dist); - } - - float lineWidth = 1.5; - - void main(void) { - vec2 v_currentPoint = gl_FragCoord.xy / u_pixelRatio; - gl_FragColor = vec4(v_color.rgb * 0.75, 1.0); - gl_FragColor *= segmentDistanceField(v_currentPoint, v_segmentStart, v_segmentEnd, lineWidth); - }`, - pointVertexShader: ` - precision mediump float; - uniform mat4 u_projectionMatrix; - uniform mat4 u_offsetScaleMatrix; - attribute vec2 a_position; - attribute float a_index; - varying vec2 v_texCoord; - - void main(void) { - mat4 offsetMatrix = u_offsetScaleMatrix; - float size = 6.0; - float offsetX = a_index == 0.0 || a_index == 3.0 ? -size / 2.0 : size / 2.0; - float offsetY = a_index == 0.0 || a_index == 1.0 ? -size / 2.0 : size / 2.0; - vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0); - gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; - float u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0; - float v = a_index == 0.0 || a_index == 1.0 ? 0.0 : 1.0; - v_texCoord = vec2(u, v); - }`, - pointFragmentShader: ` - precision mediump float; - varying vec2 v_texCoord; - - void main(void) { - gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); - }`, - attributes: [ - { - name: 'color', - callback: function (feature, properties) { + polygonShader: { + attributes: { + [DefaultAttributes.COLOR]: function (feature, properties) { const color = asArray(properties.COLOR || '#eee'); - // RGB components are encoded into one value - return color[0] * 256 * 256 + color[1] * 256 + color[2]; + color[3] = 0.85; + return packColor(color); + }, + [DefaultAttributes.OPACITY]: function () { + return 0.6; }, }, - ], + }, + lineStringShader: { + attributes: { + [DefaultAttributes.COLOR]: function (feature, properties) { + const color = [...asArray(properties.COLOR || '#eee')]; + color.forEach((_, i) => (color[i] = Math.round(color[i] * 0.75))); // darken slightly + return packColor(color); + }, + [DefaultAttributes.WIDTH]: function () { + return 1.5; + }, + [DefaultAttributes.OPACITY]: function () { + return 1; + }, + }, + }, }); } } diff --git a/src/ol/renderer/webgl/VectorLayer.js b/src/ol/renderer/webgl/VectorLayer.js index 0165264cf4..934fefeb68 100644 --- a/src/ol/renderer/webgl/VectorLayer.js +++ b/src/ol/renderer/webgl/VectorLayer.js @@ -10,39 +10,44 @@ import PolygonBatchRenderer from '../../render/webgl/PolygonBatchRenderer.js'; import VectorEventType from '../../source/VectorEventType.js'; import ViewHint from '../../ViewHint.js'; import WebGLLayerRenderer from './Layer.js'; +import { + DEFAULT_LINESTRING_FRAGMENT, + DEFAULT_LINESTRING_VERTEX, + DEFAULT_POINT_FRAGMENT, + DEFAULT_POINT_VERTEX, + DEFAULT_POLYGON_FRAGMENT, + DEFAULT_POLYGON_VERTEX, + DefaultAttributes, + packColor, +} from './shaders.js'; import {DefaultUniform} from '../../webgl/Helper.js'; import {buffer, createEmpty, equals, getWidth} from '../../extent.js'; import {create as createTransform} from '../../transform.js'; import {create as createWebGLWorker} from '../../worker/webgl.js'; import {listen, unlistenByKey} from '../../events.js'; -import './shaders.js'; // this is to make sure that default shaders are part of the bundle /** - * @typedef {Object} CustomAttribute A description of a custom attribute to be passed on to the GPU, with a value different - * for each feature. - * @property {string} name Attribute name. - * @property {function(import("../../Feature").default, Object):number} callback This callback computes the numerical value of the - * attribute for a given feature (properties are available as 2nd arg for quicker access). + * @typedef {function(import("../../Feature").default, Object):number} CustomAttributeCallback A callback computing + * the value of a custom attribute (different for each feature) to be passed on to the GPU. + * Properties are available as 2nd arg for quicker access. + */ + +/** + * @typedef {Object} ShaderProgram An object containing both shaders (vertex and fragment) as well as the required attributes + * @property {string} [vertexShader] Vertex shader source (using the default one if unspecified). + * @property {string} [fragmentShader] Fragment shader source (using the default one if unspecified). + * @property {Object} attributes Custom attributes made available in the vertex shader. + * Keys are the names of the attributes which are then accessible in the vertex shader using the `a_` prefix, e.g.: `a_opacity`. + * Default shaders rely on the attributes in {@link module:ol/render/webgl/shaders~DefaultAttributes}. */ /** * @typedef {Object} Options * @property {string} [className='ol-layer'] A CSS class name to set to the canvas element. - * @property {Array} [attributes] These attributes will be read from the features in the source - * and then passed to the GPU. The `name` property of each attribute will serve as its identifier: - * * 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} polygonVertexShader Vertex shader source, mandatory. - * @property {string} polygonFragmentShader Fragment shader source, mandatory. - * @property {string} lineStringVertexShader Vertex shader source, mandatory. - * @property {string} lineStringFragmentShader Fragment shader source, mandatory. - * @property {string} pointVertexShader Vertex shader source, mandatory. - * @property {string} pointFragmentShader 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 {ShaderProgram} [polygonShader] Vertex shaders for polygons; using default shader if unspecified + * @property {ShaderProgram} [lineStringShader] Vertex shaders for line strings; using default shader if unspecified + * @property {ShaderProgram} [pointShader] Vertex shaders for points; using default shader if unspecified + * @property {Object} [uniforms] Uniform definitions. * @property {Array} [postProcesses] Post-processes definitions */ @@ -119,13 +124,63 @@ class WebGLVectorLayerRenderer extends WebGLLayerRenderer { */ this.currentTransform_ = projectionMatrixTransform; - this.polygonVertexShader_ = options.polygonVertexShader; - this.polygonFragmentShader_ = options.polygonFragmentShader; - this.pointVertexShader_ = options.pointVertexShader; - this.pointFragmentShader_ = options.pointFragmentShader; - this.lineStringVertexShader_ = options.lineStringVertexShader; - this.lineStringFragmentShader_ = options.lineStringFragmentShader; - this.attributes_ = options.attributes; + const polygonAttributesWithDefault = { + [DefaultAttributes.COLOR]: function () { + return packColor('#ddd'); + }, + [DefaultAttributes.OPACITY]: function () { + return 1; + }, + ...(options.polygonShader && options.polygonShader.attributes), + }; + const lineAttributesWithDefault = { + [DefaultAttributes.COLOR]: function () { + return packColor('#eee'); + }, + [DefaultAttributes.OPACITY]: function () { + return 1; + }, + [DefaultAttributes.WIDTH]: function () { + return 1.5; + }, + ...(options.lineStringShader && options.lineStringShader.attributes), + }; + const pointAttributesWithDefault = { + [DefaultAttributes.COLOR]: function () { + return packColor('#eee'); + }, + [DefaultAttributes.OPACITY]: function () { + return 1; + }, + ...(options.pointShader && options.pointShader.attributes), + }; + function toAttributesArray(obj) { + return Object.keys(obj).map((key) => ({name: key, callback: obj[key]})); + } + + this.polygonVertexShader_ = + (options.polygonShader && options.polygonShader.vertexShader) || + DEFAULT_POLYGON_VERTEX; + this.polygonFragmentShader_ = + (options.polygonShader && options.polygonShader.fragmentShader) || + DEFAULT_POLYGON_FRAGMENT; + this.polygonAttributes_ = toAttributesArray(polygonAttributesWithDefault); + + this.lineStringVertexShader_ = + (options.lineStringShader && options.lineStringShader.vertexShader) || + DEFAULT_LINESTRING_VERTEX; + this.lineStringFragmentShader_ = + (options.lineStringShader && options.lineStringShader.fragmentShader) || + DEFAULT_LINESTRING_FRAGMENT; + this.lineStringAttributes_ = toAttributesArray(lineAttributesWithDefault); + + this.pointVertexShader_ = + (options.pointShader && options.pointShader.vertexShader) || + DEFAULT_POINT_VERTEX; + this.pointFragmentShader_ = + (options.pointShader && options.pointShader.fragmentShader) || + DEFAULT_POINT_FRAGMENT; + this.pointAttributes_ = toAttributesArray(pointAttributesWithDefault); this.worker_ = createWebGLWorker(); @@ -167,21 +222,21 @@ class WebGLVectorLayerRenderer extends WebGLLayerRenderer { this.worker_, this.polygonVertexShader_, this.polygonFragmentShader_, - this.attributes_ || [] + this.polygonAttributes_ ); this.pointRenderer_ = new PointBatchRenderer( this.helper, this.worker_, this.pointVertexShader_, this.pointFragmentShader_, - this.attributes_ || [] + this.pointAttributes_ ); this.lineStringRenderer_ = new LineStringBatchRenderer( this.helper, this.worker_, this.lineStringVertexShader_, this.lineStringFragmentShader_, - this.attributes_ || [] + this.lineStringAttributes_ ); }