canvas、svg 与 webGL 性能测试

560 阅读2分钟

概念

canvas是指令式绘图系统

svg是浏览器 DOM 来渲染的

性能

frame per second,一般要求是大于24fps

60fps:16ms完成计算与绘制

24fps:42ms完成计算与绘制

测试方法

工具:

chrome devTools

测试方法:

分别使用不同技术方案绘制圆形。

打开chrome devetools的rendering观察

canvas

影响canvas性能

1、绘制图形的数量

2、绘制图形的大小

代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #canvas {
      margin-top: 200px;
    }
  </style>
</head>
<body>

  <canvas width="500" height="500" id="canvas"></canvas>
  
</body>
<script>
  // 7
  const canvas = document.getElementById('canvas')
  const ctx = canvas.getContext('2d')
  const width = canvas.width
  const height = canvas.height

  function randomColor() { return `hsl(${Math.random() * 360}, 100%, 50%)`;}
  
  function drawCircle(context, raduis) {
    const x = Math.random() * width
    const y = Math.random() * height
    const fillColor = randomColor()
    context.fillStyle = fillColor
    context.beginPath()
    context.arc(x, y, raduis, 0, Math.PI * 2)
    context.fill()
  }

  function draw(context, count = 500, raduis=200) {
    for(let i = 0; i < count; i++) {
      drawCircle(context,raduis)
    }
  }
  requestAnimationFrame(function update() {
    ctx.clearRect(0,0,width,height)
    draw(ctx)
    requestAnimationFrame(update)
  })
</script>
</html>

500个,半径10

image.png 3000个半径10

image.png 500个半径500.

image.png

影响因素

1、主要数量会影响svg的渲染帧率,数量大于3000明显低于canvas

2、大小对svg绘制帧率影响不大

代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <svg></svg>
</body>
<script>

function randomColor() {
  return `hsl(${Math.random() * 360}, 100%, 50%)`;
}

const root = document.querySelector('svg');
const COUNT = 3000;
const WIDTH = 500;
const HEIGHT = 500;

function initCircles(count = COUNT) {
  for(let i = 0; i < count; i++) {
    const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    root.appendChild(circle);
  }
  return [...root.querySelectorAll('circle')];
}
const circles = initCircles();

function drawCircle(circle, radius = 10) {
  const x = Math.random() * WIDTH;
  const y = Math.random() * HEIGHT;
  const fillColor = randomColor();
  circle.setAttribute('cx', x);
  circle.setAttribute('cy', y);
  circle.setAttribute('r', radius);
  circle.setAttribute('fill', fillColor);
}

function draw() {
  for(let i = 0; i < COUNT; i++) {
    drawCircle(circles[i]);
  }
  requestAnimationFrame(draw);
}

draw();

</script>
</html>

500个半径10

image.png

3000个半径10

image.png

500个半径500

image.png

一般来说,在图形数量小于 1000 时,我们可以考虑使用 SVG,当图形数量大于 1000 但不超过 3000 时,我们考虑使用 Canvas2D。(我的测试电脑是3000个为临界点,这个结论是月影老师给出的。)

WebGL影响因素

1、WebGL 和 Canvas2D 与 SVG 不同,它的性能并不直接与渲染元素的数量相关,而是取决于 WebGL 的渲染次数

2、元素的数量多,WebGL 渲染效率也会逐渐降低,这是因为,元素越多,本身渲染耗费的内存也越多,占用内存太多,渲染效率也会下降。

3、在渲染次数相同的情况下,WebGL 的效率取决于着色器中的计算复杂度和执行次数。图形顶点越多,顶点着色器的执行次数越多,图形越大,片元着色器的执行次数越多,虽然是并行执行,但执行次数多依然会有更大的性能开销。

4、最后,如果每次执行着色器中的计算越复杂,WebGL 渲染的性能开销自然也会越大。

总的来说,WebGL 的性能主要有三点决定因素,一是渲染次数,二是着色器执行的次数,三是着色器运算的复杂度。当然,数据的大小也会决定内存的消耗,因此也会对性能有所影响,只不过影响没有前面三点那么明显。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ogl Circles</title>
</head>
<body>
  <canvas width="500" height="500"></canvas>
  <script type="module">
    import {Renderer, Program, Geometry, Transform, Mesh} from '../common/lib/ogl/index.mjs';
   
    const canvas = document.querySelector('canvas');
    const renderer = new Renderer({
      canvas,
      antialias: true,
      width: 500,
      height: 500,
    });
    
    const gl = renderer.gl;
    gl.clearColor(1, 1, 1, 1);

    function circleGeometry(gl, radius = 0.004, count = 30000, segments = 20) {
      const tau = Math.PI * 2;
      const position = new Float32Array(segments * 2 + 2);
      const index = new Uint16Array(segments * 3);
      const id = new Uint16Array(count);

      for(let i = 0; i < segments; i++) {
        const alpha = i / segments * tau;
        position.set([radius * Math.cos(alpha), radius * Math.sin(alpha)], i * 2 + 2);
      }
      for(let i = 0; i < segments; i++) {
        if(i === segments - 1) {
          index.set([0, i + 1, 1], i * 3);
        } else {
          index.set([0, i + 1, i + 2], i * 3);
        }
      }
      for(let i = 0; i < count; i++) {
        id.set([i], i);
      }
      return new Geometry(gl, {
        position: {
          data: position,
          size: 2,
        },
        index: {
          data: index,
        },
        id: {
          instanced: 1,
          size: 1,
          data: id,
        },
      });
    }
    // const geometry = new Box(gl);
    const geometry = circleGeometry(gl);
    const vertex = `
      precision highp float;
      attribute vec2 position;
      attribute float id;

      uniform float uTime;

      highp float random(vec2 co) {
        highp float a = 12.9898;
        highp float b = 78.233;
        highp float c = 43758.5453;
        highp float dt= dot(co.xy ,vec2(a,b));
        highp float sn= mod(dt,3.14);
        return fract(sin(sn) * c);
      }

      //  Function from Iñigo Quiles
      //  https://www.shadertoy.com/view/MsS3Wc
      vec3 hsb2rgb(vec3 c){
        vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 6.0)-3.0)-1.0, 0.0, 1.0);
        rgb = rgb * rgb * (3.0 - 2.0 * rgb);
        return c.z * mix(vec3(1.0), rgb, c.y);
      }

      varying vec3 vColor;
  
      void main() {
        vec2 offset = vec2(
          1.0 - 2.0 * random(vec2(id + uTime, 100000.0)),
          1.0 - 2.0 * random(vec2(id + uTime, 200000.0))
        );
        vec3 color = vec3(
          random(vec2(id + uTime, 300000.0)),
          1.0,
          1.0
        );
        vColor = hsb2rgb(color);
        gl_Position = vec4(position * 20.0 + offset, 0, 1);
      }
    `;

    const fragment = `
      precision highp float;
      varying vec3 vColor;
      void main() {
        gl_FragColor = vec4(vColor, 1);
      }
    `;

    const program = new Program(gl, {
      vertex,
      fragment,
      uniforms: {
        uTime: {value: 0},
      },
    });

    const scene = new Transform();
    const mesh = new Mesh(gl, {geometry, program});
    mesh.setParent(scene);

    function update(t) {
      program.uniforms.uTime.value = t / 1000;
      renderer.render({scene});
      requestAnimationFrame(update);
    }
    update(0);
  </script>
</body>
</html>

30000个半径0.004(相当于cavans半径10)

image.png