作为前端的你需要学习WebGL啦 (保姆级教程)

9,868 阅读12分钟

本文正在参加「金石计划」

以前通过echarts写过数据大屏。欢迎star~~~

数据可视化

数据大屏

一些数据可视化的类库和技术

image.png

Skia

Skia是Chrome和Android的底层2D绘图引擎,具体可参考百度百科,Skia采用C++编程,由于它位于浏览器的更底层,所以我们平常接触较少。

WebGl

WebGL(Web Graphics Library)是一种3D绘图协议,WebGL可以为HTML5 Canvas 提供硬件3D加速 渲染,这样Web开发人员就可以借助系统显卡来在浏览器里更流畅地展示3D场景和模型了,还能创建复杂的导航和数据视觉化。

three.js

对WebGL的封装,基于JavaScript,可直接运行 GPU 驱动游戏与图形驱动应用于浏览器。其库提供大量特性与API以绘制 3D 场景于浏览器。

zrender

zrender是二维绘图引擎,它提供Canvas、SVG、VML等多种渲染方式。ZRender也是ECharts的渲染器。

d3

D3(Data-Driven Documents)是一个Javascript图形库,基于Canvas、Svg和HTML。d3是数据驱动的矢量图绘制库。

矢量图是由一个个点链接在一起组成的,是根据几何特性来绘制的图像。请参考这里

Canvas API 主要聚焦于 2D 图形。他不像svg绘制成一个个dom,他是通过<canvas>画布来完成绘制的。

Highcharts

Highchartst是一个用纯JavaScript编写的一个图表库,能够很简单便捷的在web网站或是web应用程序添加有交互性的图表,并且免费提供给个人学习、个人网站和非商业用途使用。Highcharts系列包含Highcharts JS,Highstock JS,Highmaps JS 共三款软件,均为纯JavaScript 编写的HTML5图表库。

  • Highcharts Highcharts是一个用纯JavaScript编写的一个图表库,能够很简单便捷的在Web 网站或是Web 应用程序添加有交互性的图表,Highcharts支持的图表类型有直线图、曲线图、区域图、柱状图、饼状图、散状点图、仪表图、气泡图、瀑布流图等多达20种图表,其中很多图表可以集成在同一个图形中形成混合图。

  • Highstock Highstock是用纯JavaScript编写的股票图表控件,可以开发股票走势或大数据量的时间轴图表。它包含多个高级导航组件:预设置数据时间范围,日期选择器、滚动条、平移、缩放功能。

  • Highmaps Highmaps是一款基于HTML5的优秀地图组件。Highmaps 继承了Highcharts 简单易用的特性,利用它 可以方便快捷的创建用于展现销售、选举结果等其他与地理位置关系密切的交互性地图图表。

AntV

AntV是蚂蚁金服全新一代数据可视化解决方案,致力于提供一套简单方便、专业可靠、无限可能的数据可视化最佳实践。

AntV 包括以下解决方案:

  • G2:可视化引擎
  • G2Plot:图表库
  • G6:图可视化引擎
  • Graphin:基于G6的图分析组件
  • F2:移动可视化方案
  • ChartCube:AntV 图表在线制作
  • L7:地理空间数据可视化。

WebGl

webgl是一种3D绘图协议,衍生于OpenGL ES2.0,可以结合Html5和 JavaScript 在网页上绘制和渲染二/三维图形。

他和canvas api一样都需要canvas标签作为渲染容器。

图形的绘制主要通过WebGLRenderingContextWebGL2RenderingContext接口完成。

image.png

image.png GLSL ES是以字符串的形式存在在js中的。

webgl可以做啥

webgl开源类库

1. Three.js: JavaScript 3D WebGL库

2.Babylon.js:Web3D图形引擎

3.KickJS: Web的开源图形和游戏引擎

4.ClayGL:构建可扩展的Web3D应用程序

5.PlayCanvas:网络游戏和3D图形引擎

6.WebGLStudio.js和Litescene.js:开源Web 3D图形编辑器和创建器

7.Luma:Uber的3D WebGL可视化库

8.A—Frame是用于构建VR(虚拟现实)体验的Web框架

着色器

着色器就是让开发者自己去编写一段程序,用来代替固定渲染管线,来处理图像的渲染。

  • 顶点着色器

用来描述顶点的特性,通过计算获取位置信息,顶点是指二维三维空间中的一个点,可以理解为一个个坐标。

 // 着色器
  // 创建着色器源码 
  //  gl_Position vec4(0.0,0.0,0.0,1.0)  x, y, z, w齐次坐标 (x/w, y/w, z/w)
  const VERTEX_SHADER_SOURCE=`
    // 必须要存在 main 函数
    void main() {
      // 要绘制的点的坐标
      
      gl_Position = vec4(1.0,0.0,0.0,1.0); // 超出canvas画布将会被剪切。
      // 点的大小
      gl_PointSize = 30.0;
    }
  `; // 顶点着色器
 
  • 片元着色器

