【性能篇】28 # Canvas、SVG与WebGL在性能上的优势与劣势

简介: 【性能篇】28 # Canvas、SVG与WebGL在性能上的优势与劣势

说明

【跟月影学可视化】学习笔记。




可视化渲染的性能问题有哪些?


  • 渲染效率问题:指的是图形系统在绘图部分所花费的时间
  • 计算问题:指绘图之外的其他处理所花费的时间,包括图形数据的计算、正常的程序逻辑处理等等。


在浏览器上渲染动画,每一秒钟最高达到 60 帧左右。1 秒钟内完成 60 次图像的绘制,那么完成一次图像绘制的时间就是 1000/60(1 秒 =1000 毫秒),约等于 16 毫秒。


   60fps(即 60 帧每秒,fps 全称是 frame per second,是帧率单位)。


达到比较流畅的动画效果的最低帧率是 24fps,相当于图形系统要在大约 42 毫秒内完成一帧图像的绘制。



影响 Canvas 渲染性能的 2 大要素


影响 Canvas 渲染性能的 2 大要素:


  • 绘制图形的数量
  • 绘制图形的大小


Google Chrome浏览器怎么开启查看帧率功能?

测试例子:

<!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>canvas性能测试</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="500" height="500"></canvas>
        <script>
            const canvas = document.querySelector("canvas");
            const ctx = canvas.getContext("2d");
            const WIDTH = canvas.width;
            const HEIGHT = canvas.height;
            const COUNT = 500;
            const RADIUS = 10;
            function randomColor() {
                return `hsl(${Math.random() * 360}, 100%, 50%)`;
            }
            function drawCircle(context, radius) {
                const x = Math.random() * WIDTH;
                const y = Math.random() * HEIGHT;
                const fillColor = randomColor();
                context.fillStyle = fillColor;
                context.beginPath();
                context.arc(x, y, radius, 0, Math.PI * 2);
                context.fill();
            }
            function draw(context, count = 500, radius = 10) {
                for (let i = 0; i < count; i++) {
                    drawCircle(context, radius);
                }
            }
            requestAnimationFrame(function update() {
                ctx.clearRect(0, 0, WIDTH, HEIGHT);
                draw(ctx, COUNT, RADIUS);
                requestAnimationFrame(update);
            });
        </script>
    </body>
</html>


在 Canvas 上每一帧绘制 500 个半径为 10 的小圆:

cf84d8dd45ad4388861c701c9935b379.png


在 Canvas 上每一帧绘制 10000 个半径为 10 的小圆:


e9ecec19e0684ab9ba4611ba7efafe00.png

在 Canvas 上每一帧绘制 10000 个半径为 100 的小圆:

9b5b2d5fdef845caaf42f9f525246c04.png

我们可以看到随着数量的增大,半径的增大 fps 已经降到 24 以下了(还跟个人电脑的 GPU 和显卡有关)。




影响 SVG 性能的 2 大要素


影响 SVG 渲染性能的 2 大要素:


  • 绘制图形的数量
  • 绘制图形的大小

测试例子:

<!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>svg性能测试</title>
        <style>
            svg {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"></svg>
        <script>
            const root = document.querySelector("svg");
            const WIDTH = 500;
            const HEIGHT = 500;
            const COUNT = 500;
            const RADIUS = 10;
            function randomColor() {
                return `hsl(${Math.random() * 360}, 100%, 50%)`;
            }
            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], RADIUS);
                }
                requestAnimationFrame(draw);
            }
            draw();
        </script>
    </body>
</html>


在 SVG 上每一帧绘制 500 个半径为 10 的小圆:

f9e20dabd56f481f9c169b4f0b2ac352.png

在 SVG 上每一帧绘制 10000 个半径为 10 的小圆:跟 canvas 对比的 SVG 的帧率就要略差一些。

e9d7072c5ec54cda90081d24d4ddcd6b.png


在 SVG 上每一帧绘制 10000 个半径为 100 的小圆:跟 canvas 对比二者差距很大,因为 SVG 是浏览器 DOM 来渲染的,元素个数越多,消耗就越大。


e0ea3929ae304dafa12eb0b3e1827b07.png

SVG 与 Canvas 不同的是,图形数量增多的时候,SVG 的帧率下降会更明显,因此,一般来说,在图形数量小于 1000 时,我们可以考虑使用 SVG,当图形数量大于 1000 但不超过 3000 时,我们考虑使用 Canvas2D,当图形数量超过 3000 时,用 Canvas2D 也很难达到比较理想的帧率了,这时候,我们就要使用 WebGL 渲染。




影响 WebGL 性能的要素


