WebGl之图形绘制和变换

179 阅读5分钟

图形绘制和变换

上篇文章:webgl初学入门webgl介绍 什么是webgl WebGL是一种3D绘图协议,衍生于OpenGLES2.0,可以结合 - 掘金

图形平移-着色器

我们声明一个变量然后不断更改顶点着色器的坐标即可实现图形的平移

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  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 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 aTranslate = gl.getAttribLocation(program, 'aTranslate');

  const points = new Float32Array([
    -0.5, -0.5,
     0.5, -0.5,
     0.0,  0.5,
  ])

  const buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(aPosition)

  let x = -1;
  function animation() {
    x += 0.01;
    if (x > 1) {
      x = -1;
    }
    gl.vertexAttrib1f(aTranslate, x);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(animation);
  }

  animation()
</script>

图形平移-平移矩阵

先来看看矩阵的概念

  1. 矩阵就是纵横排列的数据表格(m行n列)
  2. 作用是把一个点转换到另一个点

image.png

我们用xyz坐标展示图形平移

image.png

得到这样的平移矩阵和公式

image.png

  • ax + by + cZ + d = x'
    • 只有当a=1,b=c=0,d=x1的时候,等式左右两边成立
  • ex + fy+ gz + h = y'
    • 只有当f=1,e=g=0,h=y1 的时候,等式左右两边成立
  • ix + jy + kz + |= z'
    • 只有当k=1,i=j=0,l= z1 的时候,等式左右两边成立
  • mx + ny + OZ + p = W
    • 只有当m=n=0=0,p=1的时候,等式左右两边成立

将计算的参数带入矩阵中

image.png

写一个平移矩阵的辅助函数

// 平移矩阵
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,
  ])
}

然后将原代码改成矩阵的写法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    uniform mat4 mat;
    void main() {
      gl_Position = mat * aPosition;
      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 mat = gl.getUniformLocation(program, 'mat');

  const points = new Float32Array([
    -0.5, -0.5,
     0.5, -0.5,
     0.0,  0.5,
  ])

  const buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(aPosition)

  let x = -1;
  function animation() {
    x += 0.01;
    if (x > 1) {
      x = -1;
    }

    const matrix = getTranslateMatrix(x, x);
    // gl.vertexAttrib1f(aTranslate, x);
    gl.uniformMatrix4fv(mat, false, matrix);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(animation);
  }

  animation()
</script>

WebGLRenderingContext.uniformMatrix[234]fv() - Web API | MDN

gl.uniformMatrix4fv(location, transpose,array)

  • location:指定 uniform 变量的存储位置
  • transpose: 在 webgl 中恒为false
  • array:矩阵数组

图形缩放-着色器

和图形平移-着色器的代码类似,只需要改个变量和顶点着色器源码就好

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

图形缩放-缩放矩阵

xyz坐标展示缩放

image.png

得到矩阵和公式

image.png

  • aX + by + Cz + d = x'
    • 只有当a=Tx,b=c=w=0的时候,等式左右两边成立
  • ex + fy + gz + h = y'
    • 只有当f=Ty,e=g=h=0的时候,等式左右两边成立
  • ix + jy + kz + | = z'
    • 只有当k=Tz,i=j=l=0的时候,等式左右两边成立
  • mX + ny + 0Z + p = W'
    • 只有当m=n=o=0,p=1的时候,等式左右两边成立

带入矩阵中

image.png

这个矩阵不论是行主序还是列主序,都是相等的,这种沿主对角线对称的矩阵,称为对称矩阵。

缩放矩阵辅助函数

// 缩放矩阵
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,
  ])
}
//代码与前面的平移矩阵一致,只需改一下matrix
const matrix = getScaleMatrix(x, x);

图形旋转-旋转矩阵

image.png

顶点A

  • x=R * cos(a)
  • y=R * sin(a)
  • z=0

顶点A'

  • x'=R * cos(a+β)
  • =R * (cos(a) * cos(β) - sin(a) * sin(β))
  • =R* cos(a) * cos(β) - R * sin(a) * sin(β)
  • y'=R* sin(a + β)
  • =R * (sin(a) * cos(β) + cos(a) * sin(β))
  • =R* sin(a) * cos(β) + R * cos(a) * sin(β)
  • z'= z

将顶点A的公式带入顶点A'

  • x'=x * cos(β) - y * sin(β)
  • y'=y * cos(β) + x * sin(β)
  • z'=z

矩阵

image.png

  • ax + by + cz + W=x * cos(β) - y * sin(B)
    • 只有当a = cos(B),b = -sin(B),c=w=0的时候,等式左右两边成立
  • ex + fy + gz + h=y * cos(β) + x * sin(β)
    • 只有当e = sin(β),f=cos(B),g=h=0的时候,等式左右两边成立
  • ix + jy + kz + | = z
    • 只有当k=1,i=j=k=0 的时候,等式左右两边成立
  • mX + ny + 0Z + p = 1
    • 只有当m=n=o=0,p=1的时候,等式左右两边成立

带入矩阵中得到

image.png

// 绕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,
  ])
}
//代码与前面的平移矩阵一致,只需改一下matrix
const matrix = getScaleMatrix(x, x);

图形复合变换-矩阵组合

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    uniform mat4 translateMatrix;
    uniform mat4 scaleMatrix;
    uniform mat4 rotationMatrix;
    void main() {
      gl_Position = translateMatrix * scaleMatrix * rotationMatrix * 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 translateMatrix = gl.getUniformLocation(program, 'translateMatrix');
  const scaleMatrix = gl.getUniformLocation(program, 'scaleMatrix');
  const rotationMatrix = gl.getUniformLocation(program, 'rotationMatrix');

  const points = new Float32Array([
    -0.5, -0.5,
     0.5, -0.5,
     0.0,  0.5,
  ])

  const buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

  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);
    // gl.vertexAttrib1f(aTranslate, x);
    gl.uniformMatrix4fv(translateMatrix, false, translate);
    gl.uniformMatrix4fv(scaleMatrix, false, scale);
    gl.uniformMatrix4fv(rotationMatrix, false, rotate);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(animation);
  }

  animation()
</script>