Webgl基础笔记(一)

249 阅读3分钟

1. 绘制一个点

(1) 流程介绍

(2) 初始化着色器

/*
 * lib/index.js
 */
function initShader(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)

  gl.linkProgram(program)
  gl.useProgram(program)

  return program
}

// 平移矩阵
function getTranslateMatrix(x = 0, y = 0, z = 0) {
  return new Float32Array([
    1.0, 0.0, 0.0, 0.0,
    0.0, 1.0, 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0,
      x,   y,   z,   1,
  ])
}

 // 缩放矩阵
 function getScaleMatrix(x = 1, y = 1, z = 1) {
  return new Float32Array([
      x, 0.0, 0.0, 0.0,
    0.0,   y, 0.0, 0.0,
    0.0, 0.0,   z, 0.0,
    0.0, 0.0, 0.0,   1,
  ])
}

// 绕z轴旋转的矩阵
function getRotateMatrix(deg) {
  return new Float32Array([
    Math.cos(deg),  Math.sin(deg), 0.0, 0.0,
    -Math.sin(deg), Math.cos(deg), 0.0, 0.0,
    0.0,            0.0,           1.0, 0.0,
    0.0,            0.0,           0.0,   1,
  ])
}

// 矩阵复合函数
function mixMatrix(A, B) {
  const result = new Float32Array(16)

  for (let i = 0; i < 4; i++) {
    result[i] = A[i] * B[0] + A[i + 4] * B[1] + A[i + 8] * B[2] + A[i + 12] * B[3]
    result[i + 4] = A[i] * B[4] + A[i + 4] * B[5] + A[i + 8] * B[6] + A[i + 12] * B[7]
    result[i + 8] = A[i] * B[8] + A[i + 4] * B[9] + A[i + 8] * B[10] + A[i + 12] * B[11]
    result[i + 12] = A[i] * B[12] + A[i + 4] * B[13] + A[i + 8] * B[14] + A[i + 12] * B[15]
  }

  return result
}

(3) 绘制顶点

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #canvas {
      background-color: #ccc;
    }
  </style>
  <script src="./lib/index.js"></script>
</head>
<body>
  <canvas id="canvas" width="400" height="400">浏览器不支持canvas</canvas>

  <script>
    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);
      }
    `; // 片元着色器

    initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

    // 执行绘制
    // 要绘制的图形是什么?从哪个开始?使用几个顶点
    gl.drawArrays(gl.POINTS, 0, 1)
  </script>
</body>
</html>

2. attribute变量

(1) 流程介绍

(2) 代码实现

...

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
	atrribute vec4 aPosition;
  void main() {
    gl_Position = aPosition; // 默认值vec4(0.0, 0.0, 0.0, 1.0)
    gl_PointSize = 10.0;
  }
`; // 顶点着色器

...

const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

const aPosition = gl.getAttribLocation(program, 'aPosition');

gl.vertexAttrib4f(aPosition, 0.5, 0.5, 0.0, 1.0);

...

3. 通过鼠标控制绘制

(1) 流程介绍

(2) 书写签名效果

...

const aPosition = gl.getAttribLocation(program, 'aPosition')

const points = []
let isDraw = false

canvas.onmousedown = function () {
  isDraw = true
}

canvas.onmouseup = function () {
  isDraw = false
}

canvas.onmousemove = function (ev) {
  if (!isDraw) {
    return
  }
  const x = ev.clientX
  const y = ev.clientY

  const domPosition = ev.target.getBoundingClientRect()

  const domx = x - domPosition.left
  const domy = y - domPosition.top

  const halfWidth = canvas.offsetWidth / 2;
  const halfHeight = canvas.offsetHeight / 2;

  const clickX = (domx - halfWidth) / halfWidth
  const clickY = (halfHeight - y) / halfHeight

  points.push({
    clickX, clickY
  })

  for (let i = 0; i < points.length; i++) {
    const point = points[i];
    gl.vertexAttrib2f(aPosition, point.clickX, point.clickY)

    gl.drawArrays(gl.POINTS, 0, 1)
  }
}

4. 绘制不同颜色的点

(1) 设置精度

precision mediump float

  • hightp 高精度
  • mediump 中精度
  • lowp 低精度

