From 9b3a9b5eca8ef9566806a9a6d3fff1433a250ff3 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Thu, 26 Sep 2019 11:15:38 +0200 Subject: [PATCH] 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. --- src/ol/layer/WebGLPoints.js | 48 ++----------- src/ol/webgl/ShaderBuilder.js | 90 ++++++++++++++++++++++++ test/spec/ol/webgl/shaderbuilder.test.js | 78 +++++++++++++++++++- 3 files changed, 173 insertions(+), 43 deletions(-) diff --git a/src/ol/layer/WebGLPoints.js b/src/ol/layer/WebGLPoints.js index 9da441e560..b778c56898 100644 --- a/src/ol/layer/WebGLPoints.js +++ b/src/ol/layer/WebGLPoints.js @@ -3,15 +3,8 @@ */ import {assign} from '../obj.js'; import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js'; -import { - formatArray, - formatColor, - formatNumber, - getSymbolFragmentShader, - getSymbolVertexShader -} from '../webgl/ShaderBuilder.js'; +import {getSymbolFragmentShader, getSymbolVertexShader, parseSymbolStyle} from '../webgl/ShaderBuilder.js'; import {assert} from '../asserts.js'; -import {asArray} from '../color.js'; import Layer from './Layer.js'; @@ -90,42 +83,13 @@ class WebGLPointsLayer extends Layer { * @inheritDoc */ createRenderer() { - const symbolStyle = 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.} */ - 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; - } + const parseResult = parseSymbolStyle(this.style.symbol); return new WebGLPointsLayerRenderer(this, { - vertexShader: getSymbolVertexShader(params), - fragmentShader: getSymbolFragmentShader(params), - uniforms: uniforms + vertexShader: getSymbolVertexShader(parseResult.params), + fragmentShader: getSymbolFragmentShader(parseResult.params), + uniforms: parseResult.uniforms, + attributes: parseResult.attributes }); } } diff --git a/src/ol/webgl/ShaderBuilder.js b/src/ol/webgl/ShaderBuilder.js index 7699132a4a..1ff808b93f 100644 --- a/src/ol/webgl/ShaderBuilder.js +++ b/src/ol/webgl/ShaderBuilder.js @@ -3,6 +3,8 @@ * @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. * @param {number} v Numerical value. @@ -195,3 +197,91 @@ export function parse(value, attributes, attributePrefix) { return formatNumber(value); } } + +/** + * @typedef {Object} StyleParseResult + * @property {ShaderParameters} params Symbol shader params. + * @property {Object.} uniforms Uniform definitions. + * @property {Array} 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.} */ + 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 + }; +} diff --git a/test/spec/ol/webgl/shaderbuilder.test.js b/test/spec/ol/webgl/shaderbuilder.test.js index a8bed4e62a..6767f6a487 100644 --- a/test/spec/ol/webgl/shaderbuilder.test.js +++ b/test/spec/ol/webgl/shaderbuilder.test.js @@ -2,7 +2,7 @@ import { getSymbolVertexShader, formatNumber, getSymbolFragmentShader, - formatColor, formatArray, parse + formatColor, formatArray, parse, parseSymbolStyle } from '../../../../src/ol/webgl/ShaderBuilder.js'; 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'); + }); + }); + });