Shader builder / add logic for transforming a style into shader params
This was previously handled by the WebGLPointsLayer class, but is now externalized and tested.
This commit is contained in:
@@ -3,15 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
import {assign} from '../obj.js';
|
import {assign} from '../obj.js';
|
||||||
import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
|
import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
|
||||||
import {
|
import {getSymbolFragmentShader, getSymbolVertexShader, parseSymbolStyle} from '../webgl/ShaderBuilder.js';
|
||||||
formatArray,
|
|
||||||
formatColor,
|
|
||||||
formatNumber,
|
|
||||||
getSymbolFragmentShader,
|
|
||||||
getSymbolVertexShader
|
|
||||||
} from '../webgl/ShaderBuilder.js';
|
|
||||||
import {assert} from '../asserts.js';
|
import {assert} from '../asserts.js';
|
||||||
import {asArray} from '../color.js';
|
|
||||||
import Layer from './Layer.js';
|
import Layer from './Layer.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -90,42 +83,13 @@ class WebGLPointsLayer extends Layer {
|
|||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
createRenderer() {
|
createRenderer() {
|
||||||
const symbolStyle = this.style.symbol;
|
const parseResult = parseSymbolStyle(this.style.symbol);
|
||||||
const size = Array.isArray(symbolStyle.size) ?
|
|
||||||
formatArray(symbolStyle.size) : formatNumber(symbolStyle.size);
|
|
||||||
const color = symbolStyle.color !== undefined ?
|
|
||||||
(typeof symbolStyle.color === 'string' ? asArray(symbolStyle.color) : symbolStyle.color) :
|
|
||||||
[255, 255, 255, 1];
|
|
||||||
const texCoord = symbolStyle.textureCoord || [0, 0, 1, 1];
|
|
||||||
const offset = symbolStyle.offset || [0, 0];
|
|
||||||
const opacity = symbolStyle.opacity !== undefined ? symbolStyle.opacity : 1;
|
|
||||||
|
|
||||||
/** @type {import('../webgl/ShaderBuilder.js').ShaderParameters} */
|
|
||||||
const params = {
|
|
||||||
uniforms: [],
|
|
||||||
colorExpression: 'vec4(' + formatColor(color) + ') * vec4(1.0, 1.0, 1.0, ' + formatNumber(opacity) + ')',
|
|
||||||
sizeExpression: 'vec2(' + size + ')',
|
|
||||||
offsetExpression: 'vec2(' + formatArray(offset) + ')',
|
|
||||||
texCoordExpression: 'vec4(' + formatArray(texCoord) + ')',
|
|
||||||
rotateWithView: !!symbolStyle.rotateWithView
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {Object.<string,import("../webgl/Helper").UniformValue>} */
|
|
||||||
const uniforms = {};
|
|
||||||
|
|
||||||
if (symbolStyle.symbolType === 'image' && symbolStyle.src) {
|
|
||||||
const texture = new Image();
|
|
||||||
texture.src = symbolStyle.src;
|
|
||||||
params.uniforms.push('sampler2D u_texture');
|
|
||||||
params.colorExpression = params.colorExpression +
|
|
||||||
' * texture2D(u_texture, v_texCoord);';
|
|
||||||
uniforms['u_texture'] = texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new WebGLPointsLayerRenderer(this, {
|
return new WebGLPointsLayerRenderer(this, {
|
||||||
vertexShader: getSymbolVertexShader(params),
|
vertexShader: getSymbolVertexShader(parseResult.params),
|
||||||
fragmentShader: getSymbolFragmentShader(params),
|
fragmentShader: getSymbolFragmentShader(parseResult.params),
|
||||||
uniforms: uniforms
|
uniforms: parseResult.uniforms,
|
||||||
|
attributes: parseResult.attributes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
* @module ol/webgl/ShaderBuilder
|
* @module ol/webgl/ShaderBuilder
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {asArray} from '../color.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will return the number as a float with a dot separator, which is required by GLSL.
|
* Will return the number as a float with a dot separator, which is required by GLSL.
|
||||||
* @param {number} v Numerical value.
|
* @param {number} v Numerical value.
|
||||||
@@ -195,3 +197,91 @@ export function parse(value, attributes, attributePrefix) {
|
|||||||
return formatNumber(value);
|
return formatNumber(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} StyleParseResult
|
||||||
|
* @property {ShaderParameters} params Symbol shader params.
|
||||||
|
* @property {Object.<string,import("./Helper").UniformValue>} uniforms Uniform definitions.
|
||||||
|
* @property {Array<import("../renderer/webgl/PointsLayer").CustomAttribute>} attributes Attribute descriptions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a {@link import("../style/LiteralStyle").LiteralSymbolStyle} object and outputs shader parameters to be
|
||||||
|
* then fed to {@link getSymbolVertexShader} and {@link getSymbolFragmentShader}.
|
||||||
|
*
|
||||||
|
* Also returns `uniforms` and `attributes` properties as expected by the
|
||||||
|
* {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}.
|
||||||
|
*
|
||||||
|
* @param {import("../style/LiteralStyle").LiteralSymbolStyle} style Symbol style.
|
||||||
|
* @returns {StyleParseResult} Result containing shader params, attributes and uniforms.
|
||||||
|
*/
|
||||||
|
export function parseSymbolStyle(style) {
|
||||||
|
const size = Array.isArray(style.size) && typeof style.size[0] == 'number' ?
|
||||||
|
style.size : [style.size, style.size];
|
||||||
|
const color = (typeof style.color === 'string' ?
|
||||||
|
asArray(style.color).map(function(c, i) {
|
||||||
|
return i < 3 ? c / 255 : c;
|
||||||
|
}) :
|
||||||
|
style.color || [255, 255, 255, 1]);
|
||||||
|
const texCoord = style.textureCoord || [0, 0, 1, 1];
|
||||||
|
const offset = style.offset || [0, 0];
|
||||||
|
const opacity = style.opacity !== undefined ? style.opacity : 1;
|
||||||
|
|
||||||
|
let attributes = [];
|
||||||
|
const varyings = [];
|
||||||
|
function pA(value) {
|
||||||
|
return parse(value, attributes, 'a_');
|
||||||
|
}
|
||||||
|
function pV(value) {
|
||||||
|
return parse(value, varyings, 'v_');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('../webgl/ShaderBuilder.js').ShaderParameters} */
|
||||||
|
const params = {
|
||||||
|
uniforms: [],
|
||||||
|
colorExpression: `vec4(${pV(color[0])}, ${pV(color[1])}, ${pV(color[2])}, ${pV(color[3])}) * vec4(1.0, 1.0, 1.0, ${pV(opacity)})`,
|
||||||
|
sizeExpression: `vec2(${pA(size[0])}, ${pA(size[1])})`,
|
||||||
|
offsetExpression: `vec2(${pA(offset[0])}, ${pA(offset[1])})`,
|
||||||
|
texCoordExpression: `vec4(${pA(texCoord[0])}, ${pA(texCoord[1])}, ${pA(texCoord[2])}, ${pA(texCoord[3])})`,
|
||||||
|
rotateWithView: !!style.rotateWithView
|
||||||
|
};
|
||||||
|
|
||||||
|
attributes = attributes.concat(varyings).filter(function(attrName, index, arr) {
|
||||||
|
return arr.indexOf(attrName) === index;
|
||||||
|
});
|
||||||
|
params.attributes = attributes.map(function(attributeName) {
|
||||||
|
return `float a_${attributeName}`;
|
||||||
|
});
|
||||||
|
params.varyings = varyings.map(function(attributeName) {
|
||||||
|
return {
|
||||||
|
name: `v_${attributeName}`,
|
||||||
|
type: 'float',
|
||||||
|
expression: `a_${attributeName}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @type {Object.<string,import("../webgl/Helper").UniformValue>} */
|
||||||
|
const uniforms = {};
|
||||||
|
|
||||||
|
if (style.symbolType === 'image' && style.src) {
|
||||||
|
const texture = new Image();
|
||||||
|
texture.src = style.src;
|
||||||
|
params.uniforms.push('sampler2D u_texture');
|
||||||
|
params.colorExpression = params.colorExpression +
|
||||||
|
' * texture2D(u_texture, v_texCoord)';
|
||||||
|
uniforms['u_texture'] = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
params: params,
|
||||||
|
attributes: attributes.map(function(attributeName) {
|
||||||
|
return {
|
||||||
|
name: attributeName,
|
||||||
|
callback: function(feature) {
|
||||||
|
return feature.get(attributeName) || 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
uniforms: uniforms
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
getSymbolVertexShader,
|
getSymbolVertexShader,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
getSymbolFragmentShader,
|
getSymbolFragmentShader,
|
||||||
formatColor, formatArray, parse
|
formatColor, formatArray, parse, parseSymbolStyle
|
||||||
} from '../../../../src/ol/webgl/ShaderBuilder.js';
|
} from '../../../../src/ol/webgl/ShaderBuilder.js';
|
||||||
|
|
||||||
describe('ol.webgl.ShaderBuilder', function() {
|
describe('ol.webgl.ShaderBuilder', function() {
|
||||||
@@ -223,4 +223,80 @@ void main(void) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('parseSymbolStyle', function() {
|
||||||
|
it('parses a style without expressions', function() {
|
||||||
|
const result = parseSymbolStyle({
|
||||||
|
symbolType: 'circle',
|
||||||
|
size: [4, 8],
|
||||||
|
color: '#336699',
|
||||||
|
rotateWithView: true
|
||||||
|
});
|
||||||
|
expect(result.params).to.eql({
|
||||||
|
uniforms: [],
|
||||||
|
attributes: [],
|
||||||
|
varyings: [],
|
||||||
|
colorExpression: 'vec4(0.2, 0.4, 0.6, 1.0) * vec4(1.0, 1.0, 1.0, 1.0)',
|
||||||
|
sizeExpression: 'vec2(4.0, 8.0)',
|
||||||
|
offsetExpression: 'vec2(0.0, 0.0)',
|
||||||
|
texCoordExpression: 'vec4(0.0, 0.0, 1.0, 1.0)',
|
||||||
|
rotateWithView: true
|
||||||
|
});
|
||||||
|
expect(result.attributes).to.eql([]);
|
||||||
|
expect(result.uniforms).to.eql({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a style with expressions', function() {
|
||||||
|
const result = parseSymbolStyle({
|
||||||
|
symbolType: 'circle',
|
||||||
|
size: ['get', 'attr1'],
|
||||||
|
color: [
|
||||||
|
1.0, 0.0, 0.5, ['get', 'attr2']
|
||||||
|
],
|
||||||
|
textureCoord: [0.5, 0.5, 0.5, 1],
|
||||||
|
offset: [3, ['get', 'attr3']]
|
||||||
|
});
|
||||||
|
expect(result.params).to.eql({
|
||||||
|
uniforms: [],
|
||||||
|
attributes: ['float a_attr1', 'float a_attr3', 'float a_attr2'],
|
||||||
|
varyings: [{
|
||||||
|
name: 'v_attr2',
|
||||||
|
type: 'float',
|
||||||
|
expression: 'a_attr2'
|
||||||
|
}],
|
||||||
|
colorExpression: 'vec4(1.0, 0.0, 0.5, v_attr2) * vec4(1.0, 1.0, 1.0, 1.0)',
|
||||||
|
sizeExpression: 'vec2(a_attr1, a_attr1)',
|
||||||
|
offsetExpression: 'vec2(3.0, a_attr3)',
|
||||||
|
texCoordExpression: 'vec4(0.5, 0.5, 0.5, 1.0)',
|
||||||
|
rotateWithView: false
|
||||||
|
});
|
||||||
|
expect(result.attributes.length).to.eql(3);
|
||||||
|
expect(result.attributes[0].name).to.eql('attr1');
|
||||||
|
expect(result.attributes[1].name).to.eql('attr3');
|
||||||
|
expect(result.attributes[2].name).to.eql('attr2');
|
||||||
|
expect(result.uniforms).to.eql({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a style with a uniform (texture)', function() {
|
||||||
|
const result = parseSymbolStyle({
|
||||||
|
symbolType: 'image',
|
||||||
|
src: '../data/image.png',
|
||||||
|
size: 6,
|
||||||
|
color: '#336699',
|
||||||
|
opacity: 0.5
|
||||||
|
});
|
||||||
|
expect(result.params).to.eql({
|
||||||
|
uniforms: ['sampler2D u_texture'],
|
||||||
|
attributes: [],
|
||||||
|
varyings: [],
|
||||||
|
colorExpression: 'vec4(0.2, 0.4, 0.6, 1.0) * vec4(1.0, 1.0, 1.0, 0.5) * texture2D(u_texture, v_texCoord)',
|
||||||
|
sizeExpression: 'vec2(6.0, 6.0)',
|
||||||
|
offsetExpression: 'vec2(0.0, 0.0)',
|
||||||
|
texCoordExpression: 'vec4(0.0, 0.0, 1.0, 1.0)',
|
||||||
|
rotateWithView: false
|
||||||
|
});
|
||||||
|
expect(result.attributes).to.eql([]);
|
||||||
|
expect(result.uniforms).to.have.property('u_texture');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user