小白学webgl合集-绘制有透视颜色不一样的立方体

效果

原理

结合透视矩阵和视觉矩阵进行绘制

知识点

01透视矩阵

透视矩阵将视图空间中的坐标转换为裁剪空间中的坐标,使得更远的物体看起来更小。

function perspectiveMatrix(fov, aspect, near, far) {
  const f = 1.0 / Math.tan(fov / 2);
  const nf = 1 / (near - far);
  
  return new Float32Array([
    f / aspect, 0, 0, 0,
    0, f, 0, 0,
    0, 0, (far + near) * nf, -1,
    0, 0, (2 * far * near) * nf, 0
  ]);
}
参数解释

函数 perspectiveMatrix 创建并返回一个透视投影矩阵,用于3D图形的透视投影。以下是每个参数的详细说明:

  1. fov(Field of View, 视野)

    • 视野角度,即观察者在垂直方向上能够看到的视角。通常以弧度表示,角度单位可以通过 Math.PI / 180 转换为弧度。
    • 例如,45 * Math.PI / 180 表示45度的视角。
  2. aspect(Aspect Ratio, 纵横比)

    • 视图的宽高比,即视图的宽度除以高度。它决定了横向和纵向的缩放比例。
    • 例如,如果视口的宽度是800像素,高度是600像素,则 aspect = 800 / 600 = 4 / 3。
  3. near(Near Clipping Plane, 近裁剪平面)

    • 近裁剪平面的距离,即从观察者到最近可见物体的距离。该值必须大于0。
    • 例如,设置为0.1表示从0.1单位距离开始可见物体。
  4. far(Far Clipping Plane, 远裁剪平面)

    • 远裁剪平面的距离,即从观察者到最远可见物体的距离。该值必须大于 near 值。
    • 例如,设置为100.0表示到100单位距离结束可见物体。

02视图矩阵

视图矩阵将世界空间中的坐标转换为视图空间中的坐标,表示相机的视点。

function lookAtMatrix(eye, center, up) {
  const zAxis = normalize(subtractVectors(eye, center));
  const xAxis = normalize(cross(up, zAxis));
  const yAxis = normalize(cross(zAxis, xAxis));

  return new Float32Array([
    xAxis[0], yAxis[0], zAxis[0], 0,
    xAxis[1], yAxis[1], zAxis[1], 0,
    xAxis[2], yAxis[2], zAxis[2], 0,
    -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1
  ]);
}

function normalize(v) {
  const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  return [v[0] / length, v[1] / length, v[2] / length];
}

function subtractVectors(a, b) {
  return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
}

function cross(a, b) {
  return [
    a[1] * b[2] - a[2] * b[1],
    a[2] * b[0] - a[0] * b[2],
    a[0] * b[1] - a[1] * b[0]
  ];
}

