WebGL实现可手动旋转的3D立体图

204 阅读1分钟
效果图

动画.gif

完整代码
<!DOCTYPE html>
<html>
  <head>
    <title>3D旋转柱状图</title>
    <style>
      body {
        margin: 0;
      }
      canvas {
        display: block;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas" width="800" height="800"></canvas>

    <!-- 引入 gl-matrix 库 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.4.0/gl-matrix.js"></script>

    <script>
      const canvas = document.getElementById("canvas");
      const gl = canvas.getContext("webgl");

      if (!gl) {
        alert("WebGL不可用,请更换支持WebGL的浏览器。");
      }

      // 顶点着色器程序
      const vsSource = `
            attribute vec4 aPosition;
            attribute vec4 aColor;
            uniform mat4 uModelViewMatrix;
            uniform mat4 uProjectionMatrix;
            varying lowp vec4 vColor;
            void main(void) {
                gl_Position = uProjectionMatrix * uModelViewMatrix * aPosition;
                vColor = aColor;
            }
        `;

      // 片元着色器程序
      const fsSource = `
            varying lowp vec4 vColor;
            void main(void) {
                gl_FragColor = vColor;
            }
        `;

      const vertexShader = gl.createShader(gl.VERTEX_SHADER);
      gl.shaderSource(vertexShader, vsSource);
      gl.compileShader(vertexShader);

      if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
        console.error("顶点着色器编译失败:", gl.getShaderInfoLog(vertexShader));
      }

      const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
      gl.shaderSource(fragmentShader, fsSource);
      gl.compileShader(fragmentShader);

      if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
        console.error(
          "片元着色器编译失败:",
          gl.getShaderInfoLog(fragmentShader)
        );
      }

      const shaderProgram = gl.createProgram();
      gl.attachShader(shaderProgram, vertexShader);
      gl.attachShader(shaderProgram, fragmentShader);
      gl.linkProgram(shaderProgram);
      gl.useProgram(shaderProgram);

      const positionAttributeLocation = gl.getAttribLocation(
        shaderProgram,
        "aPosition"
      );
      const colorAttributeLocation = gl.getAttribLocation(
        shaderProgram,
        "aColor"
      );
      const modelViewMatrixUniformLocation = gl.getUniformLocation(
        shaderProgram,
        "uModelViewMatrix"
      );
      const projectionMatrixUniformLocation = gl.getUniformLocation(
        shaderProgram,
        "uProjectionMatrix"
      );

      gl.enableVertexAttribArray(positionAttributeLocation);
      gl.enableVertexAttribArray(colorAttributeLocation);

      const vertices = new Float32Array([
        // 前面
        -1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 1.0, -1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0,
        1.0, 0.0, 0.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 0.0,

        // 后面
        -1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 1.0,
        1.0, -1.0, 0.0, 0.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 0.0,
      ]);

      const indices = new Uint16Array([
        0,
        1,
        2,
        0,
        2,
        3, // 前面
        4,
        5,
        6,
        4,
        6,
        7, // 后面
        0,
        3,
        7,
        0,
        7,
        4, // 左侧
        1,
        2,
        6,
        1,
        6,
        5, // 右侧
        0,
        1,
        5,
        0,
        5,
        4, // 底部
        2,
        3,
        7,
        2,
        7,
        6, // 顶部
      ]);

      const vertexBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

      const indexBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
      gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

      const projectionMatrix = mat4.create();
      const modelViewMatrix = mat4.create();
      const modelViewProjectionMatrix = mat4.create();

      const fieldOfView = Math.PI / 4;
      const aspect = canvas.width / canvas.height;
      const zNear = 0.1;
      const zFar = 100.0;
      mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);

      const translation = [0, 0, -5];
      const rotation = [0, 0, 0];
      const scale = [1, 1, 1];

      const identityMatrix = mat4.create();

      let isDragging = false;
      let prevMouseX;
      let prevMouseY;

      canvas.addEventListener("mousedown", (e) => {
        isDragging = true;
        prevMouseX = e.clientX;
        prevMouseY = e.clientY;
      });

      canvas.addEventListener("mouseup", () => {
        isDragging = false;
      });

      canvas.addEventListener("mousemove", (e) => {
        if (isDragging) {
          const deltaX = e.clientX - prevMouseX;
          const deltaY = e.clientY - prevMouseY;
          prevMouseX = e.clientX;
          prevMouseY = e.clientY;

          rotation[0] += deltaY * 0.01;
          rotation[1] += deltaX * 0.01;
        }
      });

      function render() {
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        mat4.identity(modelViewMatrix);
        mat4.translate(modelViewMatrix, modelViewMatrix, translation);
        mat4.rotateX(modelViewMatrix, modelViewMatrix, rotation[0]);
        mat4.rotateY(modelViewMatrix, modelViewMatrix, rotation[1]);
        mat4.scale(modelViewMatrix, modelViewMatrix, scale);

        mat4.multiply(
          modelViewProjectionMatrix,
          projectionMatrix,
          modelViewMatrix
        );

        gl.uniformMatrix4fv(
          modelViewMatrixUniformLocation,
          false,
          modelViewMatrix
        );
        gl.uniformMatrix4fv(
          projectionMatrixUniformLocation,
          false,
          projectionMatrix
        );

        gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
        gl.vertexAttribPointer(
          positionAttributeLocation,
          3,
          gl.FLOAT,
          false,
          24,
          0
        );
        gl.vertexAttribPointer(
          colorAttributeLocation,
          3,
          gl.FLOAT,
          false,
          24,
          12
        );
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

        gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

        requestAnimationFrame(render);
      }

      render();
    </script>
  </body>
</html>