WebGL教程学习笔记(持续更新)

4 篇文章 0 订阅

WebGL教程icon-default.png?t=N7T8http://www.webgl3d.cn/WebGL/在学习《WebGL教程》的过程中,经常会遇到有的章节中缺失源码的问题(后来才知道源码需要购买),比较影响学习效率,本人结合教程内容、网络搜索和自己的推导,尽最大努力补充了部分缺失的源码,并与教程的章节同步展示。

以下代码都基于HTML + Vue3实现。

第一章 WebGL零基础快速入门

5.绘制一个立方体—WebGL旋转变换

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>绘制一个立方体—WebGL旋转变换</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue
      
      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
        
              void main() {
                // 设置几何体旋转角度为30度,并把角度值转化为弧度值
                float radian = radians(30.0);
                // 求解y轴旋转角度余弦值
                float cos = cos(radian);
                // 求解y轴旋转角度正弦值
                float sin = sin(radian);
                // y轴顺时针旋转矩阵
                mat4 yMatrix = mat4(
                  sin, 0, cos, 0,
                  0, 1, 0, 0,
                  cos, 0, -sin, 0,
                  0, 0, 0, 1
                );
                // x轴旋转矩阵
                mat4 xMatrix = mat4(
                  1, 0, 0, 0,
                  0, cos, -sin, 0,
                  0, sin, cos, 0,
                  0, 0, 0, 1
                );
                gl_Position = xMatrix * (yMatrix * apos); // 顶点位置
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
              void main() {
                gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 定义片元颜色
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)
            // 数组里48个元素,每间隔3个为一组,分别代表xyz轴上的坐标值
            const data = new Float32Array([
              // 前面的4个点
              -0.5, 0.5, 0.5,
              0.5, 0.5, 0.5,
              0.5, -0.5, 0.5,
              -0.5, -0.5, 0.5,
              // 后面的4个点
              -0.5, -0.5, -0.5,
              -0.5, 0.5, -0.5,
              0.5, 0.5, -0.5,
              0.5, -0.5, -0.5,
              // 用于绘制前后两个面连线的8个点
              -0.5, 0.5, 0.5,
              -0.5, 0.5, -0.5,
              0.5, 0.5, -0.5,
              0.5, 0.5, 0.5,
              0.5, -0.5, 0.5,
              0.5, -0.5, -0.5,
              -0.5, -0.5, -0.5,
              -0.5, -0.5, 0.5,
            ])
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 创建缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)
            // LINE_LOOP模式绘制前四个点
            gl.drawArrays(gl.LINE_LOOP, 0, 4)
            // LINE_LOOP模式从第五个点开始绘制四个点
            gl.drawArrays(gl.LINE_LOOP, 4, 4)
            // LINES模式绘制后8个点
            gl.drawArrays(gl.LINES, 8, 8)
          }
          
          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

若想让立方体逆时针旋转则将顶点着色器源码替换为以下代码即可:

// 顶点着色器源码
const vertexShaderSource = `
  // attribute声明vec4类型变量apos
  attribute vec4 apos;

  void main() {
    // 设置几何体旋转角度为30度,并把角度值转化为弧度值
    float radian = radians(30.0);
    // 求解y轴旋转角度余弦值
    float cos = cos(radian);
    // 求解y轴旋转角度正弦值
    float sin = sin(radian);
    // y轴逆时针旋转矩阵
    mat4 yMatrix = mat4(
      cos, 0, sin, 0,
      0, 1, 0, 0,
      -sin, 0, cos, 0,
      0, 0, 0, 1
    );
    // x轴旋转矩阵
    mat4 xMatrix = mat4(
      1, 0, 0, 0,
      0, cos, -sin, 0,
      0, sin, cos, 0,
      0, 0, 0, 1
    );
    gl_Position = xMatrix * (yMatrix * apos); // 顶点位置
  }
`

6.WebGL顶点索引绘制

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>WebGL顶点索引绘制</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
        
              void main() {
                // 设置几何体旋转角度为30度,并把角度值转化为弧度值
                float radian = radians(30.0);
                // 求解y轴旋转角度余弦值
                float cos = cos(radian);
                // 求解y轴旋转角度正弦值
                float sin = sin(radian);
                // y轴逆时针旋转矩阵
                // mat4 yMatrix = mat4(
                //   cos, 0, sin, 0,
                //   0, 1, 0, 0,
                //   -sin, 0, cos, 0,
                //   0, 0, 0, 1
                // );
                // y轴顺时针旋转矩阵
                mat4 yMatrix = mat4(
                  sin, 0, cos, 0,
                  0, 1, 0, 0,
                  cos, 0, -sin, 0,
                  0, 0, 0, 1
                );
                // x轴旋转矩阵
                mat4 xMatrix = mat4(
                  1, 0, 0, 0,
                  0, cos, -sin, 0,
                  0, sin, cos, 0,
                  0, 0, 0, 1
                );
                gl_Position = xMatrix * (yMatrix * apos); // 顶点位置
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
              void main() {
                gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 定义片元颜色
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)

            // 数组里48个元素,每间隔3个为一组,分别代表xyz轴上的坐标值
            const data = new Float32Array([
              // 前面的4个点
              0.5,  0.5,  0.5, // 顶点0
              -0.5,  0.5,  0.5, // 顶点1
              -0.5, -0.5,  0.5, // 顶点2
              0.5, -0.5,  0.5, // 顶点3
              // 后面的4个点
              0.5,  0.5, -0.5, // 顶点4
              -0.5,  0.5, -0.5, // 顶点5
              -0.5, -0.5, -0.5, // 顶点6
              0.5, -0.5, -0.5 // 顶点7
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

            // 顶点索引数组
            const indexes = new Uint8Array([
              // 前四个点对应索引值
              0, 1, 2, 3, // gl.LINE_LOOP模式四个点绘制一个矩形框
              //后四个顶点对应索引值
              4, 5, 6, 7, // gl.LINE_LOOP模式四个点绘制一个矩形框
              // 前后对应点对应索引值  
              0, 4, // 两个点绘制一条直线
              1, 5, // 两个点绘制一条直线
              2, 6, // 两个点绘制一条直线
              3, 7 // 两个点绘制一条直线
            ])
            // 创建顶点索引缓冲区对象
            const indexesBuffer = gl.createBuffer()
            // 绑定顶点索引缓冲区对象,激活indexesBuffer
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer)
            // 索引数组indexes数据传入缓冲区
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW)

            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // LINE_LOOP模式绘制前四个点
            gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0)
            // LINE_LOOP模式从第五个点开始绘制四个点
            gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4)
            // LINES模式绘制后8个点
            gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8)
          }
          
          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

7.varying变量和颜色插值

课程源码

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <titlevarying变量和颜色插值课程源码</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
              // attribute声明顶点颜色变量
              attribute vec4 a_color;
              // varying声明顶点颜色插值后变量
              varying vec4 v_color;
        
              void main() {
                gl_Position = apos; // 顶点位置
                // 顶点颜色插值计算
                v_color = a_color;
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
            // 所有float类型数据的精度是lowp
            precision lowp float;
            // 接收顶点着色器中v_color数据
            varying vec4 v_color;

              void main() {
                gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)

            // 创建顶点位置数据数组data,存储两个顶点(-0.5, 0.5)、(0.5, 0.5)
            const data = new Float32Array([-0.5, 0.5, 0.5, 0.5])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 2, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // 创建顶点颜色数组colorData,存储两个顶点对应RGB颜色值(0, 0, 1)、(1, 0, 0)
            const colorData = new Float32Array([0, 0, 1, 1, 0, 0])
            // 创建缓冲区colorBuffer
            const colorBuffer = gl.createBuffer()
            // 绑定顶点颜色缓冲区对象
            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
            // 传入顶点颜色数据colorData
            gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
            // 获取顶点着色器的颜色变量a_color
            const a_color = gl.getAttribLocation(program, 'a_color')
            // 缓冲区中的数据按照一定的规律传递给颜色变量a_color
            gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_color)

            // 绘制线条插值效果
            gl.drawArrays(gl.LINES, 0, 2)
          }
          
          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