function dot(a, b) {
  return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
参数解释

函数 lookAtMatrix 用于生成一个视图矩阵,用于将场景从世界空间转换到观察者空间。它通常用于实现相机的视图转换,使得相机看向特定的方向。这个函数的参数如下:

  1. eye

    • 相机的位置,表示观察者所在的点。
    • 例如 [1, 1, 1] 表示相机位于 (1, 1, 1) 的位置。
  2. center

    • 视点,表示相机所看向的目标点。
    • 例如 [0, 0, 0] 表示相机看向 (0, 0, 0) 的位置。
  3. up

    • 上方向向量,表示哪一个方向是相机的“上”方向。通常为 [0, 1, 0],表示正Y方向。
函数 lookAtMatrix 用于生成一个视图矩阵,用于将场景从世界空间转换到观察者空间。它通常用于实现相机的视图转换,使得相机看向特定的方向。这个函数的参数如下:

eye:

相机的位置,表示观察者所在的点。
例如 [1, 1, 1] 表示相机位于 (1, 1, 1) 的位置。
center:

视点,表示相机所看向的目标点。
例如 [0, 0, 0] 表示相机看向 (0, 0, 0) 的位置。
up:

上方向向量,表示哪一个方向是相机的“上”方向。通常为 [0, 1, 0],表示正Y方向。

逻辑

设置透视矩阵和视觉矩阵

 代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>立方体绘制</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      canvas {
        margin: 50px auto 0;
        display: block;
        background: yellow;
      }
    </style>
    <script src="test.js"></script>
  </head>
  <body>
    <canvas id="canvas" width="400" height="400"></canvas>
    <script>
      // 获取
      const canvas = document.getElementById('canvas')
      const gl = canvas.getContext('webgl')
      // 定义片源着色器和顶点着色器
      const vsSource = `
        attribute vec4 apos;
        attribute vec4 acolor;
        varying vec4 vcolor;
        uniform mat4 u_projectionMatrix;
      uniform mat4 u_viewMatrix;
        void main() {
          gl_Position =u_projectionMatrix * u_viewMatrix * apos;
          vcolor = acolor;
        }
      `
      const fsSource = `
        precision mediump float;
        varying vec4 vcolor;
        void main() {
          gl_FragColor = vcolor;
        }
      `
      //   初始化webgl
      const program = initShader(gl, vsSource, fsSource)
      gl.useProgram(program)
      // 获取 uniform 位置
      const u_projectionMatrixLocation = gl.getUniformLocation(program, 'u_projectionMatrix')
      const u_viewMatrixLocation = gl.getUniformLocation(program, 'u_viewMatrix')
      // 创建透视矩阵和视图矩阵
      const fov = Math.PI / 4 // 45度视角
      const aspect = canvas.width / canvas.height
      const near = 0.1
      const far = 10.0
      // lookAtMatrix 函数通过计算相机的位置、目标点和上方向,生成一个视图矩阵
      const projectionMatrix = perspectiveMatrix(fov, aspect, near, far)
      const viewMatrix = lookAtMatrix([3, 1, 7], [0, 0, 0], [0, 1, 0])
      function lookAtMatrix(eye, center, up) {
        const zAxis = normalize(subtractVectors(eye, center))
        const xAxis = normalize(cross(up, zAxis))
        const yAxis = normalize(cross(zAxis, xAxis))

        return new Float32Array([
          xAxis[0],
          yAxis[0],
          zAxis[0],
          0,
          xAxis[1],
          yAxis[1],
          zAxis[1],
          0,
          xAxis[2],
          yAxis[2],
          zAxis[2],
          0,
          -dot(xAxis, eye),
          -dot(yAxis, eye),
          -dot(zAxis, eye),
          1
        ])
      }

      function normalize(v) {
        const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
        return [v[0] / length, v[1] / length, v[2] / length]
      }

      function subtractVectors(a, b) {
        return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
      }

      function cross(a, b) {
        return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
      }

      function dot(a, b) {
        return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
      }

      function perspectiveMatrix(fov, aspect, near, far) {
        const f = 1.0 / Math.tan(fov / 2)
        const nf = 1 / (near - far)

        return new Float32Array([f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) * nf, -1, 0, 0, 2 * far * near * nf, 0])
      }

      // 设置投影矩阵和视图矩阵
      gl.uniformMatrix4fv(u_projectionMatrixLocation, false, projectionMatrix)
      gl.uniformMatrix4fv(u_viewMatrixLocation, false, viewMatrix)
      // 创建缓存区
      function initBuffer(gl, program) {
        const aposLocation = gl.getAttribLocation(program, 'apos')
        const acolorLocation = gl.getAttribLocation(program, 'acolor')
        const verticesColors = new Float32Array([
          // 前面 (红色)
          -0.5,
          -0.5,
          0.5,
          1,
          0,
          0,
          1, // 左下
          0.5,
          -0.5,
          0.5,
          1,
          0,
          0,
          1, // 右下
          0.5,
          0.5,
          0.5,
          1,
          0,
          0,
          1, // 右上
          -0.5,
          0.5,
          0.5,
          1,
          0,
          0,
          1, // 左上

          // 后面 (绿色)
          -0.5,
          0.5,
          -0.5,
          0,
          1,
          0,
          1, // 左上
          -0.5,
          -0.5,
          -0.5,
          0,
          1,
          0,
          1, // 左下
          0.5,
          -0.5,
          -0.5,
          0,
          1,
          0,
          1, // 右下
          0.5,
          0.5,
          -0.5,
          0,
          1,
          0,
          1, // 右上

          // 左面 (蓝色)
          -0.5,
          -0.5,
          -0.5,
          0,
          0,
          1,
          1, // 后下
          -0.5,
          0.5,
          -0.5,
          0,
          0,
          1,
          1, // 后上
          -0.5,
          0.5,
          0.5,
          0,
          0,
          1,
          1, // 前上
          -0.5,
          -0.5,
          0.5,
          0,
          0,
          1,
          1, // 前下

          // 右面 (洋红色)
          0.5,
          -0.5,
          -0.5,
          1,
          0,
          1,
          1, // 后下
          0.5,
          0.5,
          -0.5,
          1,
          0,
          1,
          1, // 后上
          0.5,
          0.5,
          0.5,
          1,
          0,
          1,
          1, // 前上
          0.5,
          -0.5,
          0.5,
          1,
          0,
          1,
          1, // 前下

          // 顶面 (黄色)
          -0.5,
          0.5,
          0.5,
          1,
          1,
          0,
          1, // 前左
          0.5,
          0.5,
          0.5,
          1,
          1,
          0,
          1, // 前右
          0.5,
          0.5,
          -0.5,
          1,
          1,
          0,
          1, // 后右
          -0.5,
          0.5,
          -0.5,
          1,
          1,
          0,
          1, // 后左

          // 底面 (青色)
          -0.5,
          -0.5,
          0.5,
          0,
          1,
          1,
          1, // 前左
          0.5,
          -0.5,
          0.5,
          0,
          1,
          1,
          1, // 前右
          0.5,
          -0.5,
          -0.5,
          0,
          1,
          1,
          1, // 后右
          -0.5,
          -0.5,
          -0.5,
          0,
          1,
          1,
          1 // 后左
        ])

        const indices = new Uint16Array([
          // 前面
          0, 1, 2, 0, 2, 3,
          // 后面
          4, 5, 6, 4, 6, 7,
          // 左面
          8, 9, 10, 8, 10, 11,
          // 右面
          12, 13, 14, 12, 14, 15,
          // 顶面
          16, 17, 18, 16, 18, 19,
          // 底面
          20, 21, 22, 20, 22, 23
        ])

        const vertexColorBuffer = gl.createBuffer()
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer)
        gl.bufferData(gl.ARRAY_BUFFER, verticesColors, 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 FSIZE = verticesColors.BYTES_PER_ELEMENT
        // 启用顶点属性并指向顶点缓冲区中的数据
        gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, FSIZE * 7, 0)
        gl.enableVertexAttribArray(aposLocation)

        gl.vertexAttribPointer(acolorLocation, 4, gl.FLOAT, false, FSIZE * 7, FSIZE * 3)
        gl.enableVertexAttribArray(acolorLocation)
      }
      initBuffer(gl, program)
      // 启用深度测试
      // 清除缓冲区时同时清除颜色缓冲区和深度缓冲区:
      gl.enable(gl.DEPTH_TEST)
      gl.clearColor(0, 0, 0, 1)
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
      gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0)
    </script>
  </body>
</html>

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值