使用WebGL绘制热力图

1.热力图

项目中需要绘制热力图,热力图其实就是数值大小用颜色来进行区分,每个点的数值需根据颜色映射表(调色板)映射为指定颜色。需要3个数值字段,可绘制在平行坐标系中(2个数值字段分别确定x、y轴,1个数值字段确定着色)。效果如下:
在这里插入图片描述
其实就是对每个点赋予指定颜色,echarts和canvas都很容易实现热力图(使用createImageData)的效果,由于之前学习过WebGL,于是就想着用webgl来实现热力图的效果。

  • 如何使用webgl来进行绘制呢?

热力图是由一个个彩色的点构成,所以,只需要思考如何使用webgl绘制出一个个彩色的点,那么就自然能形成热力图的效果。而webgl中有顶点着色器和片元着色器,一个用于计算顶点位置,一个用于计算颜色值,所以,关键就是把数据传个这两个着色器。

2.WebGL绘制多个点

缓冲区对象

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);
    }   
  `
  //创建着色器
  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
  gl.drawArrays(gl.POINTS, 0, 1)

如果想同时绘制多个点,就需要用到它所提供的缓冲区对象,它可以一次性向顶点着色器传入多个顶点的数据。

attribute变量

attribute用来存储顶点着色器中每个顶点的输入,包括顶点位置坐标、纹理坐标和颜色等信息,但是只能用于顶点着色器。
缓冲是程序发送给GPU的数据,attribute用来从缓冲中获取所需数据,并将它提供给顶点着色器。

使用缓冲区

缓冲区对象是WebGL中的一块存储区,可以在缓冲区对象中保存想要绘制的所有顶点数据。先创建一个缓冲区,然后向其中写入顶点数据,就能一次性向顶点着色其传入多个顶点的attribute变量的数据。
在这里插入图片描述

首先需要定义所有要向缓冲区对象写入的数据。

const vertices = new Float32Array([
  -0.5, 0.5,
  -0.5, -0.5,
  0.5, 0.5
])

然后使用使用缓冲区对象向顶点着色器传入多个顶点的数据,主要有五步:
1.创建缓冲区对象

const vertexBuffer = gl.createBuffer();

2.绑定缓冲区对象

gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

3.向缓冲区对象中写入数据

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

4.将缓冲区对象分配给一个attribute变量

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

5.激活attribute变量

gl.enableVertexAttribArray(a_Position);
  • 使用缓冲区代码
// 获取attribute变量位置
const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
    
    
  console.log('Failed to get the storage location of a_Position');
  return;
}
// 向缓冲区对象写入的数据
const vertices = new Float32Array([
  -0.5, 0.5,
  -0.5, -0.5,
  0.5, 0.5
])
const vertexBuffer = gl.createBuffer();//创建缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);// 绑定缓冲区对象到gl.ARRAY_BUFFER上,gl.ARRAY_BUFFER表示缓冲区对象中包含了顶点数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);// 将数据写入缓冲区对象,gl.STATIC_DRAW代表只向缓冲区写入一次数据
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0); // 调用顶点缓冲,将缓冲数据传给a_Position
gl.enableVertexAttribArray(a_Position);// 激活a_Position使用缓冲数组

  • 着色器代码

在着色器内,一般命名以gl_开头的变量是着色器的内置变量。变量声明一般包含<存储限定符><数据类型><变量名称>,下面代码中,attribute表示存储限定符,vec是数据类型,a_Position为变量名称。

const vs_source = `
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;
    gl_PointSize = 10.0;
  }
`;
// 片元着色器
const fs_source = `
  void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  }
