Cesium - custom primitve 的使用

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;
  }
}

你可能感兴趣的:(webgl,Cesium)