(2) 流程介绍

(3) 使用uniform变量-绘制不同颜色的点

const canvas = document.querySelector('#canvas')

const gl = canvas.getContext("webgl")

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      void main() {
        gl_Position = aPosition; // 默认值vec4(0.0, 0.0, 0.0, 1.0)
        gl_PointSize = 10.0;
      }
    `; // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
      precision mediump float;
      uniform vec2 uColor;
      void main() {
        gl_FragColor = vec4(uColor.r, uColor.g, 0.0, 1.0);
      }
    `; // 片元着色器

const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

const aPosition = gl.getAttribLocation(program, 'aPosition')

const uColor = gl.getUniformLocation(program, 'uColor')

const points = []

canvas.onclick = function (ev) {
  const x = ev.clientX
  const y = ev.clientY

  const domPosition = ev.target.getBoundingClientRect()

  const domx = x - domPosition.left
  const domy = y - domPosition.top

  const halfWidth = canvas.offsetWidth / 2;
  const halfHeight = canvas.offsetHeight / 2;

  const clickX = (domx - halfWidth) / halfWidth
  const clickY = (halfHeight - y) / halfHeight

  points.push({
    clickX, clickY
  })

  for (let i = 0; i < points.length; i++) {
    const point = points[i];
    gl.vertexAttrib2f(aPosition, point.clickX, point.clickY)
    gl.uniform2f(uColor, point.clickX, point.click)

    gl.drawArrays(gl.POINTS, 0, 1)
  }
}

5. 使用缓冲对象绘制多个点

(1) 流程介绍

(2) 绘制多个点

const canvas = document.querySelector('#canvas')

const gl = canvas.getContext('webgl')

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      void main() {
        gl_Position = aPosition; // 默认值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);
      }
    ` // 片元着色器

const program = initShader(
  gl,
  VERTEX_SHADER_SOURCE,
  FRAGMENT_SHADER_SOURCE
)

const aPosition = gl.getAttribLocation(program, 'aPosition')

// 创建三个点的数组
const points = new Float32Array([-0.5, -0.5, 0.5, -0.5, 0.0, 0.5])

// 创建缓冲区对象
const buffer = gl.createBuffer()
/*
       * 绑定缓冲区对象 gl.bindBuffer(taget, buffer)
       * @params: taget gl.ARRAY_BUFFER 表示缓冲区存储的是顶点的数据,gl.ELEMENT_ARRAY_BUFFER 表示缓冲区存储的是顶点的索引值
       * @params: buffer 缓冲区对象
       */
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
/*
       * 将数据写入缓冲区对象 gl.bufferData(taget, data, type)
       * @params: taget gl.ARRAY_BUFFER 表示缓冲区存储的是顶点的数据,gl.ELEMENT_ARRAY_BUFFER 表示缓冲区存储的是顶点的索引值
       * @params: data 写入缓冲区的顶点数据
       * @params: type 如何使用缓冲区对象中的数据:gl.STATIC_DRAW 写入一次 多次绘制,gl.STREAM_DRAW 写入一次 绘制若干次,gl.DYNAMIC_DRAW 写入多次 绘制多次,
       */
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)

/*
       * 将数据写入缓冲区对象 gl.vertexAttribPointer(location, size, type, normalized, stride, offset)
       * @params: location attribute变量的存储地址
       * @params: size 每个顶点使用数据的个数
       * @params: type 指定数据格式
       * @params: normalized 表示是否将数据归一化到[0, 1][-1, 1]这个区间
       * @params: stride 两个相邻顶点之间的字节数
       * @params: offset 数据偏移量
       */
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0)

/*
       * 启用 attribute 变量
       * @params: attribute 变量的存储地址
       */
gl.enableVertexAttribArray(aPosition)

gl.drawArrays(gl.POINTS, 0, 3)

6. 多缓冲区和数据偏移

(1) 流程介绍

(2) 控制多个点的多个属性

const canvas = document.querySelector('#canvas')

const gl = canvas.getContext('webgl')

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      attribute float aPointSize;
      void main() {
        gl_Position = aPosition; // 默认值vec4(0.0, 0.0, 0.0, 1.0)
        gl_PointSize = aPointSize;
      }
    ` // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
      void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    ` // 片元着色器

const program = initShader(
  gl,
  VERTEX_SHADER_SOURCE,
  FRAGMENT_SHADER_SOURCE
)

const aPosition = gl.getAttribLocation(program, 'aPosition')
const aPointSize = gl.getAttribLocation(program, 'aPointSize')

// 创建三个点的数组
const points = new Float32Array([
  -0.5, -0.5, 10.0,
  0.5, -0.5, 20.0,
  0.0, 0.5, 30.0,
])

// 创建缓冲区对象
const buffer = gl.createBuffer()

// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)

// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT

// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 3, 0)
gl.vertexAttribPointer(aPointSize, 1, gl.FLOAT, false, BYTES * 3, BYTES * 2)

// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)
gl.enableVertexAttribArray(aPointSize)

gl.drawArrays(gl.POINTS, 0, 3)

7. 多种图形绘制

8. 着色器图形平移、缩放、旋转

// 平移
...

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      attribute float aTranslate;
      void main() {
        gl_Position = vec4(aPosition.x + aTranslate, aPosition.y, aPosition.z, 1.0);
        gl_PointSize = 10.0;
      }
    `; // 顶点着色器