课后练习1.创建一个彩色三角形

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>varying变量和颜色插值课后练习1</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
              // attribute声明顶点颜色变量
              attribute vec4 a_color;
              // varying声明顶点颜色插值后变量
              varying vec4 v_color;
        
              void main() {
                gl_Position = apos; // 顶点位置
                // 顶点颜色插值计算
                v_color = a_color;
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
            // 所有float类型数据的精度是lowp
            precision lowp float;
            // 接收顶点着色器中v_color数据
            varying vec4 v_color;

              void main() {
                gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)

            // 创建顶点位置数据数组data,存储3个顶点(-0.5, 0.5)、(0.5, 0.5)、(0.5, -0.5)
            const data = new Float32Array([-0.5, 0.5, 0.5, 0.5, 0.5, -0.5])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 2, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // 创建顶点颜色数组colorData,存储3个顶点对应RGB颜色值(0, 0, 1)、(1, 0, 0)、(0, 0, 1)
            const colorData = new Float32Array([0, 0, 1, 1, 0, 0, 0, 0, 1])
            // 创建缓冲区colorBuffer
            const colorBuffer = gl.createBuffer()
            // 绑定顶点颜色缓冲区对象
            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
            // 传入顶点颜色数据colorData
            gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
            // 获取顶点着色器的颜色变量a_color
            const a_color = gl.getAttribLocation(program, 'a_color')
            // 缓冲区中的数据按照一定的规律传递给颜色变量a_color
            gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_color)

            // 执行绘制三角形命令
            gl.drawArrays(gl.TRIANGLES, 0, 3)
          }
          
          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

课后练习2.两个单色三角面(不同颜色)

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>varying变量和颜色插值课后练习2</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
              // attribute声明顶点颜色变量
              attribute vec4 a_color;
              // varying声明顶点颜色插值后变量
              varying vec4 v_color;
        
              void main() {
                gl_Position = apos; // 顶点位置
                // 顶点颜色插值计算
                v_color = a_color;
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
            // 所有float类型数据的精度是lowp
            precision lowp float;
            // 接收顶点着色器中v_color数据
            varying vec4 v_color;

              void main() {
                gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)

            // 创建顶点位置数据数组data,存储6个顶点
            const data = new Float32Array([
              -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, // 第一个三角形的三个点
              -0.5, 0.5, 0.5, -0.5, -0.5, -0.5 // 第二个三角形的三个点
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 2, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // 创建顶点颜色数组colorData,存储6个顶点对应RGB颜色值
            const colorData = new Float32Array([
              // 三个红色点
              1, 0, 0,
              1, 0, 0,
              1, 0, 0,
              // 三个蓝色点
              0, 0, 1,
              0, 0, 1,
              0, 0, 1
            ])
            // 创建缓冲区colorBuffer
            const colorBuffer = gl.createBuffer()
            // 绑定顶点颜色缓冲区对象
            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
            // 传入顶点颜色数据colorData
            gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
            // 获取顶点着色器的颜色变量a_color
            const a_color = gl.getAttribLocation(program, 'a_color')
            // 缓冲区中的数据按照一定的规律传递给颜色变量a_color
            gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_color)

            // 执行绘制三角形命令
            gl.drawArrays(gl.TRIANGLES, 0, 3)
            gl.drawArrays(gl.TRIANGLES, 3, 3)
          }
          
          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

课后练习3.颜色插值(顶点位置、颜色使用一个缓冲区存储)

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>varying变量和颜色插值课后练习3</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
              // attribute声明顶点颜色变量
              attribute vec4 a_color;
              // varying声明顶点颜色插值后变量
              varying vec4 v_color;
        
              void main() {
                gl_Position = apos; // 顶点位置
                // 顶点颜色插值计算
                v_color = a_color;
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
            // 所有float类型数据的精度是lowp
            precision lowp float;
            // 接收顶点着色器中v_color数据
            varying vec4 v_color;

              void main() {
                gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)

            // 创建顶点位置数据数组data,存储两个顶点(-0.5, 0.5)、(0.5, 0.5)
            // 存储两个顶点对应RGB颜色值(0, 0, 1)、(1, 0, 0)
            const data = new Float32Array([
              -0.5, 0.5,
              0, 0, 1,
              0.5, 0.5,
              1, 0, 0
            ])
            // 创建顶点缓冲区对象,传入顶点颜色、位置数据
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 4表示data数组一个元素占据的字节数
            // 倒数第二个参数4*5表示每5个元素是一个选择单元
            // 第2个参数2表示从5元素组成的一个选择单元中选择前2个作为顶点位置数据
            gl.vertexAttribPointer(aposLocation, 2, gl.FLOAT, false, 4 * 5, 4 * 2)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // 获取顶点着色器的颜色变量a_color
            const a_color = gl.getAttribLocation(program, 'a_color')
            // 最后一个参数4*2表示5元素组成的一个选择单元中偏移2个元素
            // 第2个参数3表示从5元素组成的一个选择单元中选择后三个作为顶点颜色数据
            gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 4 * 5, 4 * 2)
            // 允许数据传递
            gl.enableVertexAttribArray(a_color)

            // 绘制线条
            gl.drawArrays(gl.LINES, 0, 2)
          }
          
          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

8.立方体-每个面一种颜色

