优化如下焰尾动效:
import BaseLayerViewGL2D from "@arcgis/core/views/2d/layers/BaseLayerViewGL2D.js";
import GraphicsLayer from "@arcgis/core/layers/GraphicsLayer.js";
import * as reactiveUtils from "@arcgis/core/core/reactiveUtils.js";
import { mat3, vec2, vec3, vec4 } from "gl-matrix";
const CustomLayerView2D = BaseLayerViewGL2D.createSubclass({
aPosition: 0,
aOffset: 1,
aDistance: 2,
aSide: 3,
aColor: 4,
constructor: function () {
this.transform = mat3.create();
this.extrude = mat3.create();
this.translationToCenter = vec2.create();
this.screenTranslation = vec2.create();
this.display = mat3.fromValues(NaN, 0, 0, 0, NaN, 0, -1, 1, 1);
this.screenScaling = vec3.fromValues(NaN, NaN, 1);
this.needsUpdate = false;
},
attach: function () {
const gl = this.context;
// index buffers.
const requestUpdate = () => {
this.needsUpdate = true;
this.requestRender();
};
this.watcher = reactiveUtils.on(() => this.layer.graphics, "change", requestUpdate);
const vertexSource = `
precision highp float;
uniform mat3 u_transform;
uniform mat3 u_extrude;
uniform mat3 u_display;
attribute vec2 a_position;
attribute vec2 a_offset;
attribute float a_distance;
attribute float a_side;
attribute vec4 a_color;
varying float v_distance;
varying float v_side;
varying vec4 v_color;
void main(void) {
gl_Position.xy = (u_display * (u_transform * vec3(a_position, 1.0) + u_extrude * vec3(a_offset, 0.0))).xy;
gl_Position.zw = vec2(0.0, 1.0);
v_distance = a_distance;
v_side = a_side;
v_color = a_color;
}`;
const fragmentSource = `
precision highp float;
uniform float u_current_time;
varying float v_distance;
varying float v_side;
varying vec4 v_color;
const float TRAIL_SPEED = 50.0;
const float TRAIL_LENGTH = 300.0;
const float TRAIL_CYCLE = 1000.0;
void main(void) {
float d = mod(v_distance - u_current_time * TRAIL_SPEED, TRAIL_CYCLE);
float a1 = d < TRAIL_LENGTH ? mix(0.0, 1.0, d / TRAIL_LENGTH) : 0.0;
float a2 = exp(-abs(v_side) * 3.0);
float a = a1 * a2;
gl_FragColor = v_color * a;
}`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
// Create the shader program.
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
// Bind attributes.
gl.bindAttribLocation(this.program, this.aPosition, "a_position");
gl.bindAttribLocation(this.program, this.aOffset, "a_offset");
gl.bindAttribLocation(this.program, this.aDistance, "a_distance");
gl.bindAttribLocation(this.program, this.aSide, "a_side");
gl.bindAttribLocation(this.program, this.aColor, "a_color");
// Link.
gl.linkProgram(this.program);
// Shader objects are not needed anymore.
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
// Retrieve uniform locations once and for all.
this.uTransform = gl.getUniformLocation(
this.program,
"u_transform"
);
this.uExtrude = gl.getUniformLocation(
this.program,
"u_extrude"
);
this.uDisplay = gl.getUniformLocation(this.program, "u_display");
this.uCurrentTime = gl.getUniformLocation(
this.program,
"u_current_time"
);
this.vertexBuffer = gl.createBuffer();
this.indexBuffer = gl.createBuffer();
this.indexBufferSize = 0;
if (gl.getParameter(gl.VERSION).startsWith("WebGL 2.0")) {
this.vao = gl.createVertexArray();
this.bindVertexArray = (vao) => gl.bindVertexArray(vao);
this.deleteVertexArray = (vao) => gl.deleteVertexArray(vao);
} else {
const vaoExt = gl.getExtension('OES_vertex_array_object');
this.vao = vaoExt.createVertexArrayOES();
this.bindVertexArray = (vao) => vaoExt.bindVertexArrayOES(vao);
this.deleteVertexArray = (vao) => vaoExt.deleteVertexArrayOES(vao);
}
// Set up vertex attributes
this.bindVertexArray(this.vao);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
gl.enableVertexAttribArray(this.aPosition);
gl.enableVertexAttribArray(this.aOffset);
gl.enableVertexAttribArray(this.aDistance);
gl.enableVertexAttribArray(this.aSide);
gl.enableVertexAttribArray(this.aColor);
gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 28, 0);
gl.vertexAttribPointer(this.aOffset, 2, gl.FLOAT, false, 28, 8);
gl.vertexAttribPointer(this.aDistance, 1, gl.FLOAT, false, 28, 16);
gl.vertexAttribPointer(this.aSide, 1, gl.FLOAT, false, 28, 20);
gl.vertexAttribPointer(this.aColor, 4, gl.UNSIGNED_BYTE, true, 28, 24);
this.bindVertexArray(null);
this.centerAtLastUpdate = vec2.fromValues(
this.view.state.center[0],
this.view.state.center[1]
);
},
detach: function () {
this.watcher.remove();
const gl = this.context;
gl.deleteBuffer(this.vertexBuffer);
gl.deleteBuffer(this.indexBuffer);
this.deleteVertexArray(this.vao);
gl.deleteProgram(this.program);
},
render: function (renderParameters) {
const gl = renderParameters.context;
const state = renderParameters.state;
this.updatePositions(renderParameters);
if (this.indexBufferSize === 0) {
return;
}
mat3.identity(this.transform);
this.screenTranslation[0] = (state.pixelRatio * state.size[0]) / 2;
this.screenTranslation[1] = (state.pixelRatio * state.size[1]) / 2;
mat3.translate(
this.transform,
this.transform,
this.screenTranslation
);
mat3.rotate(
this.transform,
this.transform,
(Math.PI * state.rotation) / 180
);
this.screenScaling[0] = state.pixelRatio / state.resolution;
this.screenScaling[1] = -state.pixelRatio / state.resolution;
mat3.scale(this.transform, this.transform, this.screenScaling);
mat3.translate(
this.transform,
this.transform,
this.translationToCenter
);
mat3.identity(this.extrude);
mat3.rotate(
this.extrude,
this.extrude,
(Math.PI * state.rotation) / 180
);
const HALF_WIDTH = 12;
mat3.scale(this.extrude, this.extrude, [HALF_WIDTH, -HALF_WIDTH, 1]);
// Update view `display` matrix; it converts from pixels to normalized device coordinates.
this.display[0] = 2 / (state.pixelRatio * state.size[0]);
this.display[4] = -2 / (state.pixelRatio * state.size[1]);
// Draw.
gl.useProgram(this.program);
gl.uniformMatrix3fv(this.uTransform, false, this.transform);
gl.uniformMatrix3fv(this.uExtrude, false, this.extrude);
gl.uniformMatrix3fv(this.uDisplay, false, this.display);
gl.uniform1f(this.uCurrentTime, performance.now() / 1000.0);
this.bindVertexArray(this.vao);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.drawElements(
gl.TRIANGLES,
this.indexBufferSize,
gl.UNSIGNED_SHORT,
0
);
this.requestRender();
},
// Called internally from render().
updatePositions: function (renderParameters) {
const gl = renderParameters.context;
const stationary = renderParameters.stationary;
const state = renderParameters.state;
if (!stationary) {
vec2.sub(
this.translationToCenter,
this.centerAtLastUpdate,
state.center
);
this.requestRender();
return;
}
if (
!this.needsUpdate &&
this.translationToCenter[0] === 0 &&
this.translationToCenter[1] === 0
) {
return;
}
this.centerAtLastUpdate.set(state.center);
this.translationToCenter[0] = 0;
this.translationToCenter[1] = 0;
this.needsUpdate = false;
const graphics = this.layer.graphics;
let vtxCount = 0;
let idxCount = 0;
for (let i = 0; i < graphics.items.length; ++i) {
const graphic = graphics.items[i];
const path = graphic.geometry.paths[0];
vtxCount += path.length * 2;
idxCount += (path.length - 1) * 6;
}
const vertexData = new ArrayBuffer(7 * vtxCount * 4);
const floatData = new Float32Array(vertexData);
const colorData = new Uint8Array(vertexData);
const indexData = new Uint16Array(idxCount);
let vtxCursor = 0;
let idxCursor = 0;
for (let i = 0; i < graphics.items.length; ++i) {
const graphic = graphics.items[i];
const path = graphic.geometry.paths[0];
const color = graphic.attributes["color"];
let s = {};
for (let j = 0; j < path.length; j++) {
const p = path[j];
if (s.current) {
s.delta = [p[0] - s.current[0], p[1] - s.current[1]];
const deltaLength = Math.sqrt(s.delta[0] * s.delta[0] + s.delta[1] * s.delta[1]);
s.direction = [s.delta[0] / deltaLength, s.delta[1] / deltaLength];
const normal = [-s.direction[1], s.direction[0]];
if (s.normal) {
s.offset = [s.normal[0] + normal[0], s.normal[1] + normal[1]];
// We first normalize it.
const offsetLength = Math.sqrt(s.offset[0] * s.offset[0] + s.offset[1] * s.offset[1]);
s.offset[0] /= offsetLength;
s.offset[1] /= offsetLength;
const d = s.normal[0] * s.offset[0] + s.normal[1] * s.offset[1];
s.offset[0] /= d;
s.offset[1] /= d;
} else {
s.offset = [normal[0], normal[1]];
}
floatData[vtxCursor * 7 + 0] = s.current[0] - this.centerAtLastUpdate[0];
floatData[vtxCursor * 7 + 1] = s.current[1] - this.centerAtLastUpdate[1];
floatData[vtxCursor * 7 + 2] = s.offset[0];
floatData[vtxCursor * 7 + 3] = s.offset[1];
floatData[vtxCursor * 7 + 4] = s.distance;
floatData[vtxCursor * 7 + 5] = + 1;
colorData[4 * (vtxCursor * 7 + 6) + 0] = color[0];
colorData[4 * (vtxCursor * 7 + 6) + 1] = color[1];
colorData[4 * (vtxCursor * 7 + 6) + 2] = color[2];
colorData[4 * (vtxCursor * 7 + 6) + 3] = 255;
floatData[vtxCursor * 7 + 7] = s.current[0] - this.centerAtLastUpdate[0];
floatData[vtxCursor * 7 + 8] = s.current[1] - this.centerAtLastUpdate[1];
floatData[vtxCursor * 7 + 9] = -s.offset[0];
floatData[vtxCursor * 7 + 10] = -s.offset[1];
floatData[vtxCursor * 7 + 11] = s.distance;
floatData[vtxCursor * 7 + 12] = -1;
colorData[4 * (vtxCursor * 7 + 13) + 0] = color[0];
colorData[4 * (vtxCursor * 7 + 13) + 1] = color[1];
colorData[4 * (vtxCursor * 7 + 13) + 2] = color[2];
colorData[4 * (vtxCursor * 7 + 13) + 3] = 255;
vtxCursor += 2;
if (j >= 2) {
indexData[idxCursor + 0] = vtxCursor - 4;
indexData[idxCursor + 1] = vtxCursor - 3;
indexData[idxCursor + 2] = vtxCursor - 2;
indexData[idxCursor + 3] = vtxCursor - 3;
indexData[idxCursor + 4] = vtxCursor - 1;
indexData[idxCursor + 5] = vtxCursor - 2;
idxCursor += 6;
}
s.normal = normal;
s.distance += deltaLength;
} else {
s.distance = 0;
}
s.current = p;
}
s.offset = [s.normal[0], s.normal[1]];
floatData[vtxCursor * 7 + 0] = s.current[0] - this.centerAtLastUpdate[0];
floatData[vtxCursor * 7 + 1] = s.current[1] - this.centerAtLastUpdate[1];
floatData[vtxCursor * 7 + 2] = s.offset[0];
floatData[vtxCursor * 7 + 3] = s.offset[1];
floatData[vtxCursor * 7 + 4] = s.distance;
floatData[vtxCursor * 7 + 5] = +1;
colorData[4 * (vtxCursor * 7 + 6) + 0] = color[0];
colorData[4 * (vtxCursor * 7 + 6) + 1] = color[1];
colorData[4 * (vtxCursor * 7 + 6) + 2] = color[2];
colorData[4 * (vtxCursor * 7 + 6) + 3] = 255;
floatData[vtxCursor * 7 + 7] = s.current[0] - this.centerAtLastUpdate[0];
floatData[vtxCursor * 7 + 8] = s.current[1] - this.centerAtLastUpdate[1];
floatData[vtxCursor * 7 + 9] = -s.offset[0];
floatData[vtxCursor * 7 + 10] = -s.offset[1];
floatData[vtxCursor * 7 + 11] = s.distance;
floatData[vtxCursor * 7 + 12] = -1;
colorData[4 * (vtxCursor * 7 + 13) + 0] = color[0];
colorData[4 * (vtxCursor * 7 + 13) + 1] = color[1];
colorData[4 * (vtxCursor * 7 + 13) + 2] = color[2];
colorData[4 * (vtxCursor * 7 + 13) + 3] = 255;
vtxCursor += 2;
indexData[idxCursor + 0] = vtxCursor - 4;
indexData[idxCursor + 1] = vtxCursor - 3;
indexData[idxCursor + 2] = vtxCursor - 2;
indexData[idxCursor + 3] = vtxCursor - 3;
indexData[idxCursor + 4] = vtxCursor - 1;
indexData[idxCursor + 5] = vtxCursor - 2;
idxCursor += 6;
s.current = null;
}
// Upload data to the GPU.
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);
// Record number of indices.
this.indexBufferSize = indexData.length;
}
});
const CustomLayer = GraphicsLayer.createSubclass({
createLayerView: function (view) {
if (view.type === "2d") {
return new CustomLayerView2D({
view: view,
layer: this
});
}
}
});
export default CustomLayer;