`;
  • 完整代码
<!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 id="canvas" width="400" height="400"></canvas>
  <script>
    // 顶点着色器
    const vs_source = `
      attribute vec4 a_Position;
      void main() {
        gl_Position = a_Position;
        gl_PointSize = 10.0;
      }
    `;
    // 片元着色器
    const fs_source = `
      void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    `;
    const canvas = document.getElementById('canvas');
    const gl = canvas.getContext('webgl');
  
    function initShader() {
      
      
      // 创建shader
      const vs_shader = gl.createShader(gl.VERTEX_SHADER);
      gl.shaderSource(vs_shader, vs_source);
      gl.compileShader(vs_shader);
      if (!gl.getShaderParameter(vs_shader, gl.COMPILE_STATUS)) {
      
      
        const error = gl.getShaderInfoLog(vs_shader);
        console.log('Failed to compile vs_shader:' + error);
        gl.deleteShader(vs_shader);
        return;
      }
      const fs_shader = gl.createShader(gl.FRAGMENT_SHADER);
      gl.shaderSource(fs_shader, fs_source);
      gl.compileShader(fs_shader);
      if (!gl.getShaderParameter(fs_shader, gl.COMPILE_STATUS)) {
      
      
        const error = gl.getShaderInfoLog(fs_shader);
        console.log('Failed to compile fs_shader:' + error);
        gl.deleteShader(fs_shader);
        return;
      }
  
      // 创建program
      const program = gl.createProgram();
      gl.attachShader(program, vs_shader);
      gl.attachShader(program, fs_shader);
      gl.linkProgram(program);
      if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
      
      
        const error = gl.getProgramInfoLog(program);
        console.log('无法链接程序对象:' + error);
        gl.deleteProgram(program);
        gl.deleteShader(fs_shader);
        gl.deleteShader(vs_shader);
        return;
      }
      gl.useProgram(program);
      gl.program = program;

      // 获取attribute变量位置
      const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
      if (a_Position < 0) {
      
      
        console.log('Failed to get the storage location of a_Position');
        return;
      }
      // 向缓冲区对象写入的数据
      const vertices = new Float32Array([
        -0.5, 0.5,
        -0.5, -0.5,
        0.5, 0.5
      ])
      const vertexBuffer = gl.createBuffer();//创建缓冲区对象
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);// 绑定缓冲区对象到gl.ARRAY_BUFFER上,gl.ARRAY_BUFFER表示缓冲区对象中包含了顶点数据
      gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);// 将数据写入缓冲区对象,gl.STATIC_DRAW代表只向缓冲区写入一次数据
      gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0); // 调用顶点缓冲,将缓冲数据传给a_Position
      gl.enableVertexAttribArray(a_Position);// 激活a_Position使用缓冲数组
    }
  
    initShader();
    
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.POINTS, 0, 3);//绘制3个点
  </script>
</body>
</html>
  • 效果

在这里插入图片描述

3.WebGL绘制多个彩色点

接下来就是彩色点的绘制,需要传入每个点的颜色数据。

varying 可变量

varying一般同时存在顶点着色器和片元着色器中,它的作用是从顶点着色器向片元着色器传输数据。

// 顶点着色器
const vs_source = `
  attribute vec4 a_Position;
  attribute float a_PointSize;
  attribute vec4 a_Color;
  varying vec4 v_Color;
  void main() {
    gl_Position = a_Position;
    gl_PointSize = a_PointSize;
    v_Color = a_Color;
  }
`;
// 片元着色器
const fs_source = `
  precision mediump float;
  varying vec4 v_Color;
  void main() {
    gl_FragColor = v_Color;
  }
`;

上面,顶点着色器通过a_Position、a_PointSize分别接收并设置顶点的位置和大小,通过a_Color从程序获取颜色并通过v_Color传递给片元着色器。
片元着色器,首先设置了float为中等精度,然后通过v_Color接收来自顶点着色器的颜色并将其设置给内置变量gl_FragColor,其中通过内置变量gl_FragColor来确定顶点像素颜色。

读取缓冲区

缓冲区数据,7个为一组,前两个数据代表顶点位置,第3个代码顶点大小,第4-7个就代表顶点的颜色。

const vertices = new Float32Array([
  -0.5, 0.5, 10.0, 1.0, 0.0, 0.0, 1.0,
  -0.5, -0.5, 20.0, 0.0, 1.0, 0.0, 1.0,
  0.5, 0.5, 30.0, 0.0, 0.0, 1.0, 1.0
])
  • 如何读取出相应的顶点位置、大小以及颜色数据?

gl.vertexAttribPointer()可以指定读取缓冲的规则。
在这里插入图片描述
设置缓冲读取规则和启用缓冲对象

//设置缓冲读取规则
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, SIZE * 7, 0); // 将缓冲数据中一组7个数据中的前2个数据传给a_Position
gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, SIZE * 7, SIZE * 2); // 将缓冲数据中一组7个数据中的第3(偏移2个数据取1一个)个数据传给a_PointSize
gl.vertexAttribPointer(a_Color, 4, gl.FLOAT, false, SIZE * 7, SIZE * 3); //将缓冲数据中一组7个数据中的第4-7(偏移3个数据取4个)个数据传给a_Color
//启用缓冲对象
gl.enableVertexAttribArray(a_Position);// 激活a_Position使用缓冲数组
gl.enableVertexAttribArray(a_PointSize);// 激活a_Position使用缓冲数组
gl.enableVertexAttribArray(a_Color);// 激活a_Color使用缓冲数组
  • 效果

绘制出了3个不同大小、不同颜色的点。
在这里插入图片描述

4.热力图的绘制

接下来热力图的绘制就很简单了,只将每个点的位置信息和颜色值使用缓冲区传给着色器就可以。
可以如下来定义缓冲数据,6个为一组,前2个代表位置,后4个代表颜色(每个点的颜色是根据颜色映射表进行计算得到的)。

const vertices = new Float32Array([
  -0.5, 0.5, 1.0, 0.0, 0.0, 1.0,
  -0.5, -0.5,  0.0, 1.0, 0.0, 1.0,
  0.5, 0.5,  0.0, 0.0, 1.0, 1.0
  ......
])

猜你喜欢

转载自blog.csdn.net/weixin_43288600/article/details/130486986