课程源码

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>立方体-每个面一种颜色</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
              // attribute声明顶点颜色变量
              attribute vec4 a_color;
              // varying声明顶点颜色插值后变量
              varying vec4 v_color;
        
              void main() {
                // 设置几何体旋转角度为30度,并把角度值转化为弧度值
                float radian = radians(30.0);
                // 求解y轴旋转角度余弦值
                float cos = cos(radian);
                // 求解y轴旋转角度正弦值
                float sin = sin(radian);

                // y轴逆时针旋转矩阵
                // mat4 yMatrix = mat4(
                //   cos, 0, sin, 0,
                //   0, 1, 0, 0,
                //   -sin, 0, cos, 0,
                //   0, 0, 0, 1
                // );

                // y轴顺时针旋转矩阵
                mat4 yMatrix = mat4(
                  sin, 0, cos, 0,
                  0, 1, 0, 0,
                  cos, 0, -sin, 0,
                  0, 0, 0, 1
                );

                // x轴旋转矩阵
                mat4 xMatrix = mat4(
                  1, 0, 0, 0,
                  0, cos, -sin, 0,
                  0, sin, cos, 0,
                  0, 0, 0, 1
                );

                gl_Position = xMatrix * (yMatrix * apos); // 顶点位置
                // 顶点颜色插值计算
                v_color = a_color;
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
              // 所有float类型数据的精度是lowp
              precision lowp float;
              // 接收顶点着色器中v_color数据
              varying vec4 v_color;

              void main() {
                gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)
            // 绘制立方体线框
            drawCubeLine(gl, program)
            // 绘制立方体6个面
            drawCubeSide(gl, program)
          }

          // 绘制立方体线框
          const drawCubeLine = (gl, program) => {
            // 数组里48个元素,每间隔3个为一组,分别代表xyz轴上的坐标值
            const data = new Float32Array([
              // 前面的4个点
              0.5,  0.5,  0.5, // 顶点0
              -0.5,  0.5,  0.5, // 顶点1
              -0.5, -0.5,  0.5, // 顶点2
              0.5, -0.5,  0.5, // 顶点3
              // 后面的4个点
              0.5,  0.5, -0.5, // 顶点4
              -0.5,  0.5, -0.5, // 顶点5
              -0.5, -0.5, -0.5, // 顶点6
              0.5, -0.5, -0.5 // 顶点7
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            
            // 顶点索引数组
            const indexes = new Uint8Array([
              // 前四个点对应索引值
              0, 1, 2, 3, // gl.LINE_LOOP模式四个点绘制一个矩形框
              //后四个顶点对应索引值
              4, 5, 6, 7, // gl.LINE_LOOP模式四个点绘制一个矩形框
              // 前后对应点对应索引值  
              0, 4, // 两个点绘制一条直线
              1, 5, // 两个点绘制一条直线
              2, 6, // 两个点绘制一条直线
              3, 7 // 两个点绘制一条直线
            ])
            // 创建顶点索引缓冲区对象
            const indexesBuffer = gl.createBuffer()
            // 绑定顶点索引缓冲区对象,激活indexesBuffer
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer)
            // 索引数组indexes数据传入缓冲区
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW)
            
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)
            
            // LINE_LOOP模式绘制前四个点
            gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0)
            // LINE_LOOP模式从第五个点开始绘制四个点
            gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4)
            // LINES模式绘制后8个点
            gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8)
          }

          // 绘制立方体6个面
          const drawCubeSide = (gl, program) => {
            // 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
            const data = new Float32Array([
              // 面1
              .5, .5, .5,
              -.5, .5, .5,
              -.5, -.5, .5,
              .5, .5, .5,
              -.5, -.5, .5,
              .5, -.5, .5,
              // 面2
              .5, .5, .5,
              .5, -.5, .5,
              .5, -.5, -.5,
              .5, .5, .5,
              .5, -.5, -.5,
              .5, .5, -.5,
              // 面3
              .5, .5, .5,
              .5, .5, -.5,
              -.5, .5, -.5,
              .5, .5, .5,
              -.5, .5, -.5,
              -.5, .5, .5,
              // 面4
              -.5, .5, .5,
              -.5, .5, -.5,
              -.5, -.5, -.5,
              -.5, .5, .5,
              -.5, -.5, -.5,
              -.5, -.5, .5,
              // 面5
              -.5, -.5, -.5,
              .5, -.5, -.5,
              .5, -.5, .5,
              -.5, -.5, -.5,
              .5, -.5, .5,
              -.5, -.5, .5,
              // 面6
              .5, -.5, -.5,
              -.5, -.5, -.5,
              -.5, .5, -.5,
              .5, -.5, -.5,
              -.5, .5, -.5,
              .5, .5, -.5
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // 创建顶点颜色数组colorData
            const colorData = new Float32Array([
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
              0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // 面2绿色
              0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // 面3蓝色
              1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
              0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
              1,1,1, 1,1,1, 1,1,1, 1,1,1, 1,1,1, 1,1,1 // 面6白色
            ])
            // 创建缓冲区colorBuffer
            const colorBuffer = gl.createBuffer()
            // 绑定顶点颜色缓冲区对象
            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
            // 传入顶点颜色数据colorData
            gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
            // 获取顶点着色器的颜色变量a_color
            const a_color = gl.getAttribLocation(program, 'a_color')
            // 缓冲区中的数据按照一定的规律传递给颜色变量a_color
            gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_color)

            /**执行绘制之前,一定要开启深度测试,以免颜色混乱**/
            gl.enable(gl.DEPTH_TEST)

            // 执行绘制三角形命令
            gl.drawArrays(gl.TRIANGLES, 0, 36)
          }

          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

测试

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>立方体-每个面一种颜色-测试</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
              // attribute声明顶点颜色变量
              attribute vec4 a_color;
              // varying声明顶点颜色插值后变量
              varying vec4 v_color;
        
              void main() {
                // 设置几何体旋转角度为30度,并把角度值转化为弧度值
                float radian = radians(30.0);
                // 求解y轴旋转角度余弦值
                float cos = cos(radian);
                // 求解y轴旋转角度正弦值
                float sin = sin(radian);

                // y轴逆时针旋转矩阵
                // mat4 yMatrix = mat4(
                //   cos, 0, sin, 0,
                //   0, 1, 0, 0,
                //   -sin, 0, cos, 0,
                //   0, 0, 0, 1
                // );

                // y轴顺时针旋转矩阵
                mat4 yMatrix = mat4(
                  sin, 0, cos, 0,
                  0, 1, 0, 0,
                  cos, 0, -sin, 0,
                  0, 0, 0, 1
                );

                // x轴旋转矩阵
                mat4 xMatrix = mat4(
                  1, 0, 0, 0,
                  0, cos, -sin, 0,
                  0, sin, cos, 0,
                  0, 0, 0, 1
                );

                gl_Position = xMatrix * (yMatrix * apos); // 顶点位置
                // 顶点颜色插值计算
                v_color = a_color;
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
              // 所有float类型数据的精度是lowp
              precision lowp float;
              // 接收顶点着色器中v_color数据
              varying vec4 v_color;

              void main() {
                gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)
            // 绘制立方体线框
            drawCubeLine(gl, program)
            // 绘制立方体6个面
            drawCubeSide(gl, program)
          }

          // 绘制立方体线框
          const drawCubeLine = (gl, program) => {
            // 数组里48个元素,每间隔3个为一组,分别代表xyz轴上的坐标值
            const data = new Float32Array([
              // 前面的4个点
              0.5,  0.5,  0.5, // 顶点0
              -0.5,  0.5,  0.5, // 顶点1
              -0.5, -0.5,  0.5, // 顶点2
              0.5, -0.5,  0.5, // 顶点3
              // 后面的4个点
              0.5,  0.5, -0.5, // 顶点4
              -0.5,  0.5, -0.5, // 顶点5
              -0.5, -0.5, -0.5, // 顶点6
              0.5, -0.5, -0.5 // 顶点7
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            
            // 顶点索引数组
            const indexes = new Uint8Array([
              // 前四个点对应索引值
              0, 1, 2, 3, // gl.LINE_LOOP模式四个点绘制一个矩形框
              //后四个顶点对应索引值
              4, 5, 6, 7, // gl.LINE_LOOP模式四个点绘制一个矩形框
              // 前后对应点对应索引值  
              0, 4, // 两个点绘制一条直线
              1, 5, // 两个点绘制一条直线
              2, 6, // 两个点绘制一条直线
              3, 7 // 两个点绘制一条直线
            ])
            // 创建顶点索引缓冲区对象
            const indexesBuffer = gl.createBuffer()
            // 绑定顶点索引缓冲区对象,激活indexesBuffer
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer)
            // 索引数组indexes数据传入缓冲区
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW)
            
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)
            
            // LINE_LOOP模式绘制前四个点
            gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0)
            // LINE_LOOP模式从第五个点开始绘制四个点
            gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4)
            // LINES模式绘制后8个点
            gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8)
          }

          // 绘制立方体6个面
          const drawCubeSide = (gl, program) => {
            // 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
            const data = new Float32Array([
              // 面1
              .5, .5, .5,
              -.5, .5, .5,
              -.5, -.5, .5,
              .5, .5, .5,
              -.5, -.5, .5,
              .5, -.5, .5,
              // 面2
              .5, .5, .5,
              .5, -.5, .5,
              .5, -.5, -.5,
              .5, .5, .5,
              .5, -.5, -.5,
              .5, .5, -.5,
              // 面3
              .5, .5, .5,
              .5, .5, -.5,
              -.5, .5, -.5,
              .5, .5, .5,
              -.5, .5, -.5,
              -.5, .5, .5,
              // 面4
              -.5, .5, .5,
              -.5, .5, -.5,
              -.5, -.5, -.5,
              -.5, .5, .5,
              -.5, -.5, -.5,
              -.5, -.5, .5,
              // 面5
              -.5, -.5, -.5,
              .5, -.5, -.5,
              .5, -.5, .5,
              -.5, -.5, -.5,
              .5, -.5, .5,
              -.5, -.5, .5,
              // 面6
              .5, -.5, -.5,
              -.5, -.5, -.5,
              -.5, .5, -.5,
              .5, -.5, -.5,
              -.5, .5, -.5,
              .5, .5, -.5
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // 创建顶点颜色数组colorData
            const colorData = new Float32Array([
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
              .9,0,0, .9,0,0, .9,0,0, .9,0,0, .9,0,0, .9,0,0, // 面2绿色 R=0.9
              .8,0,0, .8,0,0, .8,0,0, .8,0,0, .8,0,0, .8,0,0, // 面3蓝色 R=0.8
              1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
              0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 // 面6白色
            ])
            // 创建缓冲区colorBuffer
            const colorBuffer = gl.createBuffer()
            // 绑定顶点颜色缓冲区对象
            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
            // 传入顶点颜色数据colorData
            gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
            // 获取顶点着色器的颜色变量a_color
            const a_color = gl.getAttribLocation(program, 'a_color')
            // 缓冲区中的数据按照一定的规律传递给颜色变量a_color
            gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_color)

            /**执行绘制之前,一定要开启深度测试,以免颜色混乱**/
            gl.enable(gl.DEPTH_TEST)

            // 执行绘制三角形命令
            gl.drawArrays(gl.TRIANGLES, 0, 36)
          }

          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

9.WebGL光照渲染立方体

前置数学概念及计算公式

  • 向量的大小,也就是向量的长度(或称模)。向量a的模记作|a|。
  • 向量的模是非负实数,向量的模是可以比较大小的。向量a=(x,y) ,向量a的模=√x²+y²。
  • 因为方向不能比较大小,所以向量也就不能比较大小。对于向量来说“大于”和“小于”的概念是没有意义的。例如向量AB>向量CD是没有意义的。

9.1 光照模型

平行光漫反射简单数学模型:

漫反射光的颜色 = 几何体表面基色 x 光线颜色 x 光线入射角余弦值

漫反射数学模型RGB分量表示:

(R2,G2,B2) = (R1,G1,B1) x (R0,G0,B0) x cosθ

漫反射数学模型WebGL着色器语言表示:

// 角度值转化为弧度值
float radian = radians(60.0);
// reflectedLight的结果是(0.5, 0, 0)
vec3 reflectedLight = vec3(1.0, 0.0, 0.0) * vec3(1.0, 0.0, 0.0) * cos(radian)

平行光镜面反射数学模型:

镜面反射光的颜色 = 几何体表面基色 x 光线颜色 x 视线与反射光线的夹角余弦值n

环境光照数学模型:

环境反射光颜色 = 几何体表面基色 x 环境光颜色

复合光照数学模型:

总反射光线 = 漫反射光线 + 镜面反射光线 + 环境反射光线

9.2立方体添加平行光

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>WebGL光照渲染立方体-立方体添加平行光</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
              // attribute声明顶点颜色变量
              attribute vec4 a_color;
              // 顶点法向量变量
              attribute vec4 a_normal;
              // uniform声明平行光颜色变量
              uniform vec3 u_lightColor;
              // uniform声明平行光传入方向变量
              uniform vec3 u_lightDirection;
              // varying声明顶点颜色插值后变量
              varying vec4 v_color;
        
              void main() {
                // 设置几何体旋转角度为30度,并把角度值转化为弧度值
                float radian = radians(30.0);
                // 求解y轴旋转角度余弦值
                float cos = cos(radian);
                // 求解y轴旋转角度正弦值
                float sin = sin(radian);

                // y轴逆时针旋转矩阵
                // mat4 yMatrix = mat4(
                //   cos, 0, sin, 0,
                //   0, 1, 0, 0,
                //   -sin, 0, cos, 0,
                //   0, 0, 0, 1
                // );

                // y轴顺时针旋转矩阵
                mat4 yMatrix = mat4(
                  sin, 0, cos, 0,
                  0, 1, 0, 0,
                  cos, 0, -sin, 0,
                  0, 0, 0, 1
                );

                // x轴旋转矩阵
                mat4 xMatrix = mat4(
                  1, 0, 0, 0,
                  0, cos, -sin, 0,
                  0, sin, cos, 0,
                  0, 0, 0, 1
                );

                gl_Position = xMatrix * yMatrix * apos; // 顶点位置

                // 顶点法向量进行矩阵变换,然后归一化
                vec3 normal = normalize((xMatrix * yMatrix * a_normal).xyz);
                // 点光源照射顶点的方向归一化
                vec3 lightDirection = normalize(u_lightDirection);
                // 计算平行光方向向量和顶点法向量的点积
                float dot = max(dot(lightDirection, normal), 0.0);
                // 计算反射后的颜色
                vec3 reflectedLight = u_lightColor * a_color.rgb * dot;
                // 顶点颜色插值计算
                v_color = vec4(reflectedLight, a_color.a);
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
              // 所有float类型数据的精度是lowp
              precision lowp float;
              // 接收顶点着色器中v_color数据
              varying vec4 v_color;

              void main() {
                gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)
            // 绘制立方体线框
            drawCubeLine(gl, program)
            // 绘制立方体6个面
            drawCubeSide(gl, program)
          }

          // 绘制立方体线框
          const drawCubeLine = (gl, program) => {
            // 数组里48个元素,每间隔3个为一组,分别代表xyz轴上的坐标值
            const data = new Float32Array([
              // 前面的4个点
              0.5,  0.5,  0.5, // 顶点0
              -0.5,  0.5,  0.5, // 顶点1
              -0.5, -0.5,  0.5, // 顶点2
              0.5, -0.5,  0.5, // 顶点3
              // 后面的4个点
              0.5,  0.5, -0.5, // 顶点4
              -0.5,  0.5, -0.5, // 顶点5
              -0.5, -0.5, -0.5, // 顶点6
              0.5, -0.5, -0.5 // 顶点7
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            
            // 顶点索引数组
            const indexes = new Uint8Array([
              // 前四个点对应索引值
              0, 1, 2, 3, // gl.LINE_LOOP模式四个点绘制一个矩形框
              //后四个顶点对应索引值
              4, 5, 6, 7, // gl.LINE_LOOP模式四个点绘制一个矩形框
              // 前后对应点对应索引值  
              0, 4, // 两个点绘制一条直线
              1, 5, // 两个点绘制一条直线
              2, 6, // 两个点绘制一条直线
              3, 7 // 两个点绘制一条直线
            ])
            // 创建顶点索引缓冲区对象
            const indexesBuffer = gl.createBuffer()
            // 绑定顶点索引缓冲区对象,激活indexesBuffer
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer)
            // 索引数组indexes数据传入缓冲区
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW)
            
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)
            
            // LINE_LOOP模式绘制前四个点
            gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0)
            // LINE_LOOP模式从第五个点开始绘制四个点
            gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4)
            // LINES模式绘制后8个点
            gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8)
          }

          // 绘制立方体6个面
          const drawCubeSide = (gl, program) => {
            // 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
            const data = new Float32Array([
              // 面1
              .5, .5, .5,
              -.5, .5, .5,
              -.5, -.5, .5,
              .5, .5, .5,
              -.5, -.5, .5,
              .5, -.5, .5,
              // 面2
              .5, .5, .5,
              .5, -.5, .5,
              .5, -.5, -.5,
              .5, .5, .5,
              .5, -.5, -.5,
              .5, .5, -.5,
              // 面3
              .5, .5, .5,
              .5, .5, -.5,
              -.5, .5, -.5,
              .5, .5, .5,
              -.5, .5, -.5,
              -.5, .5, .5,
              // 面4
              -.5, .5, .5,
              -.5, .5, -.5,
              -.5, -.5, -.5,
              -.5, .5, .5,
              -.5, -.5, -.5,
              -.5, -.5, .5,
              // 面5
              -.5, -.5, -.5,
              .5, -.5, -.5,
              .5, -.5, .5,
              -.5, -.5, -.5,
              .5, -.5, .5,
              -.5, -.5, .5,
              // 面6
              .5, -.5, -.5,
              -.5, -.5, -.5,
              -.5, .5, -.5,
              .5, -.5, -.5,
              -.5, .5, -.5,
              .5, .5, -.5
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // 创建顶点颜色数组colorData
            const colorData = new Float32Array([
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
              0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // 面2绿色
              0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // 面3蓝色
              1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
              0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 // 面6白色
            ])
            // 创建缓冲区colorBuffer
            const colorBuffer = gl.createBuffer()
            // 绑定顶点颜色缓冲区对象
            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
            // 传入顶点颜色数据colorData
            gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
            // 获取顶点着色器的颜色变量a_color
            const a_color = gl.getAttribLocation(program, 'a_color')
            // 缓冲区中的数据按照一定的规律传递给颜色变量a_color
            gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_color)

            /**
             * 从program对象获取相关的变量
             * attribute变量声明的方法使用getAttribLocation()方法
             * uniform变量声明的方法使用getUniformLocation()方法
             **/
            const a_normal = gl.getAttribLocation(program, 'a_normal')
            const u_lightColor = gl.getUniformLocation(program, 'u_lightColor')
            const u_lightDirection = gl.getUniformLocation(program, 'u_lightDirection')

            // 顶点法向量数组normalData
            const normalData = new Float32Array([
              0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // z轴正方向——面1
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // x轴正方向——面2
              0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // y轴正方向——面3
              -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, // x轴负方向——面4
              0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, // y轴负方向——面5
              0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1 // z轴负方向——面6
            ])
            const normalBuffer = gl.createBuffer()
            gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer)
            gl.bufferData(gl.ARRAY_BUFFER, normalData, gl.STATIC_DRAW)
            gl.vertexAttribPointer(a_normal, 3, gl.FLOAT, false, 0, 0)
            gl.enableVertexAttribArray(a_normal)

            /**
             * 给平行光传入颜色和方向数据,RGB(1,1,1),单位向量(x,y,z)
             **/
            gl.uniform3f(u_lightColor, 1.0, 1.0, 1.0)
            // 保证向量(x,y,-z)的长度为1,即单位向量
            // 如果不是单位向量,也可以再来着色器代码中进行归一化
            const x = 1 / Math.sqrt(15), y = 2 / Math.sqrt(15), z = 3 / Math.sqrt(15)
            gl.uniform3f(u_lightDirection, x, y, -z) // 改变光传入方向数据可以观察到立方体表面颜色的变化

            /**执行绘制之前,一定要开启深度测试,以免颜色混乱**/
            gl.enable(gl.DEPTH_TEST)

            // 执行绘制三角形命令
            gl.drawArrays(gl.TRIANGLES, 0, 36)
          }

          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 如有报错则在控制台显示
            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
              const info = gl.getProgramInfoLog(program)
              throw new Error('Could not compile WebGL program. \n\n' + info)
            }
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