...

const aPosition = gl.getAttribLocation(program, 'aPosition');
const aTranslate = gl.getAttribLocation(program, 'aTranslate');

// 创建三个点的数组
const points = new Float32Array([
  -0.5, -0.5,
  0.5, -0.5,
  0, 0.5,
])

// 创建缓冲区对象
const buffer = gl.createBuffer()

// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)

// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT

// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0)

// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)

let x = -1
setInterval(() => {
  x += 0.01
  if (x > 1) {
    x = -1
  }
  gl.vertexAttrib1f(aTranslate, x)
  gl.drawArrays(gl.TRIANGLES, 0, 3)
}, 60);
// 缩放
...

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      attribute float aScale;
      void main() {
        gl_Position = vec4(aPosition.x * aScale, aPosition.y, aPosition.z, 1.0);
        gl_PointSize = 10.0;
      }
    `; // 顶点着色器

...

const aPosition = gl.getAttribLocation(program, 'aPosition')
const aScale = gl.getAttribLocation(program, 'aScale')

// 创建三个点的数组
const points = new Float32Array([
  -0.5, -0.5,
  0.5, -0.5,
  0, 0.5,
])

// 创建缓冲区对象
const buffer = gl.createBuffer()

// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)

// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT

// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0)

// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)

let x = 1
setInterval(() => {
  x += 0.1
  if (x > 2) {
    x = 1
  }
  gl.vertexAttrib1f(aScale, x)
  gl.drawArrays(gl.TRIANGLES, 0, 3)
}, 60);
// 旋转
...

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      attribute float deg;
      void main() {
        gl_Position.x = aPosition.x * cos(deg) - aPosition.y * sin(deg);
        gl_Position.y = aPosition.x * sin(deg) + aPosition.y * cos(deg);
        gl_Position.z = aPosition.z;
        gl_Position.w = aPosition.w;
      }
    `; // 顶点着色器

...

const aPosition = gl.getAttribLocation(program, 'aPosition')
const deg = gl.getAttribLocation(program, 'deg')

// 创建三个点的数组
const points = new Float32Array([
  -0.5, -0.5,
  0.5, -0.5,
  0, 0.5,
  0.5, 0.5,
])

// 创建缓冲区对象
const buffer = gl.createBuffer()

// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)

// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT

// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0)

// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)

let x = 1
function animate() {
  x += -0.03
  gl.vertexAttrib1f(deg, x)
  gl.drawArrays(gl.TRIANGLES, 0, 3)

  requestAnimationFrame(animate)
}

animate()

9. 矩阵图形平移、缩放、旋转

(1) 平移矩阵



const canvas = document.querySelector('#canvas')

const gl = canvas.getContext('webgl')

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      uniform mat4 mat;
      void main() {
        gl_Position = mat * aPosition;
      }
    ` // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
      void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    ` // 片元着色器

const program = initShader(
  gl,
  VERTEX_SHADER_SOURCE,
  FRAGMENT_SHADER_SOURCE
)

const aPosition = gl.getAttribLocation(program, 'aPosition')
const mat = gl.getUniformLocation(program, 'mat')