WebGL 的性能主要有三点决定因素:


  • 渲染次数
  • 着色器执行的次数:图形增大,片元着色器要执行的次数就会增多,就会增加 GPU 运算的开销。
  • 着色器运算的复杂度


另外,元素越多,本身渲染耗费的内存也越多,占用内存太多,渲染效率也会下降。

WebGL 有支持的批量绘制的技术,叫做 InstancedDrawing(实例化渲染),在 OGL 库中,只需要给几何体数据传递带有 instanced 属性的顶点数据,就可以自动使用 instanced drawing 技术来批量绘制图形。

下面例子会用到:


<!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>webgl性能测试</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </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.002,
                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, // 通过 instanced:1 的方式告诉 WebGL 这是一个批量绘制的数据
                        size: 1,
                        data: id,
                    },
                });
            }
            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>


WebGL,绘制 30000 个小球:WebGL 渲染之所以能达到这么高的性能,是因为 WebGL 利用 GPU 并行执行的特性,无论批量绘制多少个小球,都能够同时完成计算并渲染出来。


3abc63ec598f469cb59c73e187c6024c.png

目录
相关文章
|
关系型数据库 MySQL 数据库
n8n自动化工具部署与使用
n8n是一款开源的工作流自动化工具,类似于IFTTT。它的优点是开源、可以自托管、下载安装方便、易于使用,可以互联上百种服务。n8n基于节点能够将任何工具连接在一起,轻松部署不同类型的任务。它可以做很多事情,比如:从数据库中获取数据后下载为excel然后通过邮件发送给其他人。
9603 1
|
数据可视化 数据挖掘 C++
一文入门数分三剑客--Numpy、Pandas、Matplotlib
一文入门数分三剑客--Numpy、Pandas、Matplotlib
365 0
|
8月前
|
IDE Linux API
轻松在本地部署 DeepSeek 蒸馏模型并无缝集成到你的 IDE
本文将详细介绍如何在本地部署 DeepSeek 蒸馏模型,内容主要包括 Ollama 的介绍与安装、如何通过 Ollama 部署 DeepSeek、在 ChatBox 中使用 DeepSeek 以及在 VS Code 中集成 DeepSeek 等。
2034 15
轻松在本地部署 DeepSeek 蒸馏模型并无缝集成到你的 IDE
9-14|npm install --global windows-build-tools 安装太慢了,能够指定国内源
9-14|npm install --global windows-build-tools 安装太慢了,能够指定国内源
|
5月前
|
人工智能 数据可视化 数据库
低代码开发模式下的应用交付效率优化:拖拽式交互机制研究
低代码开发平台以其可视化操作、快速构建和灵活扩展等特性,正成为企业数字化转型的重要工具。通过拖拽式开发降低技术门槛,借助预置模板加速上线进程,并支持API对接实现复杂IT架构的无缝集成。平台提供安全可控的企业级部署方案,满足金融、制造等高敏感行业的严格要求。其核心引擎涵盖SQL优化、功能扩展、图表渲染等,全面提升开发效率与系统性能。此外,模型驱动开发与AI深度融合,进一步赋能智能化数据处理与场景化推荐,助力企业实现从开发到决策支持的全方位升级。低代码不仅重构了开发范式,还通过开放生态和扩展能力,为开发者和技术团队提供了创新空间,推动全民开发者时代的到来。
|
11月前
|
数据采集 监控 JavaScript
如何使用 D3.js 处理大规模的地理数据集?
如何使用 D3.js 处理大规模的地理数据集?
|
XML 移动开发 前端开发
HTML5 SVG和canvas的性能探讨
HTML5 中的 SVG(可缩放矢量图形)和 Canvas(画布)分别用于网页图形绘制。SVG 基于矢量图形,使用 XML 描述,适合静态或少量动态内容(如图标、图表),易于编辑且保持高分辨率;Canvas 则基于位图,通过 JavaScript 绘制,更适合快速更新大量图形的场景(如游戏、动态动画),但在复杂图形计算时可能遇到性能瓶颈。总体而言,SVG 适用于静态和少量动态内容,而 Canvas 更适合高频率更新和性能要求高的场景。
|
机器学习/深度学习 人工智能 算法
在 AI Native 环境中实现自动超参数优化的微调方法
【8月更文第1天】随着人工智能技术的不断发展,深度学习模型的训练变得越来越复杂。为了达到最佳性能,需要对模型进行微调,特别是对超参数的选择。本文将探讨如何在 AI Native 环境下使用自动化工具和技术来优化模型的微调过程。
488 5
|
消息中间件 存储 负载均衡
[AIGC ~ coze] Kafka 消费者——从源码角度深入理解
[AIGC ~ coze] Kafka 消费者——从源码角度深入理解
247 0