改变光传入方向数据后

const x = 1 / Math.sqrt(15), y = 2 / Math.sqrt(15), z = 3 / Math.sqrt(15)
gl.uniform3f(u_lightDirection, x, y, z) // 改变光传入方向数据可以观察到立方体表面颜色的变化

9.3立方体添加点光源

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>WebGL光照渲染立方体-立方体添加点光源</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
              // attribute声明顶点颜色变量
              attribute vec4 a_color;
              // 顶点法向量变量
              attribute vec4 a_normal;
              // uniform声明点光源颜色变量
              uniform vec3 u_lightColor;
              // uniform声明点光源位置变量
              uniform vec3 u_lightPosition;
              // varying声明顶点颜色插值后变量
              varying vec4 v_color;
        
              void main() {
                // 设置几何体旋转角度为30度,并把角度值转化为弧度值
                float radian = radians(30.0);
                // 求解y轴旋转角度余弦值
                float cos = cos(radian);
                // 求解y轴旋转角度正弦值
                float sin = sin(radian);

                // y轴逆时针旋转矩阵
                // mat4 yMatrix = mat4(
                //   cos, 0, sin, 0,
                //   0, 1, 0, 0,
                //   -sin, 0, cos, 0,
                //   0, 0, 0, 1
                // );

                // y轴顺时针旋转矩阵
                mat4 yMatrix = mat4(
                  sin, 0, cos, 0,
                  0, 1, 0, 0,
                  cos, 0, -sin, 0,
                  0, 0, 0, 1
                );

                // x轴旋转矩阵
                mat4 xMatrix = mat4(
                  1, 0, 0, 0,
                  0, cos, -sin, 0,
                  0, sin, cos, 0,
                  0, 0, 0, 1
                );

                gl_Position = xMatrix * yMatrix * apos; // 顶点位置

                // 顶点法向量进行矩阵变换,然后归一化
                vec3 normal = normalize((xMatrix * yMatrix * a_normal).xyz);
                // 计算点光源照射顶点的方向并归一化
                vec3 lightDirection = normalize(vec3(gl_Position) - u_lightPosition);
                // 计算点光源方向向量和顶点法向量的点积
                float dot = max(dot(lightDirection, normal), 0.0);
                // 计算反射后的颜色
                vec3 reflectedLight = u_lightColor * a_color.rgb * dot;
                // 顶点颜色插值计算
                v_color = vec4(reflectedLight, a_color.a);
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
              // 所有float类型数据的精度是lowp
              precision lowp float;
              // 接收顶点着色器中v_color数据
              varying vec4 v_color;

              void main() {
                gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)
            // 绘制立方体6个面
            drawCubeSide(gl, program)
          }

          // 绘制立方体6个面
          const drawCubeSide = (gl, program) => {
            // 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
            const data = new Float32Array([
              // 面1
              .5, .5, .5,
              -.5, .5, .5,
              -.5, -.5, .5,
              .5, .5, .5,
              -.5, -.5, .5,
              .5, -.5, .5,
              // 面2
              .5, .5, .5,
              .5, -.5, .5,
              .5, -.5, -.5,
              .5, .5, .5,
              .5, -.5, -.5,
              .5, .5, -.5,
              // 面3
              .5, .5, .5,
              .5, .5, -.5,
              -.5, .5, -.5,
              .5, .5, .5,
              -.5, .5, -.5,
              -.5, .5, .5,
              // 面4
              -.5, .5, .5,
              -.5, .5, -.5,
              -.5, -.5, -.5,
              -.5, .5, .5,
              -.5, -.5, -.5,
              -.5, -.5, .5,
              // 面5
              -.5, -.5, -.5,
              .5, -.5, -.5,
              .5, -.5, .5,
              -.5, -.5, -.5,
              .5, -.5, .5,
              -.5, -.5, .5,
              // 面6
              .5, -.5, -.5,
              -.5, -.5, -.5,
              -.5, .5, -.5,
              .5, -.5, -.5,
              -.5, .5, -.5,
              .5, .5, -.5
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // 创建顶点颜色数组colorData
            const colorData = new Float32Array([
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
              0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // 面2绿色
              0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // 面3蓝色
              1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
              0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 // 面6白色
            ])
            // 创建缓冲区colorBuffer
            const colorBuffer = gl.createBuffer()
            // 绑定顶点颜色缓冲区对象
            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
            // 传入顶点颜色数据colorData
            gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
            // 获取顶点着色器的颜色变量a_color
            const a_color = gl.getAttribLocation(program, 'a_color')
            // 缓冲区中的数据按照一定的规律传递给颜色变量a_color
            gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_color)

            /**
             * 从program对象获取相关的变量
             * attribute变量声明的方法使用getAttribLocation()方法
             * uniform变量声明的方法使用getUniformLocation()方法
             **/
            const a_normal = gl.getAttribLocation(program, 'a_normal')
            const u_lightColor = gl.getUniformLocation(program, 'u_lightColor')
            const u_lightPosition = gl.getUniformLocation(program, 'u_lightPosition')

            // 顶点法向量数组normalData
            const normalData = new Float32Array([
              0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // z轴正方向——面1
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // x轴正方向——面2
              0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // y轴正方向——面3
              -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, // x轴负方向——面4
              0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, // y轴负方向——面5
              0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1 // z轴负方向——面6
            ])
            const normalBuffer = gl.createBuffer()
            gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer)
            gl.bufferData(gl.ARRAY_BUFFER, normalData, gl.STATIC_DRAW)
            gl.vertexAttribPointer(a_normal, 3, gl.FLOAT, false, 0, 0)
            gl.enableVertexAttribArray(a_normal)

            /**
             * 给点光源传入颜色和方向数据,RGB(1,1,1),单位向量(x,y,z)
             **/
            gl.uniform3f(u_lightColor, 1.0, 1.0, 1.0)
            gl.uniform3f(u_lightPosition, 2.0, 3.0, 4.0) // 改变点光源位置数据可以观察到立方体表面颜色的变化

            /**执行绘制之前,一定要开启深度测试,以免颜色混乱**/
            gl.enable(gl.DEPTH_TEST)

            // 执行绘制三角形命令
            gl.drawArrays(gl.TRIANGLES, 0, 36)
          }

          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 如有报错则在控制台显示
            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
              const info = gl.getProgramInfoLog(program)
              throw new Error('Could not compile WebGL program. \n\n' + info)
            }
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

 改变点光源位置数据后

