Skip to content

WebGL

webgl是一种3d绘图协议,衍生于OpenGL ES2.0,可以结合htmljs在网页上渲染二维三维图像

用途

  • 数据可视化
  • 图形、游戏引擎
  • 地图
  • VR
  • 物品展示
  • 室内设计
  • 城市规划

优势

  • 可在浏览器中运行
  • 适合前端开发者

Canvas画布

使用<canvas></canvas>元素绘制二维和三维的图像

二维图像主要通过 CanvasRenderingContext2D 接口完成:canvas.getContext('2d')

三维图像主要通过 CanvasRenderingContext 接口完成 canvas.getContext('webgl'),可以使用canvas.getContext('webgl2')获取2.0标准的api

第一个webgl应用,为三维画布重置画布颜色:

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webgl</title>
</head>

<body>
  <canvas></canvas>
  <script>
    const canvas = document.querySelector('canvas')
    const gl = canvas.getContext('webgl')
    // 设置清空颜色缓存时的颜色值
    gl.clearColor(1, 0, 0, 0.5)
    // 使用预设值清空缓冲区
    gl.clear(gl.COLOR_BUFFER_BIT)
  </script>
</body>

</html>

着色器(渲染管线)

开发者自行编写的程序,用来代替固定渲染管线,处理图像的渲染。

它以字符串的形式存在在js中,被js读取后传递给webgl

  • 顶点着色器:位置
  • 片元着色器:像素

创建一个着色器项目初始化代码:

js
export default function initProgram(
  gl,
  VERTEX_SHADER_SOURCE,
  FRAGMENT_SHADER_SOURCE
) {
  // 创建着色器
  const vertexShader = gl.createShader(gl.VERTEX_SHADER);
  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);

  // 指定源码
  gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE);
  gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE);

  // 编译
  gl.compileShader(vertexShader);
  gl.compileShader(fragmentShader);

  // 创建程序对象
  const program = gl.createProgram();

  // 程序对象连接着色器
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);

  // js跟程序对象关联
  gl.linkProgram(program);

  // 使用程序对象
  gl.useProgram(program);

  return program;
}

绘制图形:

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>绘制一个点</title>
</head>

<body>
  <canvas></canvas>
  <script type="module" >
    import initProgram from './lib/program-init.js'
    const canvas = document.querySelector('canvas')
    const gl = canvas.getContext('webgl')

    // 顶点着色器源码
    const VERTEX_SHADER_SOURCE = `
      void main() {
        // 点的坐标
        gl_Position = vec4(0.0,0.0,0.0,1.0);
        // 点的大小
        gl_PointSize = 10.0;
      }
    `

    // 片元着色器源码
    const FRAGMENT_SHADER_SOURCE = `
      void main() {
        // 颜色
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
      }
    `

    // 在顶点着色器中 vec4 代表: x,y,z,w 其次坐标(x/w,y/w,z/w)
    // 在片元着色器中 vec4 代表:r,g,b,a

    const program = initProgram(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

    // 执行绘制

    // 绘制点
    gl.drawArrays(gl.POINTS, 0, 1)
    // 绘制线段
    // gl.drawArrays(gl.LINES, 0, 1)
    // 绘制三角形
    // gl.drawArrays(gl.TRIANGLES, 0, 1)
  </script>
</body>

</html>

坐标系

二维坐标系:

二维坐标系

三维坐标系:

三维坐标系视角

三维坐标系绘图区域

着色器glsl代码语法

变量声明:attribute

只在顶点着色器源码中使用,定义:

attribute vec4 aPosition;

  • attribute:存储限定符
  • vec4:类型
  • aPosition:变量名

分号不可忽略,aPosition会被赋值默认值,默认值为中心点。

通过变量修改着色器实现:

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>顶点着色器-attribute</title>
  <style>
    canvas{
      background-color: #eee;
    }
  </style>
</head>

<body>
  <canvas></canvas>
  <script type="module">
    import initProgram from './lib/program-init.js'
    const canvas = document.querySelector('canvas')
    const gl = canvas.getContext('webgl')

    const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      void main() {
        gl_Position = aPosition;
        gl_PointSize = 10.0;
      }
    `

    const FRAGMENT_SHADER_SOURCE = `
      void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
      }
    `

    const program = initProgram(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)


    // 返回变量的存储地址
    const aPosition = gl.getAttribLocation(program,'aPosition')

    // 赋值
    gl.vertexAttrib4f(aPosition,0.5,0.5,0.0,1.0);

    // vertexAttrib1f vertexAttrib2f vertexAttrib3f 传入参数数量不同

    // 赋值后需要重新绘制
    gl.drawArrays(gl.POINTS, 0, 1)
  </script>
</body>

</html>

变量声明:uniform

可以在顶点和片元着色器中使用,但是不能传递给顶点着色器的变量上

uniform vec4 uColor;

使用方式和attribute一样,但是片元着色器和顶点着色器不一样,顶点着色器设置变量时有默认精度,片元着色器没有,需要进行设置。 gl_FragColor固定接收 4 个参数值的vec4类型,在使用uniform vec2等方式定义变量时需要选择性的将参数值填充到gl_FragColorgl_FragColor = vec4(uColor.r,uColor.g,0.0,1.0)

通过变量修改着色器实现:

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>顶点着色器-attribute</title>
  <style>
    canvas{
      background-color: #eee;
    }
  </style>
</head>

<body>
  <canvas></canvas>
  <script type="module">
    import initProgram from './lib/program-init.js'
    const canvas = document.querySelector('canvas')
    const gl = canvas.getContext('webgl')

    const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      void main() {
        gl_Position = aPosition;
        gl_PointSize = 10.0;
      }
    `
      // 低精度:lowp 中精度:mediump 高精度:highp
    const FRAGMENT_SHADER_SOURCE = `
      precision mediump float;
      uniform vec4 uColor;
      void main() {
        gl_FragColor = uColor;
      }
    `

    const program = initProgram(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)


    // 返回变量的存储地址
    const uColor = gl.getUniformLocation(program,'uColor')

    // 赋值
    gl.uniform4f(uColor,-0.5,-0.5,0.0,1.0);

    // uniform1f uniform2f uniform3f 传入参数数量不同
    // 当使用 uniform1f 时,应该使用 float 而不是vec1来定义变量,使用float后变量被定义为浮点数,可以作为值直接在 gl_FragColor 中使用

    // 赋值后需要重新绘制
    gl.drawArrays(gl.POINTS, 0, 1)
  </script>