进行逐片元处理程序,通过计算获取颜色信息,片元可以理解为一个个像素。

  // gl_FragColor vec4(1.0,0.0,0.0,1.0) r, g, b, a
  const FRAGMENT_SHADER_SOURCE=`
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器

坐标系

  • canvas绘制的坐标系是canvas元素的左上角

image.png

  • webgl绘制原点是中心位置。因为它需要处理三维图形。 image.png

webgl绘制一个点

image.png

  • 创建webgl上下文对象const gl=ctx.getContext('webgl')
  • 创建着色器源码,就是一个字符串,但是必须按规定编写,每条语句都需要加分号。
  • vec4对象创建一个顶点坐标。 gl_Position vec4(0.0,0.0,0.0,1.0) x, y, z, w齐次坐标 (x/w, y/w, z/w)
  • 创建片元着色器源码,gl_FragColor vec4(1.0,0.0,0.0,1.0) r, g, b, a
  • 创建着色器,并绑定一个程序对象。
  • gl.drawArrays(gl.POINTS,0,1)绘制图形。【要绘制的图形是什么, 从哪个开始,使用几个顶点】。
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;
}
  const ctx=document.getElementById('canvas')
  const gl=ctx.getContext('webgl')

  // 着色器
  // 创建着色器源码
  const VERTEX_SHADER_SOURCE=`
    // 必须要存在 main 函数
    void main() {
      // 要绘制的点的坐标
      gl_Position = vec4(1.0,0.0,0.0,1.0); // 超出canvas画布将会被剪切。
      // 点的大小
      gl_PointSize = 30.0;
    }
  `; // 顶点着色器

  //  gl_Position vec4(0.0,0.0,0.0,1.0)  x, y, z, w齐次坐标 (x/w, y/w, z/w)

  // gl_FragColor vec4(1.0,0.0,0.0,1.0) r, g, b, a
  const FRAGMENT_SHADER_SOURCE=`
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器


  // 创建着色器
  // 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)

  const program=initShader(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); // 3个点

  // 3个顶点
  // 0.0, 0.0, 0.0
  // 0.2, 0.0, 0.0
  // 0.4, 0.0, 0.0
  gl.drawArrays(gl.POINTS,0,1);
  gl.drawArrays(gl.LINES,1,2);

attribute 定义属性变量

作用: 动态传入顶点数据。

image.png 注意:attribute变量只能在顶点着色器中使用,不能在片元着色器中使用。

  • 变量声明 image.png
  • 顶点属性vec4的初始值是0.0, 0.0, 0.0, 1.0
  • 获取属性变量,const aPosition = gl.getAttribLocation(program, 'aPosition');
  • 设置属性变量值。gl.vertexAttrib1f(aPosition, x)vertexAttrib[1|2|3|4]f以设置vec4对象的值,其他的值可以设置为默认值
  const ctx = document.getElementById('canvas')
  const gl = ctx.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 = 30.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');

  // 可以省略默认值
  // gl.vertexAttrib4f(aPosition, 0.5,0.5,0.0,1.0)
  // gl.vertexAttrib3f(aPosition, 0.5,0.5,0.0)
  // gl.vertexAttrib2f(aPosition, 0.5,0.5)


  let x = 0;
  setInterval(() => {
    x += 0.1;
    if (x > 1.0) {
      x = 0;
    }
    gl.vertexAttrib1f(aPosition, x)

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

uniform 定义属性变量

image.png

  • 在片元着色器中使用uniform必须设置精度片元着色器中未设置精度,顶点着色器中默认设置了高精度。
 precision mediump float; // 必须设置精度 
 
 // 还有高精度, 低精度
 ⾼精度:highp, 低精度:lowp
  • 声明变量uniform vec2 uColor;这里的变量类型和下面赋值的时候是对应的。
  • gl.uniform2f(uColor,points[i].clickX,points[i].clickY), gl.vertexAttrib2f(aPosition,points[i].clickX,points[i].clickY)
  • 注意我们再设置坐标的范围时,必须是[-1, 1]之间的,所以在计算鼠标的移动位置时,需要做缩放处理。

注意着色器变量只能接收vce4类型变量。所以对于片元着色器我们可以设置片元着色器为其他类型(float, vce2, vce3)但是我们可以这样动态设置vce4的值。

 gl_FragColor = vec4(uColor.r, uColor.g, 0.0,1.0); // vec4

如果使用uniform1f()赋值的话,我们只能通过float去定义变量类型。并且vec4中直接获取变量即可。

uniform float uColor; // 这里定义的vec和下面的赋值需要对应。
void main() {
  gl_FragColor = vec4(uColor, 0.0, 0.0,1.0); // vec4
}
  const ctx=document.getElementById('canvas')

  const gl=ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE=`
    uniform vec4 uPosition; // 虽然可以用在定点着色器中,但是他不能传递顶点数据。这个是对所有的顶点都生效的。但是对于顶点来说每个坐标都是不一样的。
    // 只传递顶点数据
    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; // 这里定义的vec和下面的赋值需要对应。
    void main() {
      gl_FragColor = vec4(uColor.r, uColor.g, 0.0,1.0); // vec4
    }
  `; // 片元着色器

  const program=initShader(gl,VERTEX_SHADER_SOURCE,FRAGMENT_SHADER_SOURCE)

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

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

  const points=[]
  ctx.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;

    /*
    0 200 400

    -1 0 1

    -200 0 200

    -1 0 1

    需要先 -200 (当前画布的宽度) 然后再 除以 200

    1 0 -1

    0 200 400

    200 0 -200 / 200

    需要先让 200 减这个数,然后再 / 200

    * */
    const halfWidth=ctx.offsetWidth/2
    const halfHeight=ctx.offsetHeight/2

    // [-1, 1]
    const clickX=(domx-halfWidth)/halfWidth
    // [1, -1]
    const clickY=(halfHeight-domy)/halfHeight

    points.push({
      clickX,clickY
    })

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

      gl.uniform2f(uColor,points[i].clickX,points[i].clickY)
      gl.drawArrays(gl.POINTS,0,1);
    }
  }