gl.uniform3f(u_lightPosition, -2, 0, 3) // 改变点光源位置数据可以观察到立方体表面颜色的变化

10.立方体旋转动画

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>立方体旋转动画</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
              // attribute声明顶点颜色变量
              attribute vec4 a_color;
              // 顶点法向量变量
              attribute vec4 a_normal;
              // uniform声明点光源颜色变量
              uniform vec3 u_lightColor;
              // uniform声明点光源位置变量
              uniform vec3 u_lightPosition;
              /* uniform声明旋转矩阵变量mx、my */
              uniform mat4 mx; // 绕x轴旋转矩阵
              uniform mat4 my; // 绕y轴旋转矩阵
              // varying声明顶点颜色插值后变量
              varying vec4 v_color;
        
              void main() {
                gl_Position = mx * my * apos; // 顶点位置

                // 顶点法向量进行矩阵变换,然后归一化
                vec3 normal = normalize((mx * my * a_normal).xyz);
                // 计算点光源照射顶点的方向并归一化
                vec3 lightDirection = normalize(vec3(gl_Position) - u_lightPosition);
                // 计算点光源方向向量和顶点法向量的点积
                float dot = max(dot(lightDirection, normal), 0.0);
                // 计算反射后的颜色
                vec3 reflectedLight = u_lightColor * a_color.rgb * dot;
                // 顶点颜色插值计算
                v_color = vec4(reflectedLight, a_color.a);
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
              // 所有float类型数据的精度是lowp
              precision lowp float;
              // 接收顶点着色器中v_color数据
              varying vec4 v_color;

              void main() {
                gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)
            // 绘制立方体6个面
            drawCubeSide(gl, program)
          }

          // 绘制立方体6个面
          const drawCubeSide = (gl, program) => {
            // 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
            const data = new Float32Array([
              // 面1
              .5, .5, .5,
              -.5, .5, .5,
              -.5, -.5, .5,
              .5, .5, .5,
              -.5, -.5, .5,
              .5, -.5, .5,
              // 面2
              .5, .5, .5,
              .5, -.5, .5,
              .5, -.5, -.5,
              .5, .5, .5,
              .5, -.5, -.5,
              .5, .5, -.5,
              // 面3
              .5, .5, .5,
              .5, .5, -.5,
              -.5, .5, -.5,
              .5, .5, .5,
              -.5, .5, -.5,
              -.5, .5, .5,
              // 面4
              -.5, .5, .5,
              -.5, .5, -.5,
              -.5, -.5, -.5,
              -.5, .5, .5,
              -.5, -.5, -.5,
              -.5, -.5, .5,
              // 面5
              -.5, -.5, -.5,
              .5, -.5, -.5,
              .5, -.5, .5,
              -.5, -.5, -.5,
              .5, -.5, .5,
              -.5, -.5, .5,
              // 面6
              .5, -.5, -.5,
              -.5, -.5, -.5,
              -.5, .5, -.5,
              .5, -.5, -.5,
              -.5, .5, -.5,
              .5, .5, -.5
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // 创建顶点颜色数组colorData
            const colorData = new Float32Array([
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
              0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // 面2绿色
              0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // 面3蓝色
              1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
              0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 // 面6白色
            ])
            // 创建缓冲区colorBuffer
            const colorBuffer = gl.createBuffer()
            // 绑定顶点颜色缓冲区对象
            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
            // 传入顶点颜色数据colorData
            gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
            // 获取顶点着色器的颜色变量a_color
            const a_color = gl.getAttribLocation(program, 'a_color')
            // 缓冲区中的数据按照一定的规律传递给颜色变量a_color
            gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_color)

            /**
             * 从program对象获取相关的变量
             * attribute变量声明的方法使用getAttribLocation()方法
             * uniform变量声明的方法使用getUniformLocation()方法
             **/
            const a_normal = gl.getAttribLocation(program, 'a_normal')
            const u_lightColor = gl.getUniformLocation(program, 'u_lightColor')
            const u_lightPosition = gl.getUniformLocation(program, 'u_lightPosition')

            // 顶点法向量数组normalData
            const normalData = new Float32Array([
              0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // z轴正方向——面1
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // x轴正方向——面2
              0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // y轴正方向——面3
              -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, // x轴负方向——面4
              0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, // y轴负方向——面5
              0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1 // z轴负方向——面6
            ])
            const normalBuffer = gl.createBuffer()
            gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer)
            gl.bufferData(gl.ARRAY_BUFFER, normalData, gl.STATIC_DRAW)
            gl.vertexAttribPointer(a_normal, 3, gl.FLOAT, false, 0, 0)
            gl.enableVertexAttribArray(a_normal)

            /**
             * 给点光源传入颜色和方向数据,RGB(1,1,1),单位向量(x,y,z)
             **/
            gl.uniform3f(u_lightColor, 1.0, 1.0, 1.0)
            gl.uniform3f(u_lightPosition, 2.0, 3.0, 4.0) // 改变点光源位置数据可以观察到立方体表面颜色的变化

            /* 从program对象获得旋转矩阵变量mx、my地址 */
            const mx = gl.getUniformLocation(program, 'mx')
            const my = gl.getUniformLocation(program, 'my')
            const PI = Math.PI

            /* 绕x轴旋转45度 */
            let angle = Math.PI / 4 // 起始角度
            const mxArr = new Float32Array([
              1, 0, 0, 0,
              0, Math.cos(angle), -Math.sin(angle), 0,
              0, Math.sin(angle), Math.cos(angle), 0,
              0, 0, 0, 1
            ]);
            // 把数据mxArr传递给着色器旋转矩阵变量mx
            gl.uniformMatrix4fv(mx, false, mxArr)

            /* 绕轴旋转30度 */
            const myRadian = PI / 6
            const myArr = new Float32Array([
              Math.sin(myRadian), 0, Math.cos(myRadian), 0,
              0, 1, 0, 0,
              Math.cos(myRadian), 0, -Math.sin(myRadian), 0,
              0, 0, 0, 1
            ])
            // 把数据myArr传递给着色器旋转矩阵变量my
            gl.uniformMatrix4fv(my, false, myArr)

            /**执行绘制之前,一定要开启深度测试,以免颜色混乱**/
            gl.enable(gl.DEPTH_TEST)

            // 定义绘制函数draw(),定时更新旋转矩阵数据,并调用WebGL绘制API
            const draw = () => {
              // gl.clear(gl.COLOR_BUFFER_BIT) // 清空画布上一帧图像
              /*
               * 立方体绕y轴旋转
               */
              angle += 0.01 // 每次渲染角度递增,每次渲染不同的角度
              const sin = Math.sin(angle) //旋转角度正弦值
              const cos = Math.cos(angle) // 旋转角度余弦值
              const myArr = new Float32Array([
                cos, 0, -sin, 0,
                0, 1, 0, 0,
                sin, 0, cos, 0,
                0, 0, 0, 1
              ])
              gl.uniformMatrix4fv(my, false, myArr)
              requestAnimationFrame(draw)
              /**执行绘制命令**/
              gl.drawArrays(gl.TRIANGLES, 0, 36)
            }
            draw()
          }

          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 如有报错则在控制台显示
            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
              const info = gl.getProgramInfoLog(program)
              throw new Error('Could not compile WebGL program. \n\n' + info)
            }
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

