Merge pull request #13461 from jahow/webgl-shape-renderer

WebGL vector renderer for polygons, lines and points
This commit is contained in:
Olivier Guyot
2022-07-22 10:05:31 +02:00
committed by GitHub
27 changed files with 3996 additions and 518 deletions
-160
View File
@@ -13,26 +13,6 @@ import {
} from '../../transform.js';
import {containsCoordinate} from '../../extent.js';
/**
* @enum {string}
*/
export const WebGLWorkerMessageType = {
GENERATE_BUFFERS: 'GENERATE_BUFFERS',
};
/**
* @typedef {Object} WebGLWorkerGenerateBuffersMessage
* This message will trigger the generation of a vertex and an index buffer based on the given render instructions.
* When the buffers are generated, the worked will send a message of the same type to the main thread, with
* the generated buffers in it.
* Note that any addition properties present in the message *will* be sent back to the main thread.
* @property {WebGLWorkerMessageType} type Message type
* @property {ArrayBuffer} renderInstructions Render instructions raw binary buffer.
* @property {ArrayBuffer} [vertexBuffer] Vertices array raw binary buffer (sent by the worker).
* @property {ArrayBuffer} [indexBuffer] Indices array raw binary buffer (sent by the worker).
* @property {number} [customAttributesCount] Amount of custom attributes count in the render instructions.
*/
/**
* @typedef {Object} PostProcessesOptions
* @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than
@@ -343,144 +323,4 @@ class WebGLLayerRenderer extends LayerRenderer {
}
}
const tmpArray_ = [];
const bufferPositions_ = {vertexPosition: 0, indexPosition: 0};
function writePointVertex(buffer, pos, x, y, index) {
buffer[pos + 0] = x;
buffer[pos + 1] = y;
buffer[pos + 2] = index;
}
/**
* An object holding positions both in an index and a vertex buffer.
* @typedef {Object} BufferPositions
* @property {number} vertexPosition Position in the vertex buffer
* @property {number} indexPosition Position in the index buffer
*/
/**
* Pushes a quad (two triangles) based on a point geometry
* @param {Float32Array} instructions Array of render instructions for points.
* @param {number} elementIndex Index from which render instructions will be read.
* @param {Float32Array} vertexBuffer Buffer in the form of a typed array.
* @param {Uint32Array} indexBuffer Buffer in the form of a typed array.
* @param {number} customAttributesCount Amount of custom attributes for each element.
* @param {BufferPositions} [bufferPositions] Buffer write positions; if not specified, positions will be set at 0.
* @return {BufferPositions} New buffer positions where to write next
* @property {number} vertexPosition New position in the vertex buffer where future writes should start.
* @property {number} indexPosition New position in the index buffer where future writes should start.
* @private
*/
export function writePointFeatureToBuffers(
instructions,
elementIndex,
vertexBuffer,
indexBuffer,
customAttributesCount,
bufferPositions
) {
// This is for x, y and index
const baseVertexAttrsCount = 3;
const baseInstructionsCount = 2;
const stride = baseVertexAttrsCount + customAttributesCount;
const x = instructions[elementIndex + 0];
const y = instructions[elementIndex + 1];
// read custom numerical attributes on the feature
const customAttrs = tmpArray_;
customAttrs.length = customAttributesCount;
for (let i = 0; i < customAttrs.length; i++) {
customAttrs[i] = instructions[elementIndex + baseInstructionsCount + i];
}
let vPos = bufferPositions ? bufferPositions.vertexPosition : 0;
let iPos = bufferPositions ? bufferPositions.indexPosition : 0;
const baseIndex = vPos / stride;
// push vertices for each of the four quad corners (first standard then custom attributes)
writePointVertex(vertexBuffer, vPos, x, y, 0);
customAttrs.length &&
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
vPos += stride;
writePointVertex(vertexBuffer, vPos, x, y, 1);
customAttrs.length &&
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
vPos += stride;
writePointVertex(vertexBuffer, vPos, x, y, 2);
customAttrs.length &&
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
vPos += stride;
writePointVertex(vertexBuffer, vPos, x, y, 3);
customAttrs.length &&
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
vPos += stride;
indexBuffer[iPos++] = baseIndex;
indexBuffer[iPos++] = baseIndex + 1;
indexBuffer[iPos++] = baseIndex + 3;
indexBuffer[iPos++] = baseIndex + 1;
indexBuffer[iPos++] = baseIndex + 2;
indexBuffer[iPos++] = baseIndex + 3;
bufferPositions_.vertexPosition = vPos;
bufferPositions_.indexPosition = iPos;
return bufferPositions_;
}
/**
* Returns a texture of 1x1 pixel, white
* @private
* @return {ImageData} Image data.
*/
export function getBlankImageData() {
const canvas = document.createElement('canvas');
const image = canvas.getContext('2d').createImageData(1, 1);
image.data[0] = 255;
image.data[1] = 255;
image.data[2] = 255;
image.data[3] = 255;
return image;
}
/**
* Generates a color array based on a numerical id
* Note: the range for each component is 0 to 1 with 256 steps
* @param {number} id Id
* @param {Array<number>} [opt_array] Reusable array
* @return {Array<number>} Color array containing the encoded id
*/
export function colorEncodeId(id, opt_array) {
const array = opt_array || [];
const radix = 256;
const divide = radix - 1;
array[0] = Math.floor(id / radix / radix / radix) / divide;
array[1] = (Math.floor(id / radix / radix) % radix) / divide;
array[2] = (Math.floor(id / radix) % radix) / divide;
array[3] = (id % radix) / divide;
return array;
}
/**
* Reads an id from a color-encoded array
* Note: the expected range for each component is 0 to 1 with 256 steps.
* @param {Array<number>} color Color array containing the encoded id
* @return {number} Decoded id
*/
export function colorDecodeId(color) {
let id = 0;
const radix = 256;
const mult = radix - 1;
id += Math.round(color[0] * radix * radix * radix * mult);
id += Math.round(color[1] * radix * radix * mult);
id += Math.round(color[2] * radix * mult);
id += Math.round(color[3] * mult);
return id;
}
export default WebGLLayerRenderer;
+16 -12
View File
@@ -5,14 +5,11 @@ import BaseVector from '../../layer/BaseVector.js';
import VectorEventType from '../../source/VectorEventType.js';
import ViewHint from '../../ViewHint.js';
import WebGLArrayBuffer from '../../webgl/Buffer.js';
import WebGLLayerRenderer, {
WebGLWorkerMessageType,
colorDecodeId,
colorEncodeId,
} from './Layer.js';
import WebGLLayerRenderer from './Layer.js';
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
import {ARRAY_BUFFER, DYNAMIC_DRAW, ELEMENT_ARRAY_BUFFER} from '../../webgl.js';
import {AttributeType, DefaultUniform} from '../../webgl/Helper.js';
import {WebGLWorkerMessageType} from '../../render/webgl/constants.js';
import {
apply as applyTransform,
create as createTransform,
@@ -22,6 +19,7 @@ import {
} from '../../transform.js';
import {assert} from '../../asserts.js';
import {buffer, createEmpty, equals, getWidth} from '../../extent.js';
import {colorDecodeId, colorEncodeId} from '../../render/webgl/utils.js';
import {create as createWebGLWorker} from '../../worker/webgl.js';
import {getUid} from '../../util.js';
import {listen, unlistenByKey} from '../../events.js';
@@ -294,7 +292,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
*/
this.generateBuffersRun_ = 0;
/**
* @private
*/
this.worker_ = createWebGLWorker();
this.worker_.addEventListener(
'message',
/**
@@ -303,7 +305,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
*/
function (event) {
const received = event.data;
if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) {
if (received.type === WebGLWorkerMessageType.GENERATE_POINT_BUFFERS) {
const projectionTransform = received.projectionTransform;
if (received.hitDetection) {
this.hitVerticesBuffer_.fromArrayBuffer(received.vertexBuffer);
@@ -540,7 +542,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.previousExtent_ = frameState.extent.slice();
}
this.helper.useProgram(this.program_);
this.helper.useProgram(this.program_, frameState);
this.helper.prepareDraw(frameState);
// write new data
@@ -637,9 +639,10 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
}
}
/** @type {import('./Layer').WebGLWorkerGenerateBuffersMessage} */
/** @type {import('../../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
const message = {
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
id: 0,
type: WebGLWorkerMessageType.GENERATE_POINT_BUFFERS,
renderInstructions: this.renderInstructions_.buffer,
customAttributesCount: this.customAttributes.length,
};
@@ -650,10 +653,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.worker_.postMessage(message, [this.renderInstructions_.buffer]);
this.renderInstructions_ = null;
/** @type {import('./Layer').WebGLWorkerGenerateBuffersMessage} */
/** @type {import('../../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
if (this.hitDetectionEnabled_) {
const hitMessage = {
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
id: 0,
type: WebGLWorkerMessageType.GENERATE_POINT_BUFFERS,
renderInstructions: this.hitRenderInstructions_.buffer,
customAttributesCount: 5 + this.customAttributes.length,
};
@@ -726,7 +730,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
Math.floor(frameState.size[1] / 2),
]);
this.helper.useProgram(this.hitProgram_);
this.helper.useProgram(this.hitProgram_, frameState);
this.helper.prepareDrawToRenderTarget(
frameState,
this.hitRenderTarget_,
+1 -1
View File
@@ -508,7 +508,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
}
}
this.helper.useProgram(this.program_);
this.helper.useProgram(this.program_, frameState);
this.helper.prepareDraw(frameState, !blend);
const zs = Object.keys(tileTexturesByZ)
+409
View File
@@ -0,0 +1,409 @@
/**
* @module ol/renderer/webgl/VectorLayer
*/
import BaseVector from '../../layer/BaseVector.js';
import LineStringBatchRenderer from '../../render/webgl/LineStringBatchRenderer.js';
import MixedGeometryBatch from '../../render/webgl/MixedGeometryBatch.js';
import PointBatchRenderer from '../../render/webgl/PointBatchRenderer.js';
import PolygonBatchRenderer from '../../render/webgl/PolygonBatchRenderer.js';
import VectorEventType from '../../source/VectorEventType.js';
import ViewHint from '../../ViewHint.js';
import WebGLLayerRenderer from './Layer.js';
import {DefaultUniform} from '../../webgl/Helper.js';
import {
FILL_FRAGMENT_SHADER,
FILL_VERTEX_SHADER,
POINT_FRAGMENT_SHADER,
POINT_VERTEX_SHADER,
STROKE_FRAGMENT_SHADER,
STROKE_VERTEX_SHADER,
packColor,
} from './shaders.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';
/**
* @typedef {function(import("../../Feature").default, Object<string, *>):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<import("./shaders.js").DefaultAttributes,CustomAttributeCallback>} 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 {ShaderProgram} [fill] Attributes and shaders for filling polygons.
* @property {ShaderProgram} [stroke] Attributes and shaders for line strings and polygon strokes.
* @property {ShaderProgram} [point] Attributes and shaders for points.
* @property {Object<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions.
* @property {Array<import("./Layer").PostProcessesOptions>} [postProcesses] Post-processes definitions
*/
/**
* @param {Object<import("./shaders.js").DefaultAttributes,CustomAttributeCallback>} obj Lookup of attribute getters.
* @return {Array<import("../../render/webgl/BatchRenderer").CustomAttribute>} An array of attribute descriptors.
*/
function toAttributesArray(obj) {
return Object.keys(obj).map((key) => ({name: key, callback: obj[key]}));
}
/**
* @classdesc
* Experimental WebGL vector renderer. Supports polygons, lines and points:
* * Polygons are broken down into triangles
* * Lines are rendered as strips of quads
* * Points are rendered as quads
*
* You need to provide vertex and fragment shaders as well as custom attributes for each type of geometry. All shaders
* can access the uniforms in the {@link module:ol/webgl/Helper~DefaultUniform} enum.
* The vertex shaders can access the following attributes depending on the geometry type:
* * For polygons: {@link module:ol/render/webgl/PolygonBatchRenderer~Attributes}
* * For line strings: {@link module:ol/render/webgl/LineStringBatchRenderer~Attributes}
* * For points: {@link module:ol/render/webgl/PointBatchRenderer~Attributes}
*
* Please note that the fragment shaders output should have premultiplied alpha, otherwise visual anomalies may occur.
*
* Note: this uses {@link module:ol/webgl/Helper~WebGLHelper} internally.
*/
class WebGLVectorLayerRenderer extends WebGLLayerRenderer {
/**
* @param {import("../../layer/Layer.js").default} layer Layer.
* @param {Options} options Options.
*/
constructor(layer, options) {
const uniforms = options.uniforms || {};
const projectionMatrixTransform = createTransform();
uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform;
super(layer, {
uniforms: uniforms,
postProcesses: options.postProcesses,
});
this.sourceRevision_ = -1;
this.previousExtent_ = createEmpty();
/**
* This transform is updated on every frame and is the composition of:
* - invert of the world->screen transform that was used when rebuilding buffers (see `this.renderTransform_`)
* - current world->screen transform
* @type {import("../../transform.js").Transform}
* @private
*/
this.currentTransform_ = projectionMatrixTransform;
const fillAttributes = {
color: function () {
return packColor('#ddd');
},
opacity: function () {
return 1;
},
...(options.fill && options.fill.attributes),
};
const strokeAttributes = {
color: function () {
return packColor('#eee');
},
opacity: function () {
return 1;
},
width: function () {
return 1.5;
},
...(options.stroke && options.stroke.attributes),
};
const pointAttributes = {
color: function () {
return packColor('#eee');
},
opacity: function () {
return 1;
},
...(options.point && options.point.attributes),
};
this.fillVertexShader_ =
(options.fill && options.fill.vertexShader) || FILL_VERTEX_SHADER;
this.fillFragmentShader_ =
(options.fill && options.fill.fragmentShader) || FILL_FRAGMENT_SHADER;
this.fillAttributes_ = toAttributesArray(fillAttributes);
this.strokeVertexShader_ =
(options.stroke && options.stroke.vertexShader) || STROKE_VERTEX_SHADER;
this.strokeFragmentShader_ =
(options.stroke && options.stroke.fragmentShader) ||
STROKE_FRAGMENT_SHADER;
this.strokeAttributes_ = toAttributesArray(strokeAttributes);
this.pointVertexShader_ =
(options.point && options.point.vertexShader) || POINT_VERTEX_SHADER;
this.pointFragmentShader_ =
(options.point && options.point.fragmentShader) || POINT_FRAGMENT_SHADER;
this.pointAttributes_ = toAttributesArray(pointAttributes);
/**
* @private
*/
this.worker_ = createWebGLWorker();
/**
* @private
*/
this.batch_ = new MixedGeometryBatch();
const source = this.getLayer().getSource();
this.batch_.addFeatures(source.getFeatures());
this.sourceListenKeys_ = [
listen(
source,
VectorEventType.ADDFEATURE,
this.handleSourceFeatureAdded_,
this
),
listen(
source,
VectorEventType.CHANGEFEATURE,
this.handleSourceFeatureChanged_,
this
),
listen(
source,
VectorEventType.REMOVEFEATURE,
this.handleSourceFeatureDelete_,
this
),
listen(
source,
VectorEventType.CLEAR,
this.handleSourceFeatureClear_,
this
),
];
}
afterHelperCreated() {
this.polygonRenderer_ = new PolygonBatchRenderer(
this.helper,
this.worker_,
this.fillVertexShader_,
this.fillFragmentShader_,
this.fillAttributes_
);
this.pointRenderer_ = new PointBatchRenderer(
this.helper,
this.worker_,
this.pointVertexShader_,
this.pointFragmentShader_,
this.pointAttributes_
);
this.lineStringRenderer_ = new LineStringBatchRenderer(
this.helper,
this.worker_,
this.strokeVertexShader_,
this.strokeFragmentShader_,
this.strokeAttributes_
);
}
/**
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
* @private
*/
handleSourceFeatureAdded_(event) {
const feature = event.feature;
this.batch_.addFeature(feature);
}
/**
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
* @private
*/
handleSourceFeatureChanged_(event) {
const feature = event.feature;
this.batch_.changeFeature(feature);
}
/**
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
* @private
*/
handleSourceFeatureDelete_(event) {
const feature = event.feature;
this.batch_.removeFeature(feature);
}
/**
* @private
*/
handleSourceFeatureClear_() {
this.batch_.clear();
}
/**
* Render the layer.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @return {HTMLElement} The rendered element.
*/
renderFrame(frameState) {
const gl = this.helper.getGL();
this.preRender(gl, frameState);
const layer = this.getLayer();
const vectorSource = layer.getSource();
const projection = frameState.viewState.projection;
const multiWorld = vectorSource.getWrapX() && projection.canWrapX();
const projectionExtent = projection.getExtent();
const extent = frameState.extent;
const worldWidth = multiWorld ? getWidth(projectionExtent) : null;
const endWorld = multiWorld
? Math.ceil((extent[2] - projectionExtent[2]) / worldWidth) + 1
: 1;
let world = multiWorld
? Math.floor((extent[0] - projectionExtent[0]) / worldWidth)
: 0;
do {
this.polygonRenderer_.render(
this.batch_.polygonBatch,
this.currentTransform_,
frameState,
world * worldWidth
);
this.lineStringRenderer_.render(
this.batch_.lineStringBatch,
this.currentTransform_,
frameState,
world * worldWidth
);
this.pointRenderer_.render(
this.batch_.pointBatch,
this.currentTransform_,
frameState,
world * worldWidth
);
} while (++world < endWorld);
this.helper.finalizeDraw(frameState);
const canvas = this.helper.getCanvas();
const layerState = frameState.layerStatesArray[frameState.layerIndex];
const opacity = layerState.opacity;
if (opacity !== parseFloat(canvas.style.opacity)) {
canvas.style.opacity = String(opacity);
}
this.postRender(gl, frameState);
return canvas;
}
/**
* Determine whether renderFrame should be called.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @return {boolean} Layer is ready to be rendered.
*/
prepareFrameInternal(frameState) {
const layer = this.getLayer();
const vectorSource = layer.getSource();
const viewState = frameState.viewState;
const viewNotMoving =
!frameState.viewHints[ViewHint.ANIMATING] &&
!frameState.viewHints[ViewHint.INTERACTING];
const extentChanged = !equals(this.previousExtent_, frameState.extent);
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
if (sourceChanged) {
this.sourceRevision_ = vectorSource.getRevision();
}
if (viewNotMoving && (extentChanged || sourceChanged)) {
const projection = viewState.projection;
const resolution = viewState.resolution;
const renderBuffer =
layer instanceof BaseVector ? layer.getRenderBuffer() : 0;
const extent = buffer(frameState.extent, renderBuffer * resolution);
vectorSource.loadFeatures(extent, resolution, projection);
this.ready = false;
let remaining = 3;
const rebuildCb = () => {
remaining--;
this.ready = remaining <= 0;
this.getLayer().changed();
};
this.polygonRenderer_.rebuild(
this.batch_.polygonBatch,
frameState,
'Polygon',
rebuildCb
);
this.lineStringRenderer_.rebuild(
this.batch_.lineStringBatch,
frameState,
'LineString',
rebuildCb
);
this.pointRenderer_.rebuild(
this.batch_.pointBatch,
frameState,
'Point',
rebuildCb
);
this.previousExtent_ = frameState.extent.slice();
}
this.helper.makeProjectionTransform(frameState, this.currentTransform_);
this.helper.prepareDraw(frameState);
return true;
}
/**
* @param {import("../../coordinate.js").Coordinate} coordinate Coordinate.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @param {number} hitTolerance Hit tolerance in pixels.
* @param {import("../vector.js").FeatureCallback<T>} callback Feature callback.
* @param {Array<import("../Map.js").HitMatch<T>>} matches The hit detected matches with tolerance.
* @return {T|undefined} Callback result.
* @template T
*/
forEachFeatureAtCoordinate(
coordinate,
frameState,
hitTolerance,
callback,
matches
) {
return undefined;
}
/**
* Clean up.
*/
disposeInternal() {
this.worker_.terminate();
this.layer_ = null;
this.sourceListenKeys_.forEach(function (key) {
unlistenByKey(key);
});
this.sourceListenKeys_ = null;
super.disposeInternal();
}
}
export default WebGLVectorLayerRenderer;
+198
View File
@@ -0,0 +1,198 @@
/**
* @module ol/renderer/webgl/shaders
*/
import {asArray} from '../../color.js';
/** @typedef {'color'|'opacity'|'width'} DefaultAttributes */
/**
* Packs red/green/blue channels of a color into a single float value; alpha is ignored.
* This is how the color is expected to be computed.
* @param {import("../../color.js").Color|string} color Color as array of numbers or string
* @return {number} Float value containing the color
*/
export function packColor(color) {
const array = asArray(color);
const r = array[0] * 256 * 256;
const g = array[1] * 256;
const b = array[2];
return r + g + b;
}
const DECODE_COLOR_EXPRESSION = `vec3(
fract(floor(a_color / 256.0 / 256.0) / 256.0),
fract(floor(a_color / 256.0) / 256.0),
fract(a_color / 256.0)
);`;
/**
* Default polygon vertex shader.
* Relies on the color and opacity attributes.
* @type {string}
*/
export const FILL_VERTEX_SHADER = `
precision mediump float;
uniform mat4 u_projectionMatrix;
attribute vec2 a_position;
attribute float a_color;
attribute float a_opacity;
varying vec3 v_color;
varying float v_opacity;
void main(void) {
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0);
v_color = ${DECODE_COLOR_EXPRESSION}
v_opacity = a_opacity;
}`;
/**
* Default polygon fragment shader.
* @type {string}
*/
export const FILL_FRAGMENT_SHADER = `
precision mediump float;
varying vec3 v_color;
varying float v_opacity;
void main(void) {
gl_FragColor = vec4(v_color, 1.0) * v_opacity;
}`;
/**
* Default linestring vertex shader.
* Relies on color, opacity and width attributes.
* @type {string}
*/
export const STROKE_VERTEX_SHADER = `
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;
attribute float a_opacity;
attribute float a_width;
varying vec2 v_segmentStart;
varying vec2 v_segmentEnd;
varying float v_angleStart;
varying float v_angleEnd;
varying vec3 v_color;
varying float v_opacity;
varying float v_width;
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;
}
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) * a_width * 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 = ${DECODE_COLOR_EXPRESSION}
v_opacity = a_opacity;
v_width = a_width;
}`;
/**
* Default linestring fragment shader.
* @type {string}
*/
export const STROKE_FRAGMENT_SHADER = `
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;
varying float v_opacity;
varying float v_width;
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);
}
void main(void) {
vec2 v_currentPoint = gl_FragCoord.xy / u_pixelRatio;
gl_FragColor = vec4(v_color, 1.0) * v_opacity;
gl_FragColor *= segmentDistanceField(v_currentPoint, v_segmentStart, v_segmentEnd, v_width);
}`;
/**
* Default point vertex shader.
* Relies on color and opacity attributes.
* @type {string}
*/
export const POINT_VERTEX_SHADER = `
precision mediump float;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
attribute vec2 a_position;
attribute float a_index;
attribute float a_color;
attribute float a_opacity;
varying vec2 v_texCoord;
varying vec3 v_color;
varying float v_opacity;
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);
v_color = ${DECODE_COLOR_EXPRESSION}
v_opacity = a_opacity;
}`;
/**
* Default point fragment shader.
* @type {string}
*/
export const POINT_FRAGMENT_SHADER = `
precision mediump float;
varying vec3 v_color;
varying float v_opacity;
void main(void) {
gl_FragColor = vec4(v_color, 1.0) * v_opacity;
}`;