绘制.gif

图形移动

通过着色器让图形移动

我们可以定义一个attribute属性,让顶点各个轴的上值加上这个属性对应的值就行。间接去修改顶点属性。


 // 创建着色器源码
  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;
    }
  `; // 顶点着色器
  
  // 不断改变aTranslate的值即可。
  let x = -1;
  setInterval(() => {
    x += 0.01;
    if (x > 1) {
      x = -1;
    }
    gl.vertexAttrib1f(aTranslate, x);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
  }, 60)

完整代码


  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;
  setInterval(() => {
    x += 0.01;
    if (x > 1) {
      x = -1;
    }
    gl.vertexAttrib1f(aTranslate, x);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
  }, 60)

通过平移矩阵让图形移动

我们可以定义一个uniform属性,让顶点属性乘以该uniform属性即可。这里定义uniform的目的是让其作用于所有顶点。

定义一个平移矩阵函数

    // 平移矩阵
    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 VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    uniform mat4 mat; // 平移时需要平移三角形的所有顶点,所以使用uniform属性
    void main() {
      gl_Position = mat * aPosition;
      gl_PointSize = 10.0;
    }
  `; // 顶点着色器
  
  let x = -1;
  function animation() {
    x += 0.01;
    if (x > 1) {
      x = -1;
    }
    
    // 获取平移矩阵数据
    const matrix = getTranslateMatrix(x, x);
    // 赋值uniform属性
    gl.uniformMatrix4fv(mat, false, matrix);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(animation);
  }

  animation()

完整代码


  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    uniform mat4 mat; // 平移时需要平移三角形的所有顶点,所以使用uniform属性
    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.uniformMatrix4fv(mat, false, matrix);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(animation);
  }

  animation()

图形缩放

通过着色器让图形缩放

我们依旧是定义一个attribute属性,让顶点各个轴的上值乘上这个属性对应的值就行。间接去修改顶点属性。

 // 创建着色器源码
  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;
    }
  `; // 顶点着色器
  
  let x = 1;
  setInterval(() => {
    x += 0.1;
    if (x > 2) {
      x = 1;
    }
    // 修改缩放属性
    gl.vertexAttrib1f(aScale, x);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
  }, 60)

完整代码


  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  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;
    }
  `; // 顶点着色器

  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 aScale = gl.getAttribLocation(program, 'aScale');

  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;
  setInterval(() => {
    x += 0.1;
    if (x > 2) {
      x = 1;
    }
    gl.vertexAttrib1f(aScale, x);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
  }, 60)

通过缩放矩阵让图形缩放

就是需要定义一个缩放矩阵。方式同平移矩阵。

// 缩放矩阵
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,
  ])
}

完整代码


  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    uniform mat4 mat; // 类型为mat4
    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)
  
    // 缩放矩阵
    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) 
     * 
     * - location: 指定 uniform 变量的存储位置 
     * - transpose: 在 webgl 中恒为false 
     * - array: 矩阵数组
     * 
     **/
    gl.uniformMatrix4fv(mat, false, matrix);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(animation);
  }

  animation()

图形旋转

通过着色器让图形旋转

通过对顶点各个分量乘以对应的三角函数,就可以实现。

 // 创建着色器源码
  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;
    }
  `; // 顶点着色器
  
  let x = 1;
  function animation() {
    x += -0.01;
    gl.vertexAttrib1f(deg, x);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(animation)
  }
  animation();

完整代码


  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

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

  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;
    gl.vertexAttrib1f(deg, x);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(animation)
  }
  animation();

通过旋转矩阵让图形旋转

首先需要定义一个旋转矩阵

// 绕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,
  ])
}

完整代码


  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)
  
    // 绕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,
      ])
    }
    
  let x = 0;
  function animation() {
    x += 0.01;

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

    requestAnimationFrame(animation);
  }

  animation()