11.WebGL绘制多个几何体

方式二(重用数据)

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>WebGL绘制多个几何体-方式二(重用数据)</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              // attribute声明vec4类型变量apos
              attribute vec4 apos;
              // attribute声明顶点颜色变量
              attribute vec4 a_color;
              // 顶点法向量变量
              attribute vec4 a_normal;
              // uniform声明点光源颜色变量
              uniform vec3 u_lightColor;
              // uniform声明点光源位置变量
              uniform vec3 u_lightPosition;
              /**uniform声明旋转矩阵变量mx、my,平移矩阵Tx**/
              uniform mat4 mx; // 绕x轴旋转矩阵
              uniform mat4 my; // 绕y轴旋转矩阵
              uniform mat4 Tx; // 沿着x轴平移矩阵
              // varying声明顶点颜色插值后变量
              varying vec4 v_color;
        
              void main() {
                // 设置几何体旋转角度为30度,并把角度值转化为弧度值
                float radian = radians(30.0);
                // 求解y轴旋转角度余弦值
                float cos = cos(radian);
                // 求解y轴旋转角度正弦值
                float sin = sin(radian);
                // 顶点位置
                gl_Position = Tx * mx * my * apos;
                // gl_Position = mx * my * Tx * apos; // 连乘顺序改变,运算结果也会改变

                // 顶点法向量进行矩阵变换,然后归一化
                vec3 normal = normalize((mx * my * a_normal).xyz);
                // 计算点光源照射顶点的方向并归一化
                vec3 lightDirection = normalize(vec3(gl_Position) - u_lightPosition);
                // 计算点光源方向向量和顶点法向量的点积
                float dot = max(dot(lightDirection, normal), 0.0);
                // 计算反射后的颜色
                vec3 reflectedLight = u_lightColor * a_color.rgb * dot;
                // 顶点颜色插值计算
                v_color = vec4(reflectedLight, a_color.a);
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
              // 所有float类型数据的精度是lowp
              precision lowp float;
              // 接收顶点着色器中v_color数据
              varying vec4 v_color;

              void main() {
                gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)
            // 绘制立方体6个面
            drawCubeSide(gl, program)
          }

          // 绘制立方体6个面
          const drawCubeSide = (gl, program) => {
            // 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
            const data = new Float32Array([
              // 面1
              .3, .3, .3,
              -.3, .3, .3,
              -.3, -.3, .3,
              .3, .3, .3,
              -.3, -.3, .3,
              .3, -.3, .3,
              // 面2
              .3, .3, .3,
              .3, -.3, .3,
              .3, -.3, -.3,
              .3, .3, .3,
              .3, -.3, -.3,
              .3, .3, -.3,
              // 面3
              .3, .3, .3,
              .3, .3, -.3,
              -.3, .3, -.3,
              .3, .3, .3,
              -.3, .3, -.3,
              -.3, .3, .3,
              // 面4
              -.3, .3, .3,
              -.3, .3, -.3,
              -.3, -.3, -.3,
              -.3, .3, .3,
              -.3, -.3, -.3,
              -.3, -.3, .3,
              // 面5
              -.3, -.3, -.3,
              .3, -.3, -.3,
              .3, -.3, .3,
              -.3, -.3, -.3,
              .3, -.3, .3,
              -.3, -.3, .3,
              // 面6
              .3, -.3, -.3,
              -.3, -.3, -.3,
              -.3, .3, -.3,
              .3, -.3, -.3,
              -.3, .3, -.3,
              .3, .3, -.3,
              // 立方体2的顶点坐标数据
            ])
            // 创建顶点缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定顶点缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
            const aposLocation = gl.getAttribLocation(program, 'apos')
            // 缓冲区中的数据按照一定的规律传递给位置变量apos
            gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(aposLocation)

            // 创建顶点颜色数组colorData
            const colorData = new Float32Array([
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
              0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // 面2绿色
              0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // 面3蓝色
              1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
              0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 // 面6白色
            ])
            // 创建缓冲区colorBuffer
            const colorBuffer = gl.createBuffer()
            // 绑定顶点颜色缓冲区对象
            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
            // 传入顶点颜色数据colorData
            gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
            // 获取顶点着色器的颜色变量a_color
            const a_color = gl.getAttribLocation(program, 'a_color')
            // 缓冲区中的数据按照一定的规律传递给颜色变量a_color
            gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_color)

            /**
             * 从program对象获取相关的变量
             * attribute变量声明的方法使用getAttribLocation()方法
             * uniform变量声明的方法使用getUniformLocation()方法
             **/
            const a_normal = gl.getAttribLocation(program, 'a_normal')
            const u_lightColor = gl.getUniformLocation(program, 'u_lightColor')
            const u_lightPosition = gl.getUniformLocation(program, 'u_lightPosition')

            // 顶点法向量数组normalData
            const normalData = new Float32Array([
              0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // z轴正方向——面1
              1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // x轴正方向——面2
              0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // y轴正方向——面3
              -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, // x轴负方向——面4
              0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, // y轴负方向——面5
              0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1 // z轴负方向——面6
            ])
            const normalBuffer = gl.createBuffer()
            gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer)
            gl.bufferData(gl.ARRAY_BUFFER, normalData, gl.STATIC_DRAW)
            gl.vertexAttribPointer(a_normal, 3, gl.FLOAT, false, 0, 0)
            gl.enableVertexAttribArray(a_normal)

            /**
             * 给点光源传入颜色和方向数据,RGB(1,1,1),单位向量(x,y,z)
             **/
            gl.uniform3f(u_lightColor, 1.0, 1.0, 1.0)
            gl.uniform3f(u_lightPosition, 2.0, 3.0, 4.0) // 改变点光源位置数据可以观察到立方体表面颜色的变化

            /* 从program对象获得旋转矩阵变量mx、my地址 */
            const mx = gl.getUniformLocation(program, 'mx')
            const my = gl.getUniformLocation(program, 'my')
            const Tx = gl.getUniformLocation(program, 'Tx')
            // 旋转矩阵的旋转角度数据
            const angle = Math.PI / 4 // 旋转角度
            const sin = Math.sin(angle)
            const cos = Math.cos(angle)
            // 旋转矩阵数据
            const mxArr = new Float32Array([1,0,0,0,  0,cos,-sin,0,  0,sin,cos,0,  0,0,0,1])
            const myArr = new Float32Array([cos,0,-sin,0,  0,1,0,0,  sin,0,cos,0,  0,0,0,1])
            // 类型数组传入旋转矩阵
            gl.uniformMatrix4fv(mx, false, mxArr)
            gl.uniformMatrix4fv(my, false, myArr)

            draw(gl, Tx, 0.5) // 绘制第一个正方体
            // gl.clear(gl.COLOR_BUFFER_BIT) // 清除帧缓冲区中颜色缓冲区存储的颜色数据,执行后会清除第一个正方体
            draw(gl, Tx, -0.5) // 绘制第二个正方体
          }

          // 绘制立方体函数
          const draw = (gl, Tx, x) => {
            // 平移矩阵数据
            const TxArr = new Float32Array([1,0,0,0,  0,1,0,0,  0,0,1,0,  x,0,0,1])
            // 类型数组传入平移矩阵
            gl.uniformMatrix4fv(Tx, false, TxArr)
            /* 执行绘制之前,一定要开启深度测试,以免颜色混乱 */
            gl.enable(gl.DEPTH_TEST)
            // 执行绘制三角形命令
            gl.drawArrays(gl.TRIANGLES, 0, 36)
          }

          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 如有报错则在控制台显示
            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
              const info = gl.getProgramInfoLog(program)
              throw new Error('Could not compile WebGL program. \n\n' + info)
            }
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