</body>

</html>

鼠标事件

获取鼠标所在位置的二维坐标:

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>得到鼠标所在位置</title>
  <style>
    canvas {
      background-color: #eee;
    }
  </style>
</head>

<body>
  <canvas></canvas>
  <script type="module">
    import initProgram from './lib/program-init.js'
    const canvas = document.querySelector('canvas')
    const gl = canvas.getContext('webgl')

    const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      void main() {
        gl_Position = aPosition;
        gl_PointSize = 10.0;
      }
    `

    const FRAGMENT_SHADER_SOURCE = `
      void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
      }
    `

    const program = initProgram(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

    // 返回变量的存储地址
    const aPosition = gl.getAttribLocation(program, 'aPosition')

    const rect = canvas.getBoundingClientRect();
    const width = canvas.offsetWidth
    const height = canvas.offsetHeight
    const halfWidth = width / 2
    const halfHeight = height / 2

    canvas.addEventListener('click', (event) => {
      const clientX = event.clientX
      const clientY = event.clientY

      var offsetX = clientX - rect.left;
      var offsetY = clientY - rect.top;

      const x = (offsetX - halfWidth) / halfWidth
      const y = (halfHeight - offsetY) / halfHeight

      // 赋值
      gl.vertexAttrib2f(aPosition, x, y);

      // 赋值后需要重新绘制
      gl.drawArrays(gl.POINTS, 0, 1)
    })




  </script>
</body>

</html>

缓冲区对象

处理多个顶点绘制带来的渲染时机问题,它是一块内存区域,可以一次性将大量顶点数据填充进去缓存起来,供着色器使用

创建和绑定缓冲区对象:

  1. 创建缓冲区对象:
js
const buffer = gl.createBuffer();
  1. 绑定缓存区对象
js
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  • gl.ARRAY_BUFFER表示缓存顶点数据
  • gl.ELEMENT_ARRAY_BUFFER表示缓存顶点的索引值
  1. 使用类型化数组数据结构创建顶点数据
js
const points = new Float32Array([-0.5, -0.5, 0.5, -0.5, 0.0, 0.5]);
  1. 存入数据
js
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

最后一个参数表示数据的使用方式:

  • gl.STATIC_DRAW:写入一次,多次绘制
  • gl.STREAM_DRAW:写入一次,若干次绘制
  • gl.DYNAMIC_DRAW:写入多次,多次绘制
  1. 修改数据
js
const aPosition  = gl.getAttribLocation(program,'aPosition)
gl.vertexAttribPointer(aPosition,2,gl.FLOAT,false,0,0)

点击查看 vertexAttribPointer 参数说明

  1. 激活顶点着色器变量
js
gl.enableVertexAttribArray(aPosition);
  1. 渲染
js
gl.drawArrays(gl.POINTS, 0, 3);

点击查看 drawArrays 参数说明


alt 缓冲区对象

类型化数组:

webgl中,需要处理大量的同类型数据,使用类型化数组可以让程序预知数组元素的类型,提高执行性能

  • Int8Array:8 位整型
  • UInt8Array:8 位无符号整型
  • Int16Array:16 位整型
  • Unit16Array:16 位无符号整型
  • Int32Array:32 位整型
  • UInt32Array:32 位无符号整型
  • Float32Array:单精度 32 位浮点型
  • Float64Array:双精度 64 位浮点型

多缓冲区