Merge branch 'dev-optimize-line' into 'master'

feat: support bevel joint, dashline & anti-alias

主要改进了 3 点:
1. 超过阈值(非常小的锐角)时,miter 接头转成 bevel 接头
2. 支持虚线 demo/dashline.html
3. anti-alias 边缘反走样,可配置模糊半径

See merge request !29
This commit is contained in:
thinkinggis 2019-05-28 11:34:54 +08:00
commit 713140033c
17 changed files with 501 additions and 155 deletions

View File

@ -30,8 +30,7 @@ const scene = new L7.Scene({
zoom: 14.82,
});
scene.on('loaded', () => {
$.get('./data/contour.geojson', data => {
// data.features = data.features.slice(0,1);
$.get('https://gw.alipayobjects.com/os/rmsportal/ZVfOvhVCzwBkISNsuKCc.json', data => {
scene.LineLayer({
zIndex: 2
})

55
demos/dashline.html Normal file
View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="geometry" content="diagram">
<link rel="stylesheet" href="./assets/common.css">
<title>dashline demo</title>
<style>
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
<div id="map"></div>
<script src="https://webapi.amap.com/maps?v=1.4.8&key=15cd8a57710d40c9b7c0e3cc120f1200&plugin=Map3D"></script>
<script src="./assets/jquery-3.2.1.min.js"></script>
<script src="./assets/dat.gui.min.js"></script>
<script src="../build/L7.js"></script>
<script>
const color1 = [ 'rgba(37, 140, 249, 0.8)', 'rgba(14, 241, 242, 0.8)', 'rgba(255, 255, 255, 0.8)' ];
const scene = new L7.Scene({
id: 'map',
mapStyle: 'dark', // 样式URL
center: [ 102.602992, 23.107329],
pitch: 15,
zoom: 14.82,
});
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/rmsportal/ZVfOvhVCzwBkISNsuKCc.json', data => {
scene.LineLayer({
zIndex: 2
})
.source(data)
.size('ELEV',(value)=>{
return [2,(value-1000)*7];
})
.active(true)
.shape('line')
.style({
lineType: 'dash',
dashArray: 200,
dashOffset: 0.2,
dashRatio: 0.5
})
.color('ELEV',["#E8FCFF", "#CFF6FF", "#A1E9ff", "#65CEF7", "#3CB1F0", "#2894E0", "#1772c2", "#105CB3", "#0D408C", "#002466"].reverse())
.render();
});
});
</script>
</body>
</html>

View File

@ -108,8 +108,9 @@
"earcut": "^2.1.3",
"fecha": "^2.3.3",
"gl-matrix": "^2.4.1",
"gl-vec2": "^1.3.0",
"lodash": "^4.17.5",
"polyline-normals": "^2.0.2",
"polyline-miter-util": "^1.0.1",
"rbush": "^2.0.2",
"simple-statistics": "^7.0.1",
"supercluster": "^6.0.1",

View File

@ -75,7 +75,7 @@ export default class LineBuffer extends BufferBase {
}
_getMeshLineAttributes() {
const layerData = this.get('layerData');
const { lineType } = this.get('style');
const { dashArray } = this.get('style');
const positions = [];
const pickingIds = [];
const normal = [];
@ -84,10 +84,11 @@ export default class LineBuffer extends BufferBase {
const indexArray = [];
const sizes = [];
const attrDistance = [];
const attrDashArray = [];
layerData.forEach(item => {
const props = item;
const positionCount = positions.length / 3;
const attr = lineShape.Line(item.coordinates, props, positionCount, (lineType !== 'soild'));
const attr = lineShape.Line(item.coordinates, props, positionCount, dashArray);
positions.push(...attr.positions);
normal.push(...attr.normal);
miter.push(...attr.miter);
@ -96,6 +97,7 @@ export default class LineBuffer extends BufferBase {
sizes.push(...attr.sizes);
attrDistance.push(...attr.attrDistance);
pickingIds.push(...attr.pickingIds);
attrDashArray.push(...attr.dashArray);
});
return {
positions,
@ -105,7 +107,8 @@ export default class LineBuffer extends BufferBase {
indexArray,
pickingIds,
sizes,
attrDistance
attrDistance,
attrDashArray
};
}

View File

@ -29,6 +29,12 @@ export default function fillBuffer(layerData) {
throw new Error('Invalid shape type: ' + shape);
}
toPointShapeAttributes(polygon, coordinates, { size, shape, color, id }, attribute);
// toPointShapeAttributes(polygon, null, {}, attribute);
// instanced attributes
// attribute.vertices.push(...coordinates);
// attribute.a_size.push(...size);
// attribute.colors.push(...color);
// attribute.pickingIds.push(id);
});
return attribute;
@ -78,5 +84,8 @@ function toPointShapeAttributes(polygon, geo, style, attribute) {
attribute.colors.push(...color, ...color, ...color);
attribute.pickingIds.push(id, id, id);
// attribute.shapePositions.push(ax, ay, az, bx, by, bz, cx, cy, cz);
// attribute.normals.push(nx, ny, nz, nx, ny, nz, nx, ny, nz);
}
}

View File

@ -1,9 +1,9 @@
import * as THREE from '../../core/three';
import Material from './material';
import { getModule } from '../../util/shaderModule';
import { getModule, wrapUniforms } from '../../util/shaderModule';
import arcline_frag from '../shader/arcline_frag.glsl';
import arcline_vert from '../shader/arcline_vert.glsl';
import merge from '@antv/util/lib/deep-mix';
export function LineMaterial(options) {
const { vs, fs } = getModule('line');
@ -40,19 +40,14 @@ export function ArcLineMaterial(options) {
return material;
}
export function MeshLineMaterial(options) {
const { vs, fs } = getModule('meshline');
export function MeshLineMaterial(options, defines) {
const { vs, fs, uniforms } = getModule('meshline');
const material = new Material({
uniforms: {
u_opacity: { value: options.u_opacity || 1.0 },
u_time: { value: options.u_time || 0 },
u_zoom: { value: options.u_zoom },
u_duration: { value: options.u_duration || 2.0 },
u_interval: { value: options.u_interval || 1.0 },
u_trailLength: { value: options.u_trailLength || 0.2 },
u_activeId: { value: options.activeId || 0 },
u_activeColor: { value: options.activeColor || [ 1.0, 0, 0, 1.0 ] }
},
uniforms: wrapUniforms(merge(uniforms, options, {
u_activeId: options.activeId,
u_activeColor: options.activeColor
})),
defines,
vertexShader: vs,
fragmentShader: fs,
transparent: true,
@ -60,23 +55,3 @@ export function MeshLineMaterial(options) {
});
return material;
}
export function DashLineMaterial(options) {
const { vs, fs } = getModule('meshline');
const material = new Material({
uniforms: {
u_opacity: { value: options.u_opacity || 1.0 },
u_time: { value: options.u_time || 0 },
u_zoom: { value: options.u_zoom },
u_dashSteps: { value: options.u_dashSteps || 12 },
u_dashSmooth: { value: options.u_dashSmooth || 0.01 },
u_dashDistance: { value: options.u_dashDistance || 0.2 },
u_activeId: { value: options.activeId || 0 },
u_activeColor: { value: options.activeColor || [ 1.0, 0, 0, 1.0 ] }
},
vertexShader: vs,
fragmentShader: fs,
transparent: true
});
return material;
}

View File

@ -1,10 +0,0 @@
varying float v_lineU;
uniform float u_dashSteps;
uniform float u_dashSmooth;
uniform float u_dashDistance;
varying vec4 v_color;
void main() {
float lineUMod = mod(v_lineU, 1.0/ u_dashSteps) * u_dashSteps;
float dash = smoothstep(u_dashDistance, u_dashDistance+u_dashSmooth, length(lineUMod-0.5));
gl_FragColor = vec4(v_color.xyz * vec3(dash), v_color.a * dash);
}

View File

@ -1,23 +0,0 @@
precision highp float;
attribute float a_miter;
attribute vec4 a_color;
attribute float a_size;
uniform float u_zoom;
uniform float u_opacity;
varying vec4 v_color;
attribute float a_distance;
varying float v_lineU;
uniform float u_activeId;
uniform vec4 u_activeColor;
void main() {
v_lineU = a_distance;
mat4 matModelViewProjection = projectionMatrix * modelViewMatrix;
vec3 pointPos = position.xyz + vec3(normal * a_size * pow(2.0,20.0-u_zoom) / 2.0 * a_miter);
v_color = a_color;
v_color.a *= u_opacity;
if(pickingId == u_activeId) {
v_color = u_activeColor;
}
gl_Position = matModelViewProjection * vec4(pointPos, 1.0);
worldId = id_toPickColor(pickingId);
}

View File

@ -19,10 +19,6 @@ import mesh_line_vert from '../shader/meshline_vert.glsl';
import line_frag from '../shader/line_frag.glsl';
import line_vert from '../shader/line_vert.glsl';
// 虚线
import dash_vert from '../shader/dashline_vert.glsl';
import dash_frag from '../shader/dashline_frag.glsl';
// 热力图
import heatmap_color_vert from '../shader/heatmap_colorize_vert.glsl';
import heatmap_color_frag from '../shader/heatmap_colorize_frag.glsl';
@ -58,7 +54,6 @@ export function compileBuiltinModules() {
registerModule('pointline', { vs: point_line_vert, fs: point_line_frag });
registerModule('meshline', { vs: mesh_line_vert, fs: mesh_line_frag });
registerModule('line', { vs: line_vert, fs: line_frag });
registerModule('dashline', { vs: dash_vert, fs: dash_frag });
registerModule('heatmap_color', { vs: heatmap_color_vert, fs: heatmap_color_frag });
registerModule('heatmap_intensity', { vs: heatmap_intensity_vert, fs: heatmap_intensity_frag });
registerModule('text', { vs: text_vert, fs: text_frag });

View File

@ -1,11 +1,28 @@
precision highp float;
uniform float u_opacity;
uniform float u_opacity : 1.0;
uniform float u_dash_offset : 0.0;
uniform float u_dash_ratio : 0.0;
uniform float u_blur : 0.9;
varying vec4 v_color;
varying float vTime;
varying float v_distance;
varying float v_dash_array;
varying float v_time;
varying vec2 v_normal;
void main() {
gl_FragColor = v_color;
gl_FragColor = v_color;
#ifdef DASHLINE
gl_FragColor.a *= u_opacity * ceil(mod(v_distance + u_dash_offset, v_dash_array) - (v_dash_array * u_dash_ratio));
#else
gl_FragColor.a = v_color.a * u_opacity;
#ifdef ANIMATE
gl_FragColor.a *= vTime;
#endif
#endif
#ifdef ANIMATE
gl_FragColor.a *= v_time;
#endif
// anti-alias
float blur = 1. - smoothstep(u_blur, 1., length(v_normal));
gl_FragColor.a *= blur;
}

View File

@ -1,38 +1,48 @@
precision highp float;
attribute float a_miter;
attribute vec4 a_color;
attribute float a_size;
attribute float a_distance;
attribute float a_dash_array;
uniform float u_zoom;
uniform float u_time : 0;
uniform float u_activeId : 1;
uniform vec4 u_activeColor : [ 1.0, 0, 0, 1.0 ];
varying float v_time;
varying vec4 v_color;
uniform float u_time;
varying float vTime;
uniform float u_activeId;
uniform vec4 u_activeColor;
// animate
varying float v_distance;
varying float v_dash_array;
varying vec2 v_normal;
#ifdef ANIMATE
uniform float u_duration; // 动画持续时间
uniform float u_interval;
uniform float u_repeat;
uniform float u_trailLength;
uniform float u_duration : 2.0;
uniform float u_interval : 1.0;
uniform float u_trailLength : 0.2;
#endif
void main() {
mat4 matModelViewProjection = projectionMatrix * modelViewMatrix;
vec3 pointPos = vec3(position.xy,0.) + vec3(normal * a_size * pow(2.0,20.0-u_zoom) / 2.0 * a_miter);
v_color = a_color;
if(pickingId == u_activeId) {
v_color = a_color;
v_distance = a_distance;
v_dash_array = a_dash_array;
// anti-alias
v_normal = vec2(normal * sign(a_miter));
// extrude along normal
float extrude_scale = pow(2.0, 20.0 - u_zoom);
vec3 offset = vec3(normal * a_size * extrude_scale / 2.0 * a_miter);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position.xy + offset.xy, 0., 1.0);
#ifdef ANIMATE
float alpha =1.0 - fract( mod(1.0- a_distance,u_interval)* (1.0/u_interval) + u_time / u_duration);
alpha = (alpha + u_trailLength -1.0) / u_trailLength;
v_time = clamp(alpha,0.,1.);
#endif
// picking
if(pickingId == u_activeId) {
v_color = u_activeColor;
}
#ifdef ANIMATE
//mod(a_distance,0.2) * 5.
float alpa =1.0 - fract( mod(1.0- a_distance,u_interval)* (1.0/u_interval) + u_time / u_duration);
alpa = (alpa + u_trailLength -1.0) / u_trailLength;
vTime = clamp(alpa,0.,1.);
// vTime = (28800. + mod(u_time* 1000.,28800.)- position.z) / 100.;
#endif
worldId = id_toPickColor(pickingId);
gl_Position = matModelViewProjection * vec4(pointPos.xy, 0., 1.0);
worldId = id_toPickColor(pickingId);
}

View File

@ -1,4 +1,5 @@
import getNormal from 'polyline-normals';
import getNormals from '../../util/polyline-normals';
import flatten from '@antv/util/lib/flatten';
/**
* shape arc
@ -64,69 +65,61 @@ export function defaultLine(geo, index) {
return { positions, indexes: indexArray };
}
// mesh line
export function Line(path, props, positionsIndex) {
export function Line(path, props, positionsIndex, lengthPerDashSegment = 200) {
if (path.length === 1) path = path[0];// 面坐标转线坐标
const positions = [];
const pickingIds = [];
const normal = [];
const miter = [];
const colors = [];
const indexArray = [];
const normals = getNormal(path);
const dashArray = [];
const { normals, attrIndex, attrPos } = getNormals(path, false, positionsIndex);
let attrDistance = [];
const sizes = [];
let c = 0;
let index = positionsIndex;
const { size, color, id } = props;
path.forEach((point, pointIndex, list) => {
const i = index;
colors.push(...color);
attrPos.forEach((point, pointIndex) => {
colors.push(...color);
pickingIds.push(id);
pickingIds.push(id);
sizes.push(size[0]);
sizes.push(size[0]);
if (pointIndex !== list.length - 1) {
indexArray[c++] = i + 0;
indexArray[c++] = i + 3;
indexArray[c++] = i + 1;
indexArray[c++] = i + 0;
indexArray[c++] = i + 2;
indexArray[c++] = i + 3;
}
// point[2] = size[1];
positions.push(...point);
point[2] = size[1];
positions.push(...point);
if (pointIndex === 0) {
attrDistance.push(0, 0);
if (pointIndex === 0 || pointIndex === 1) {
attrDistance.push(0);
} else if (pointIndex % 2 === 0) {
attrDistance.push(attrDistance[pointIndex - 2]
+ lineSegmentDistance(attrPos[pointIndex - 2], attrPos[pointIndex]));
} else {
const d = attrDistance[pointIndex * 2 - 1] + lineSegmentDistance(path[pointIndex - 1], path[pointIndex]);
attrDistance.push(d, d);
attrDistance.push(attrDistance[pointIndex - 1]);
}
index += 2;
});
const totalLength = attrDistance[attrDistance.length - 1];
const ratio = lengthPerDashSegment / totalLength;
normals.forEach(n => {
const norm = n[0];
const m = n[1];
normal.push(norm[0], norm[1], 0);
normal.push(norm[0], norm[1], 0);
miter.push(-m);
miter.push(m);
dashArray.push(ratio);
});
attrDistance = attrDistance.map(d => {
return d / attrDistance[attrDistance.length - 1];
return d / totalLength;
});
return {
positions,
normal,
indexArray,
indexArray: flatten(attrIndex),
miter,
colors,
sizes,
pickingIds,
attrDistance
attrDistance,
dashArray
};
}

View File

@ -11,15 +11,19 @@ export default function DrawLine(attributes, cfg, layer) {
geometry.addAttribute('normal', new THREE.Float32BufferAttribute(attributes.normal, 3));
geometry.addAttribute('a_miter', new THREE.Float32BufferAttribute(attributes.miter, 1));
geometry.addAttribute('a_distance', new THREE.Float32BufferAttribute(attributes.attrDistance, 1));
geometry.addAttribute('a_dash_array', new THREE.Float32BufferAttribute(attributes.attrDashArray, 1));
const lineMaterial = new MeshLineMaterial({
u_opacity: style.opacity,
u_zoom: zoom,
u_time: 0,
u_dash_offset: style.dashOffset,
u_dash_ratio: style.dashRatio,
activeColor: activeOption.fill
}, {
SHAPE: false,
ANIMATE: false
ANIMATE: false,
DASHLINE: style.lineType === 'dash'
});
const lineMesh = new THREE.Mesh(geometry, lineMaterial);

View File

@ -16,6 +16,17 @@ export default function DrawFill(attributes, style) {
geometry.addAttribute('normal', new THREE.Float32BufferAttribute(attributes.normals, 3));
geometry.addAttribute('a_shape', new THREE.Float32BufferAttribute(attributes.shapePositions, 3));
geometry.addAttribute('a_size', new THREE.Float32BufferAttribute(attributes.a_size, 3));
// const instancedGeometry = new THREE.InstancedBufferGeometry();
// instancedGeometry.addAttribute('normal', new THREE.Float32BufferAttribute(attributes.normals, 3));
// instancedGeometry.addAttribute('a_shape', new THREE.Float32BufferAttribute(attributes.shapePositions, 3));
// // instanced attributes
// instancedGeometry.addAttribute('position', new THREE.InstancedBufferAttribute(new Float32Array(attributes.vertices), 3));
// instancedGeometry.addAttribute('a_color', new THREE.InstancedBufferAttribute(new Float32Array(attributes.colors), 4));
// instancedGeometry.addAttribute('pickingId', new THREE.InstancedBufferAttribute(new Float32Array(attributes.pickingIds), 1));
// instancedGeometry.addAttribute('a_size', new THREE.InstancedBufferAttribute(new Float32Array(attributes.a_size), 3));
const material = new PolygonMaterial({
u_opacity: opacity,
u_activeColor: activeColor
@ -25,6 +36,7 @@ export default function DrawFill(attributes, style) {
material.setDefinesvalue('SHAPE', true);
material.depthTest = false;
const fillMesh = new THREE.Mesh(geometry, material);
// const fillMesh = new THREE.Mesh(instancedGeometry, material);
return fillMesh;
}

View File

@ -0,0 +1,131 @@
/**
* 对于 polyline-normal 的改进
* 超过阈值miter 转成 bevel 接头
* 要注意 Three.js 中默认 THREE.FrontFaceDirectionCCW
* @see https://zhuanlan.zhihu.com/p/59541559
*/
import { direction, normal, computeMiter } from 'polyline-miter-util';
import { create, copy, dot } from 'gl-vec2';
function extrusions(positions, out, point, normal, scale) {
addNext(out, normal, -scale);
addNext(out, normal, scale);
positions.push(point);
positions.push(point);
}
function addNext(out, normal, length) {
out.push([[ normal[0], normal[1] ], length ]);
}
export default function(points, closed, indexOffset) {
const lineA = [ 0, 0 ];
const lineB = [ 0, 0 ];
const tangent = [ 0, 0 ];
const miter = [ 0, 0 ];
let _lastFlip = -1;
let _started = false;
let _normal = null;
const tmp = create();
let count = indexOffset || 0;
const miterLimit = 3;
const out = [];
const attrPos = [];
const attrIndex = [];
const attrCounters = [ 0, 0 ];
if (closed) {
points = points.slice();
points.push(points[0]);
}
const total = points.length;
for (let i = 1; i < total; i++) {
const index = count;
const last = points[i - 1];
const cur = points[i];
const next = i < points.length - 1 ? points[i + 1] : null;
attrCounters.push(i / total, i / total);
direction(lineA, cur, last);
if (!_normal) {
_normal = [ 0, 0 ];
normal(_normal, lineA);
}
if (!_started) {
_started = true;
extrusions(attrPos, out, last, _normal, 1);
}
attrIndex.push([ index + 0, index + 3, index + 1 ]);
// no miter, simple segment
if (!next) {
// reset normal
normal(_normal, lineA);
extrusions(attrPos, out, cur, _normal, 1);
attrIndex.push(
_lastFlip === 1 ? [ index + 1, index + 3, index + 2 ] : [ index, index + 2, index + 3 ]);
count += 2;
} else {
// get unit dir of next line
direction(lineB, next, cur);
// stores tangent & miter
let miterLen = computeMiter(tangent, miter, lineA, lineB, 1);
// get orientation
let flip = (dot(tangent, _normal) < 0) ? -1 : 1;
const bevel = miterLen > miterLimit;
if (bevel) {
miterLen = miterLimit;
attrCounters.push(i / total);
// next two points in our first segment
addNext(out, _normal, -flip);
attrPos.push(cur);
addNext(out, miter, miterLen * flip);
attrPos.push(cur);
attrIndex.push(_lastFlip !== -flip
? [ index + 1, index + 3, index + 2 ] : [ index, index + 2, index + 3 ]);
// now add the bevel triangle
attrIndex.push([ index + 2, index + 3, index + 4 ]);
normal(tmp, lineB);
copy(_normal, tmp); // store normal for next round
addNext(out, _normal, -flip);
attrPos.push(cur);
// the miter is now the normal for our next join
count += 3;
} else {
// next two points for our miter join
extrusions(attrPos, out, cur, miter, miterLen);
attrIndex.push(_lastFlip === 1
? [ index + 1, index + 3, index + 2 ] : [ index, index + 2, index + 3 ]);
flip = -1;
// the miter is now the normal for our next join
copy(_normal, miter);
count += 2;
}
_lastFlip = flip;
}
}
return {
normals: out,
attrIndex,
attrPos,
attrCounters
};
}

View File

@ -1,3 +1,7 @@
import uniq from '@antv/util/lib/uniq';
import isString from '@antv/util/lib/is-string';
import ColorUtil from '../attr/color-util';
const SHADER_TYPE = {
VS: 'vs',
FS: 'fs'
@ -9,9 +13,10 @@ const globalDefaultprecision = '#ifdef GL_FRAGMENT_PRECISION_HIGH\n precision hi
const globalDefaultAttribute = 'attribute float pickingId;\n varying vec4 worldId;\n';
const globalDefaultInclude = '#pragma include "pick_color"\n';
const includeRegExp = /#pragma include (["^+"]?["\ "[a-zA-Z_0-9](.*)"]*?)/g;
const uniformRegExp = /uniform\s+(bool|float|int|vec2|vec3|vec4|ivec2|ivec3|ivec4|mat2|mat3|mat4|sampler2D|samplerCube)\s+([\s\S]*?);/g;
function processModule(rawContent, includeList, type) {
return rawContent.replace(includeRegExp, (_, strMatch) => {
const compiled = rawContent.replace(includeRegExp, (_, strMatch) => {
const includeOpt = strMatch.split(' ');
const includeName = includeOpt[0].replace(/"/g, '');
@ -19,18 +24,110 @@ function processModule(rawContent, includeList, type) {
return '';
}
let txt = rawContentCache[includeName][type];
const txt = rawContentCache[includeName][type];
includeList.push(includeName);
txt = processModule(txt, includeList, type);
return txt;
const { content } = processModule(txt, includeList, type);
return content;
});
return {
content: compiled,
includeList
};
}
export function registerModule(moduleName, { vs, fs }) {
function getUniformLengthByType(type) {
let arrayLength = 0;
switch (type) {
case 'vec2':
case 'ivec2':
arrayLength = 2;
break;
case 'vec3':
case 'ivec3':
arrayLength = 3;
break;
case 'vec4':
case 'ivec4':
case 'mat2':
arrayLength = 4;
break;
case 'mat3':
arrayLength = 9;
break;
case 'mat4':
arrayLength = 16;
break;
default:
}
return arrayLength;
}
function extractUniforms(content) {
const uniforms = {};
content = content.replace(uniformRegExp, (_, type, c) => {
const defaultValues = c.split(':');
const uniformName = defaultValues[0].trim();
let defaultValue = '';
if (defaultValues.length > 1) {
defaultValue = defaultValues[1].trim();
}
// set default value for uniform according to its type
// eg. vec2 u -> [0.0, 0.0]
switch (type) {
case 'bool':
defaultValue = defaultValue === 'true';
break;
case 'float':
case 'int':
defaultValue = Number(defaultValue);
break;
case 'vec2':
case 'vec3':
case 'vec4':
case 'ivec2':
case 'ivec3':
case 'ivec4':
case 'mat2':
case 'mat3':
case 'mat4':
if (defaultValue) {
defaultValue = defaultValue.replace('[', '').replace(']', '')
.split(',')
.reduce((prev, cur) => {
prev.push(Number(cur.trim()));
return prev;
}, []);
} else {
defaultValue = new Array(getUniformLengthByType(type)).fill(0);
}
break;
default:
}
uniforms[uniformName] = defaultValue;
return `uniform ${type} ${uniformName};\n`;
});
return {
content,
uniforms
};
}
export function registerModule(moduleName, { vs, fs, uniforms: declaredUniforms }) {
const { content: extractedVS, uniforms: vsUniforms } = extractUniforms(vs);
const { content: extractedFS, uniforms: fsUniforms } = extractUniforms(fs);
rawContentCache[moduleName] = {
[SHADER_TYPE.VS]: vs,
[SHADER_TYPE.FS]: fs
[SHADER_TYPE.VS]: extractedVS,
[SHADER_TYPE.FS]: extractedFS,
uniforms: {
...vsUniforms,
...fsUniforms,
...declaredUniforms
}
};
}
@ -39,11 +136,20 @@ export function getModule(moduleName) {
return moduleCache[moduleName];
}
let vs = rawContentCache[moduleName][SHADER_TYPE.VS];
let fs = rawContentCache[moduleName][SHADER_TYPE.FS];
vs = globalDefaultAttribute + globalDefaultInclude + vs;
vs = processModule(vs, [], SHADER_TYPE.VS);
fs = processModule(fs, [], SHADER_TYPE.FS);
let rawVS = rawContentCache[moduleName][SHADER_TYPE.VS];
const rawFS = rawContentCache[moduleName][SHADER_TYPE.FS];
rawVS = globalDefaultAttribute + globalDefaultInclude + rawVS;
const { content: vs, includeList: vsIncludeList } = processModule(rawVS, [], SHADER_TYPE.VS);
let { content: fs, includeList: fsIncludeList } = processModule(rawFS, [], SHADER_TYPE.FS);
// TODO: extract uniforms and their default values from GLSL
const uniforms = uniq(vsIncludeList.concat(fsIncludeList).concat(moduleName)).reduce((prev, cur) => {
return {
...prev,
...rawContentCache[cur].uniforms
};
}, {});
/**
* set default precision for fragment shader
@ -55,7 +161,76 @@ export function getModule(moduleName) {
moduleCache[moduleName] = {
[SHADER_TYPE.VS]: vs.trim(),
[SHADER_TYPE.FS]: fs.trim()
[SHADER_TYPE.FS]: fs.trim(),
uniforms
};
return moduleCache[moduleName];
}
export function wrapUniforms(uniforms) {
return Object.keys(uniforms).reduce((prev, cur) => {
prev[cur] = {
value: uniforms[cur]
};
return prev;
}, {});
}
const DEFAULT_LIGHT = {
type: 'directional',
direction: [ 1, 10.5, 12 ],
ambient: [ 0.2, 0.2, 0.2 ],
diffuse: [ 0.6, 0.6, 0.6 ],
specular: [ 0.1, 0.1, 0.1 ]
};
const DEFAULT_DIRECTIONAL_LIGHT = {
direction: [ 0, 0, 0 ],
ambient: [ 0, 0, 0 ],
diffuse: [ 0, 0, 0 ],
specular: [ 0, 0, 0 ]
};
const DEFAULT_SPOT_LIGHT = {
position: [ 0, 0, 0 ],
direction: [ 0, 0, 0 ],
ambient: [ 0, 0, 0 ],
diffuse: [ 0, 0, 0 ],
specular: [ 0, 0, 0 ],
constant: 1,
linear: 0,
quadratic: 0,
angle: 14,
exponent: 40,
blur: 5
};
const COLOR_ATTRIBUTES = [
'ambient', 'diffuse', 'specular'
];
export function generateLightingUniforms(lights) {
const lightsMap = {
u_directional_lights: new Array(3).fill({ ...DEFAULT_DIRECTIONAL_LIGHT }),
u_num_of_directional_lights: 0,
u_spot_lights: new Array(3).fill({ ...DEFAULT_SPOT_LIGHT }),
u_num_of_spot_lights: 0
};
if (!lights || !lights.length) {
lights = [ DEFAULT_LIGHT ];
}
lights.forEach(({ type, ...rest }, i) => {
const lightsUniformName = `u_${type}_lights`;
const lightsNumUniformName = `u_num_of_${type}_lights`;
Object.keys(rest).forEach(key => {
if (isString(rest[key]) && COLOR_ATTRIBUTES.indexOf(key) > -1) {
rest[key] = ColorUtil.color2RGBA(rest[key]).slice(0, 3);
}
});
lightsMap[lightsUniformName][i] = { ...lightsMap[lightsUniformName][i], ...rest };
lightsMap[lightsNumUniformName]++;
});
return lightsMap;
}