12.WebGL纹理贴图

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>WebGL纹理贴图</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              attribute vec4 a_Position; // 顶点位置坐标
              attribute vec2 a_TexCoord; // 纹理坐标
              varying vec2 v_TexCoord; // 插值后纹理坐标
        
              void main() {
                // 顶点坐标赋值给内置变量gl_Position
                gl_Position = a_Position;
                // 纹理坐标插值计算
                v_TexCoord = a_TexCoord;
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
              // 所有float类型数据的精度是highp
              precision highp float;
              // 接收插值后的纹理坐标
              varying vec2 v_TexCoord;
              // 纹理图片像素数据
              uniform sampler2D u_Sampler;

              void main() {
                gl_FragColor = texture2D(u_Sampler, v_TexCoord); // 采集纹素,逐片元赋值像素值
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)
            /*
             * 四个顶点坐标数据data,z轴为零
             * 定义纹理贴图在WebGL坐标系中位置
             */
            const data = new Float32Array([
              -0.5, 0.5, // 左上角——v0
              -0.5, -0.5, // 左下角——v1
              0.5, 0.5, // 右上角——v2
              0.5, -0.5 // 右下角——v3
            ])
            // 获取顶点着色器的位置变量a_Position
            const a_Position = gl.getAttribLocation(program, 'a_Position')
            // 创建缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 缓冲区中的数据按照一定的规律传递给位置变量a_Position
            gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_Position)

            /*
             * 创建UV纹理坐标数据textureData
             */
            const textureData = new Float32Array([
              0, 1, // 左上角——uv0
              0, 0, // 左下角——uv1
              1, 1, // 右上角——uv2
              1, 0 // 右下角——uv3
            ])
            /*
             * 从program对象获取相关的变量
             * attribute变量声明的方法使用getAttribLocation()方法
             * uniform变量声明的方法使用getUniformLocation()方法
             */
            const a_TexCoord = gl.getAttribLocation(program, 'a_TexCoord')
            const u_Sampler = gl.getUniformLocation(program, 'u_Sampler')
            // 创建缓冲区对象
            const textureBuffer = gl.createBuffer()
            // 绑定缓冲区对象,激活textureBuffer
            gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer)
            // UV纹理坐标数据textureData数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, textureData, gl.STATIC_DRAW)
            // 缓冲区中的数据按照一定的规律传递给位置变量a_TexCoord
            gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_TexCoord)

            /*
             * 加载纹理图像像素数据
             */
            const image = new Image()
            image.src = '../../img/test/panda.jpg' // 设置图片路径,必须是分辨率为2的n次幂的图片,否则只显示黑色
            image.onload = () => {
              // 传入图片纹理数据,然后执行绘制方法drawArrays()
              const texture = gl.createTexture() // 创建纹理图像缓冲区
              // 因为纹理的坐标原点位于左下角,和我们通常的左上角坐标原点刚好相反,所以要将它按Y轴进行反转,方便我们设置坐标
              gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) // 纹理图片上下反转
              gl.activeTexture(gl.TEXTURE0) // 激活0号纹理单元TEXTURE0
              gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理缓冲区
              // 设置纹理贴图填充方式(纹理贴图像素尺寸大于顶点绘制区域像素尺寸)
              gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
              // 设置纹理贴图填充方式(纹理贴图像素尺寸小于顶点绘制区域像素尺寸)
              gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
              // 设置纹素格式,jpg格式对应gl.RGB,png格式对应gl.RGBA
              gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image)
              gl.uniform1i(u_Sampler, 0) // 纹理缓冲区单元TEXTURE0中的颜色数据传入片元着色器
              // gl.clear(gl.COLOR_BUFFER_BIT)
              // 进行绘制
              gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
            }
          }

          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