// 获取矩阵
function getTranslateMatrix(x = 0, y = 0, z = 0) {
  return new Float32Array([
    1.0, 0.0, 0.0, 0.0,
    0.0, 1.0, 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0,
    x,   y,   z,   1,
  ])
}

// 创建三个点的数组
const points = new Float32Array([
  -0.5, -0.5,
  0.5, -0.5,
  0, 0.5,
])

// 创建缓冲区对象
const buffer = gl.createBuffer()

// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)

// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT

// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0)

// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)

let x = -1
function animation() {
  x += 0.01
  if (x > 1) {
    x = -1
  }
  const matrix = getTranslateMatrix(x, x)
  /*
         * 修改矩阵变量 gl.uniformMatrix4fv(location, transpose, array)
         * @params: transpose 在webgl中恒为false
         * @params: array 矩阵数组 
         */   
  gl.uniformMatrix4fv(mat, false, matrix)
  gl.drawArrays(gl.TRIANGLES, 0, 3)

  requestAnimationFrame(animation)
}

animation()

(2) 缩放矩阵



...

// 获取矩阵
function getScaleMatrix(x = 1, y = 1, z = 1) {
  return new Float32Array([
    x, 0.0, 0.0, 0.0,
    0.0,   y, 0.0, 0.0,
    0.0, 0.0,   z, 0.0,
    0.0, 0.0, 0.0,   1,
  ])
}

...

let x = 0.1
function animation() {
  x += 0.01
  if (x > 1.5) {
    x = 0.1
  }
  const matrix = getScaleMatrix(x, x)
  /*
         * 修改矩阵变量 gl.uniformMatrix4fv(location, transpose, array)
         * @params: transpose 在webgl中恒为false
         * @params: array 矩阵数组 
         */   
  gl.uniformMatrix4fv(mat, false, matrix)
  gl.drawArrays(gl.TRIANGLES, 0, 3)

  requestAnimationFrame(animation)
}

animation()

(3) 旋转矩阵



...

// 获取矩阵
function getRotateMatrix(deg) {
  return new Float32Array([
    Math.cos(deg),  Math.sin(deg), 0.0, 0.0,
    -Math.sin(deg), Math.cos(deg), 0.0, 0.0,
    0.0,            0.0,           1.0, 0.0,
    0.0,            0.0,           0.0,   1,
  ])
}

...

let x = 0
function animation() {
  x += 0.01
  const matrix = getRotateMatrix(x)
  gl.uniformMatrix4fv(mat, false, matrix)
  gl.drawArrays(gl.TRIANGLES, 0, 3)

  requestAnimationFrame(animation)
}

animation()

10. 图形复合变换-组合矩阵


const canvas = document.querySelector('#canvas')

const gl = canvas.getContext('webgl')

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
      attribute vec4 aPosition;
      uniform mat4 mat;
      void main() {
        gl_Position = mat * aPosition;
      }
    ` // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
      void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    ` // 片元着色器

const program = initShader(
  gl,
  VERTEX_SHADER_SOURCE,
  FRAGMENT_SHADER_SOURCE
)

const aPosition = gl.getAttribLocation(program, 'aPosition')
const mat = gl.getUniformLocation(program, 'mat')

// 创建三个点的数组
const points = new Float32Array([
  -0.5, -0.5,
  0.5, -0.5,
  0, 0.5,
])

// 创建缓冲区对象
const buffer = gl.createBuffer()

// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)

// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT

// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0)

// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)

let deg = 0
let translateX = -1
let scaleX = 0.1
function animation() {
  deg += 0.01
  translateX += 0.01
  scaleX += 0.01

  if (translateX > 1) {
    translateX = -1
  }
  if (scaleX > 1.5) {
    scaleX = 0.1
  }
  const translate = getTranslateMatrix(translateX)
  const scale = getScaleMatrix(scaleX)
  const rotate = getRotateMatrix(deg)

  const matrix = mixMatrix(mixMatrix(translate, scale), rotate)

  gl.uniformMatrix4fv(mat, false, matrix)
  gl.drawArrays(gl.TRIANGLES, 0, 3)

  requestAnimationFrame(animation)
}

animation()