cesium的官方例子 Custom Primitive 很有用,通过这个例子可以学到 如何在 cesium 中 自定义几何体 和 着色器。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta name="description" content="Custom Primitive example." />
<meta name="cesium-sandcastle-labels" content="Development" />
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script
type="text/javascript"
src="../../../Build/CesiumUnminified/Cesium.js"
nomodule
></script>
<script type="module" src="../load-cesium-es6.js"></script>
</head>
<body
class="sandcastle-loading"
data-sandcastle-bucket="bucket-requirejs.html"
>
<style>
@import url(../templates/bucket.css);
#toolbar input[type="range"] {
width: 70px;
}
#toolbar input {
vertical-align: middle;
padding-top: 2px;
padding-bottom: 2px;
}
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<script id="cesium_sandcastle_script">
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
const viewer = new Cesium.Viewer("cesiumContainer");
/**
* Simple example of writing a Primitive from scratch. This
* primitive displays a procedurally-generated surface at a given
* position on the globe.
*/
function CustomPrimitive(cartographicPosition) {
this.show = true;
// This is initialized in the first call of update()
// so we don't need a context
this.drawCommand = undefined;
// number of faces wide. There are resolution + 1 vertices.
this.faceResolution = 1;
// Compute a model matrix that puts the surface at a specific point on
// the globe.
this.cartographicPosition = cartographicPosition;
const cartesianPosition = Cesium.Cartographic.toCartesian(
cartographicPosition,
Cesium.Ellipsoid.WGS84,
new Cesium.Cartesian3()
);
this.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(
cartesianPosition,
Cesium.Ellipsoid.WGS84,
new Cesium.Matrix4()
);
this.halfWidthMeters = 10000;
this.boundingSphere = new Cesium.BoundingSphere(
cartesianPosition,
this.halfWidthMeters * Math.SQRT2
);
this.time = undefined;
}
/**
* generate a quad that's subdivided into faceResolution x faceResolution quads.
* The vertices will be adjusted in the vertex shader.
*/
function generateVertices(faceResolution, halfWidthMeters) {
const vertexResolution = faceResolution + 1;
const vertexCount = vertexResolution * vertexResolution;
const componentsPerVertex = 3;
const vertices = new Float32Array(vertexCount * componentsPerVertex);
for (let i = 0; i < vertexResolution; i++) {
for (let j = 0; j < vertexResolution; j++) {
const u = i / (vertexResolution - 1);
const v = j / (vertexResolution - 1);
const index = i * vertexResolution + j;
const x = halfWidthMeters * (2 * u - 1);
const y = halfWidthMeters * (2 * v - 1);
const z = 0;
vertices[index * componentsPerVertex] = x;
vertices[index * componentsPerVertex + 1] = y;
vertices[index * componentsPerVertex + 2] = z;
}
}
return vertices;
}
/**
* Tessellate a big square region into faceResolution x faceResolution quads
*/
function generateIndices(faceResolution) {
const indicesPerQuad = 6;
const indexCount = faceResolution * faceResolution * indicesPerQuad;
const indices = new Uint16Array(indexCount);
const vertexResolution = faceResolution + 1;
let quadIndex = 0;
for (let i = 0; i < faceResolution; i++) {
for (let j = 0; j < faceResolution; j++) {
const a = i * vertexResolution + j;
const b = i * vertexResolution + (j + 1);
const c = (i + 1) * vertexResolution + (j + 1);
const d = (i + 1) * vertexResolution + j;
indices[quadIndex * indicesPerQuad] = a;
indices[quadIndex * indicesPerQuad + 1] = b;
indices[quadIndex * indicesPerQuad + 2] = c;
indices[quadIndex * indicesPerQuad + 3] = c;
indices[quadIndex * indicesPerQuad + 4] = d;
indices[quadIndex * indicesPerQuad + 5] = a;
quadIndex++;
}
}
return indices;
}
const scratchColor = new Cesium.Color();
/**
* Generate a 1D texture for the color palette
*/
function generateTexture(context) {
const width = 256;
const textureTypedArray = new Uint8Array(width * 4);
for (let i = 0; i < width; i++) {
const bucket32 = 32 * Math.floor(i / 32);
const bucket4 = 4 * Math.floor(i / 4);
const color = Cesium.Color.fromHsl(
bucket32 / width,
bucket32 / width,
(255 - bucket4) / width,
1.0,
scratchColor
);
textureTypedArray[4 * i] = 255 * color.red;
textureTypedArray[4 * i + 1] = 255 * color.green;
textureTypedArray[4 * i + 2] = 255 * color.blue;
textureTypedArray[4 * i + 3] = 255 * color.alpha;
}
return new Cesium.Texture({
context: context,
pixelFormat: Cesium.PixelFormat.RGBA,
pixelDataType: Cesium.ComponentDatatype.fromTypedArray(
textureTypedArray
),
source: {
width: width,
height: 1,
arrayBufferView: textureTypedArray,
},
flipY: false,
sampler: new Cesium.Sampler({
minificationFilter: Cesium.TextureMinificationFilter.NEAREST,
magnificationFilter: Cesium.TextureMagnificationFilter.NEAREST,
}),
});
}
function initialize(primitive, context) {
// Inteference patterns made by two plane waves crossing each
// other at an angle. The color is based on the height.
const vertexShader = `
in vec3 a_position;
uniform float u_time;
void main() {
gl_Position = czm_modelViewProjection * vec4(a_position, 1.0);
}
`;
const fragmentShader = `
uniform float u_time;
uniform vec4 u_color;
void main() {
out_FragColor = u_color;
}
`;
const positionLocation = 0;
const attributeLocations = {
a_position: positionLocation,
};
const renderState = Cesium.RenderState.fromCache({
depthTest: {
enabled: true,
},
});
const shaderProgram = Cesium.ShaderProgram.fromCache({
context: context,
vertexShaderSource: vertexShader,
fragmentShaderSource: fragmentShader,
attributeLocations: attributeLocations,
});
const positionTypedArray = generateVertices(
primitive.faceResolution,
primitive.halfWidthMeters
);
primitive.typedArr = positionTypedArray;
const positionVertexBuffer = Cesium.Buffer.createVertexBuffer({
context: context,
typedArray: positionTypedArray,
usage: Cesium.BufferUsage.STATIC_DRAW,
});
const positionAttribute = {
index: positionLocation,
vertexBuffer: positionVertexBuffer,
componentsPerAttribute: 3,
componentDatatype: Cesium.ComponentDatatype.fromTypedArray(
positionTypedArray
),
};
const indexCount =
primitive.faceResolution * primitive.faceResolution * 6;
const indexTypedArray = generateIndices(primitive.faceResolution);
const indexBuffer = Cesium.Buffer.createIndexBuffer({
context: context,
typedArray: indexTypedArray,
indexDatatype: Cesium.ComponentDatatype.fromTypedArray(
indexTypedArray
),
usage: Cesium.BufferUsage.STATIC_DRAW,
});
const vertexArray = new Cesium.VertexArray({
context: context,
attributes: [positionAttribute],
indexBuffer: indexBuffer,
});
const texture = generateTexture(context);
primitive.time = performance.now();
const color = Cesium.Color.ORANGE.withAlpha(0.6)
const uniformMap = {
u_time: function () {
const now = performance.now();
return now - primitive.time;
},
u_texture: function () {
return texture;
},
u_color: () => color,
};
const drawCommand = new Cesium.DrawCommand({
boundingVolume: primitive.boundingSphere,
modelMatrix: primitive.modelMatrix,
pass: Cesium.Pass.OPAQUE,
shaderProgram: shaderProgram,
renderState: renderState,
vertexArray: vertexArray,
count: indexCount,
primitiveType: Cesium.PrimitiveType.LINE_STRIP,
uniformMap: uniformMap,
});
primitive.drawCommand = drawCommand;
}
CustomPrimitive.prototype.update = function (frameState) {
if (!this.show) {
return;
}
// Rendering resources are initialized in the first update() call
// so we have access to the rendering context
if (!Cesium.defined(this.drawCommand)) {
initialize(this, frameState.context);
}
this.typedArr[2] = 5000 * Math.sin(performance.now() * 0.001);
this.typedArr[11] = 5000 * Math.cos(performance.now() * 0.001);
this.drawCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(this.typedArr);
frameState.commandList.push(this.drawCommand);
};
const position = new Cesium.Cartographic(0, 0, 1000.0);
const primitive = viewer.scene.primitives.add(
new CustomPrimitive(position)
);
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(0, 0, 50000),
duration: 0.1,
});
//Sandcastle_End
};
if (typeof Cesium !== "undefined") {
window.startupCalled = true;
window.startup(Cesium).catch((error) => {
"use strict";
console.error(error);
});
Sandcastle.finishedLoading();
}
</script>
</body>
</html>
以上,是我对这个例子的修改,需要特别留意这处修改
this.drawCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(this.typedArr);
通过运行这个修改后的例子,说明了 可以 动态修改 着色器的 attribute 属性,从而可以实现可编辑形状的 primitive;需要特别留意的是,primitive的 modelMatrix 和 boundingSphere 需要初始化
import * as Cesium from 'cesium';
import * as THREE from 'three';
const tmpVector = new THREE.Vector3();
const tmpCenter = new THREE.Vector3();
function generateRectVertices() {
const vertexCount = 4;
const componentsPerVertex = 3;
const vertices = new Float32Array(vertexCount * componentsPerVertex);
const points = [-1, 0, -1, 1, 0, -1, 1, 0, 1, -1, 0, 1];
points.forEach((it, index) => (vertices[index] = it));
return vertices;
}
export default class EditableRectCesium {
show = true;
modelMatrix = Cesium.Matrix4.clone(
new THREE.Matrix4().elements,
new Cesium.Matrix4(),
);
boundingSphere = new Cesium.BoundingSphere(
new Cesium.Cartesian3(),
Math.SQRT2 * 1,
);
time = performance.now();
drawCommand;
/**
* @type {Float32Array}
*/
float32Array;
vectors = [];
constructor(cartesian, vectors) {
this.vectors = vectors;
this.cartesian = cartesian;
Cesium.Cartesian3.clone(cartesian, this.boundingSphere.center);
this.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(
cartesian,
Cesium.Ellipsoid.WGS84,
new Cesium.Matrix4(),
);
const enuMatInv = Cesium.Matrix4.inverse(
this.modelMatrix,
new Cesium.Matrix4(),
);
const enuMatInv3 = new THREE.Matrix4();
Cesium.Matrix4.clone(enuMatInv, enuMatInv3.elements);
this.enuMatInv3 = enuMatInv3;
}
_init(context) {
const vertexShader = `
in vec3 a_position;
uniform float u_time;
void main() {
gl_Position = czm_modelViewProjection * vec4(a_position, 1.0);
}
`;
const fragmentShader = `
uniform float u_time;
uniform vec4 u_color;
void main() {
out_FragColor = u_color;
}
`;
const positionLocation = 0;
const attributeLocations = {
a_position: positionLocation,
};
const renderState = Cesium.RenderState.fromCache({
depthTest: {
enabled: true,
},
});
const shaderProgram = Cesium.ShaderProgram.fromCache({
context: context,
vertexShaderSource: vertexShader,
fragmentShaderSource: fragmentShader,
attributeLocations: attributeLocations,
});
const positionTypedArray = generateRectVertices();
this.float32Array = positionTypedArray;
const positionVertexBuffer = Cesium.Buffer.createVertexBuffer({
context: context,
typedArray: positionTypedArray,
usage: Cesium.BufferUsage.STATIC_DRAW,
});
const positionAttribute = {
index: positionLocation,
vertexBuffer: positionVertexBuffer,
componentsPerAttribute: 3,
componentDatatype:
Cesium.ComponentDatatype.fromTypedArray(positionTypedArray),
};
const indexTypedArray = new Uint16Array([0, 1, 2, 0, 2, 3]);
const indexBuffer = Cesium.Buffer.createIndexBuffer({
context: context,
typedArray: indexTypedArray,
indexDatatype: Cesium.ComponentDatatype.fromTypedArray(indexTypedArray),
usage: Cesium.BufferUsage.STATIC_DRAW,
});
const vertexArray = new Cesium.VertexArray({
context: context,
attributes: [positionAttribute],
indexBuffer: indexBuffer,
});
const uniformMap = {
u_time: performance.now(),
u_color: () => Cesium.Color.ORANGE.withAlpha(0.8),
};
const drawCommand = new Cesium.DrawCommand({
boundingVolume: this.boundingSphere,
modelMatrix: this.modelMatrix,
pass: Cesium.Pass.TRANSLUCENT,
shaderProgram: shaderProgram,
renderState: renderState,
vertexArray: vertexArray,
count: indexTypedArray.length,
primitiveType: Cesium.PrimitiveType.TRIANGLES,
uniformMap: uniformMap,
});
this.drawCommand = drawCommand;
}
update(frameState) {
if (!this.show) {
return;
}
// Rendering resources are initialized in the first update() call
// so we have access to the rendering context
if (!Cesium.defined(this.drawCommand)) {
this._init(frameState.context);
}
// this.updatePositionAttr(this.vectors);
frameState.commandList.push(this.drawCommand);
}
updatePositionAttr(vectors) {
if (!this.drawCommand) {
return;
}
const buffer = this.drawCommand.vertexArray.getAttribute(0).vertexBuffer;
const arr = this.float32Array;
// tmpCenter.set(0, 0, 0);
vectors.forEach((vector, index) => {
// tmpCenter.add(vector);
tmpVector.copy(vector).applyMatrix4(this.enuMatInv3);
arr[index * 3] = tmpVector.x;
arr[index * 3 + 1] = tmpVector.y;
arr[index * 3 + 2] = tmpVector.z;
});
buffer.copyFromArrayView(arr);
// tmpCenter.divideScalar(4);
// const radius = vectors[0].distanceTo(vectors[2]) * 0.5;
// Cesium.Cartesian3.clone(tmpCenter, this.boundingSphere.center);
// this.boundingSphere.radius = radius;
}
}