注意:贴图的图片必须是分辨率为2的n次幂的图片,否则只显示黑色。

2的N次幂是指:1 2 4 8 16 32 64 128 256 512 1024 2048……

13.彩色图转灰度图

<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
    <title>彩色图转灰度图</title>
    <style>
      html {
        box-sizing: border-box;
        scroll-behavior: smooth !important;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #fff;
        counter-reset: boxnum;
      }

      body:before {
        content: "";
        position: fixed;
        top: -10px;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 10px;
        -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
      }

      .wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        overflow: hidden;
      }

      .container {
        flex: 1;
        display: flex;
        justify-content: center;
      }
    </style>
  </head>
  <body>
    <!-- 1920*1080 -->
    <div id="wrap" class="wrapper">
      <div class="container">
        <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
    <script>
      const { createApp } = Vue

      createApp({
        setup() {
          const init = () => {
            // 通过getElementById()方法获取canvas画布
            const canvas = document.getElementById('webgl')
            // 通过方法getContext()获取WebGL上下文
            const gl = canvas.getContext('webgl')
            // 顶点着色器源码
            const vertexShaderSource = `
              attribute vec4 a_Position; // 顶点位置坐标
              attribute vec2 a_TexCoord; // 纹理坐标
              varying vec2 v_TexCoord; // 插值后纹理坐标
        
              void main() {
                // 顶点坐标赋值给内置变量gl_Position
                gl_Position = a_Position;
                // 纹理坐标插值计算
                v_TexCoord = a_TexCoord;
              }
            `
            // 片元着色器源码
            const fragShaderSource = `
              // 所有float类型数据的精度是highp
              precision highp float;
              // 接收插值后的纹理坐标
              varying vec2 v_TexCoord;
              // 纹理图片像素数据
              uniform sampler2D u_Sampler;

              void main() {
                // 采集纹素
                vec4 texture = texture2D(u_Sampler,v_TexCoord);
                // 计算RGB三个分量光能量之和,也就是亮度
                float luminance = 0.299 * texture.r + 0.587 * texture.g + 0.114 * texture.b;
                // 逐片元赋值,RGB相同均为亮度值,用黑白两色表达图片的明暗变化
                gl_FragColor = vec4(luminance, luminance, luminance, 1.0);
              }
            `
            // 初始化着色器
            const program = initShader(gl, vertexShaderSource, fragShaderSource)
            /*
             * 四个顶点坐标数据data,z轴为零
             * 定义纹理贴图在WebGL坐标系中位置
             */
            const data = new Float32Array([
              -0.5, 0.5, // 左上角——v0
              -0.5, -0.5, // 左下角——v1
              0.5, 0.5, // 右上角——v2
              0.5, -0.5 // 右下角——v3
            ])
            // 获取顶点着色器的位置变量a_Position
            const a_Position = gl.getAttribLocation(program, 'a_Position')
            // 创建缓冲区对象
            const buffer = gl.createBuffer()
            // 绑定缓冲区对象,激活buffer
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            // 顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
            // 缓冲区中的数据按照一定的规律传递给位置变量a_Position
            gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_Position)

            /*
             * 创建UV纹理坐标数据textureData
             */
            const textureData = new Float32Array([
              0, 1, // 左上角——uv0
              0, 0, // 左下角——uv1
              1, 1, // 右上角——uv2
              1, 0 // 右下角——uv3
            ])
            /*
             * 从program对象获取相关的变量
             * attribute变量声明的方法使用getAttribLocation()方法
             * uniform变量声明的方法使用getUniformLocation()方法
             */
            const a_TexCoord = gl.getAttribLocation(program, 'a_TexCoord')
            const u_Sampler = gl.getUniformLocation(program, 'u_Sampler')
            // 创建缓冲区对象
            const textureBuffer = gl.createBuffer()
            // 绑定缓冲区对象,激活textureBuffer
            gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer)
            // UV纹理坐标数据textureData数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, textureData, gl.STATIC_DRAW)
            // 缓冲区中的数据按照一定的规律传递给位置变量a_TexCoord
            gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 0, 0)
            // 允许数据传递
            gl.enableVertexAttribArray(a_TexCoord)

            /*
             * 加载纹理图像像素数据
             */
            const image = new Image()
            image.src = '../../img/test/panda1.jpg' // 设置图片路径,必须是分辨率为2的n次幂的图片,否则只显示黑色
            image.onload = () => {
              // 传入图片纹理数据,然后执行绘制方法drawArrays()
              const texture = gl.createTexture() // 创建纹理图像缓冲区
              // 因为纹理的坐标原点位于左下角,和我们通常的左上角坐标原点刚好相反,所以要将它按Y轴进行反转,方便我们设置坐标
              gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) // 纹理图片上下反转
              gl.activeTexture(gl.TEXTURE0) // 激活0号纹理单元TEXTURE0
              gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理缓冲区
              // 设置纹理贴图填充方式(纹理贴图像素尺寸大于顶点绘制区域像素尺寸)
              gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
              // 设置纹理贴图填充方式(纹理贴图像素尺寸小于顶点绘制区域像素尺寸)
              gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
              // 设置纹素格式,jpg格式对应gl.RGB,png格式对应gl.RGBA
              gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image)
              gl.uniform1i(u_Sampler, 0) // 纹理缓冲区单元TEXTURE0中的颜色数据传入片元着色器
              // gl.clear(gl.COLOR_BUFFER_BIT)
              // 进行绘制
              gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
            }
          }

          // 初始化着色器函数
          // initShader()函数可以完成着色器代码的编译,然后在GPU上执行
          const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
            // 创建顶点着色器对象
            const vertexShader = gl.createShader(gl.VERTEX_SHADER)
            // 创建片元着色器对象
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
            // 引入顶点、片元着色器源代码
            gl.shaderSource(vertexShader, vertexShaderSource)
            gl.shaderSource(fragmentShader, fragmentShaderSource)
            // 编译顶点、片元着色器
            gl.compileShader(vertexShader)
            gl.compileShader(fragmentShader)
            // 创建程序对象program
            const program = gl.createProgram()
            // 附着顶点着色器和片元着色器到program
            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            // 链接program
            gl.linkProgram(program)
            // 使用program
            gl.useProgram(program)
            // 返回程序program对象
            return program
          }
          
          Vue.onMounted(init)
          
          return { init }
        }
      }).mount('#wrap')
    </script>
  </body>
</html>

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虎踞丘上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值