【性能篇】30 # 怎么给WebGL绘制加速?

news2025/2/3 0:52:33

说明

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

常规绘图方式的性能瓶颈

例子:在一个画布上渲染 3000 个不同颜色的、位置随机的三角形,并且让每个三角形的旋转角度也随机。

<!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>常规绘图方式的性能瓶颈</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="500" height="500"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);

            const vertex = `
                attribute vec2 a_vertexPosition;
                
                void main() {
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;

            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                uniform vec4 u_color;
                
                void main() {
                    gl_FragColor = u_color;
                }
            `;

            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            // 创建随机三角形的顶点
            function randomTriangle(
                x = 0,
                y = 0,
                rotation = 0.0,
                radius = 0.1
            ) {
                const a = rotation,
                    b = a + (2 * Math.PI) / 3,
                    c = a + (4 * Math.PI) / 3;

                return [
                    [x + radius * Math.sin(a), y + radius * Math.cos(a)],
                    [x + radius * Math.sin(b), y + radius * Math.cos(b)],
                    [x + radius * Math.sin(c), y + radius * Math.cos(c)],
                ];
            }

            const COUNT = 3000;

            // 依次渲染每个三角形
            function render() {
                for (let i = 0; i < COUNT; i++) {
                    const x = 2 * Math.random() - 1;
                    const y = 2 * Math.random() - 1;
                    const rotation = 2 * Math.PI * Math.random();

                    renderer.uniforms.u_color = [
                        Math.random(),
                        Math.random(),
                        Math.random(),
                        1,
                    ];

                    const positions = randomTriangle(x, y, rotation);
                    renderer.setMeshData([
                        {
                            positions,
                        },
                    ]);

                    renderer._draw();
                }
                requestAnimationFrame(render);
            }

            render();
        </script>
    </body>
</html>

我这台电脑渲染出来只有 4.2 fps

在这里插入图片描述

减少 CPU 计算次数

可以创建一个正三角形,然后通过视图矩阵的变化来实现绘制多个三角形,而视图矩阵可以放在顶点着色器中计算。

<!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>减少 CPU 计算次数</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="500" height="500"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);

            // 利用顶点着色器内完成位置和角度的计算
            const vertex = `
                attribute vec2 a_vertexPosition;

                uniform mat3 modelMatrix;

                void main() {
                    vec3 pos = modelMatrix * vec3(a_vertexPosition, 1);
                    gl_Position = vec4(pos, 1);
                }
            `;

            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                uniform vec4 u_color;
                
                void main() {
                    gl_FragColor = u_color;
                }
            `;

            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            
            // 生成一个正三角形顶点,并设置数据到缓冲区
            const alpha = 2 * Math.PI / 3;
            const beta = 2 * alpha;

            renderer.setMeshData({
                positions: [
                    [0, 0.1],
                    [0.1 * Math.sin(alpha), 0.1 * Math.cos(alpha)],
                    [0.1 * Math.sin(beta), 0.1 * Math.cos(beta)],
                ],
            });

            const COUNT = 3000;

            // 依次渲染每个三角形
            function render() {
                for (let i = 0; i < COUNT; i++) {
                    const x = 2 * Math.random() - 1;
                    const y = 2 * Math.random() - 1;
                    const rotation = 2 * Math.PI * Math.random();

                    // 用随机坐标和角度更新每个三角形的 modelMatrix 数据
                    renderer.uniforms.modelMatrix = [
                        Math.cos(rotation), -Math.sin(rotation), 0,
                        Math.sin(rotation), Math.cos(rotation), 0,
                        x, y, 1
                    ];

                    renderer.uniforms.u_color = [
                        Math.random(),
                        Math.random(),
                        Math.random(),
                        1,
                    ];

                    renderer._draw();
                }
                requestAnimationFrame(render);
            }

            render();
        </script>
    </body>
</html>

也是 4.2 fps,由于浏览器的 JavaScript 引擎的运算速度很快,感觉将顶点计算放到顶点着色器中进行了,性能差别也很微小。

在这里插入图片描述

静态批量绘制(多实例绘制)

重复图形的批量绘制,在 WebGL 中也叫做多实例绘制(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>静态批量绘制(多实例绘制)</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="500" height="500"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const canvas = document.querySelector('canvas');
            const renderer = new GlRenderer(canvas);

            const vertex = `
                attribute vec2 a_vertexPosition;
                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);
                }

                varying vec3 vColor;

                void main() {
                    float t = id / 10000.0;
                    float alpha = 6.28 * random(vec2(uTime, 2.0 + t));
                    float c = cos(alpha);
                    float s = sin(alpha);

                    mat3 modelMatrix = mat3(
                        c, -s, 0,
                        s, c, 0,
                        2.0 * random(vec2(uTime, t)) - 1.0, 2.0 * random(vec2(uTime, 1.0 + t)) - 1.0, 1
                    );
                    vec3 pos = modelMatrix * vec3(a_vertexPosition, 1);
                    vColor = vec3(
                        random(vec2(uTime, 4.0 + t)),
                        random(vec2(uTime, 5.0 + t)),
                        random(vec2(uTime, 6.0 + t))
                    );
                    gl_Position = vec4(pos, 1);
                }
            `;

            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                varying vec3 vColor;
                
                void main() {
                    gl_FragColor.rgb = vColor;
                    gl_FragColor.a = 1.0;
                }
            `;

            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            const alpha = (2 * Math.PI) / 3;
            const beta = 2 * alpha;

            const COUNT = 3000;
            renderer.setMeshData({
                positions: [
                    [0, 0.1],
                    [0.1 * Math.sin(alpha), 0.1 * Math.cos(alpha)],
                    [0.1 * Math.sin(beta), 0.1 * Math.cos(beta)],
                ],
                instanceCount: COUNT,
                attributes: {
                    id: { data: [...new Array(COUNT).keys()], divisor: 1 },
                },
            });

            function render(t) {
                renderer.uniforms.uTime = t / 1e6;
                renderer.render();
                requestAnimationFrame(render);
            }

            render(0);
        </script>
    </body>
</html>

效果如下:每一帧的实际渲染次数(即 WebGL 执行 drawElements 的次数)从原来的 3000 减少到了只有 1 次,而且计算都放到着色器里,利用 GPU 并行处理了,因此性能提升了 3000 倍。

在这里插入图片描述

动态批量绘制

如果是绘制不同的几何图形,只要它们使用同样的着色器程序,而且没有改变 uniform 变量,可以将顶点数据先合并再渲染,以减少渲染次数。

例子:将上面常规的代码改成随机的正三角形、正方形和正五边形

<!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>动态批量绘制</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="500" height="500"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);

            const vertex = `
                attribute vec2 a_vertexPosition;
                
                void main() {
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;

            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                uniform vec4 u_color;
                
                void main() {
                    gl_FragColor = u_color;
                }
            `;

            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            // 创建随机的正三角形、正方形和正五边形
            function randomShape(x = 0, y = 0, edges = 3, rotation = 0.0, radius = 0.1) {
                const a0 = rotation;
                const delta = 2 * Math.PI / edges;
                const positions = [];
                const cells = [];
                for(let i = 0; i < edges; i++) {
                    const angle = a0 + i * delta;
                    positions.push([x + radius * Math.sin(angle), y + radius * Math.cos(angle)]);
                    if(i > 0 && i < edges - 1) {
                        cells.push([0, i, i + 1]);
                    }
                }
                return { positions, cells };
            }

            const COUNT = 3000;

            // 依次渲染每个三角形
            function render() {
                for (let i = 0; i < COUNT; i++) {
                    const x = 2 * Math.random() - 1;
                    const y = 2 * Math.random() - 1;
                    const rotation = 2 * Math.PI * Math.random();

                    renderer.uniforms.u_color = [
                        Math.random(),
                        Math.random(),
                        Math.random(),
                        1,
                    ];

                    // 随机生成三、四、五、六边形
                    const {positions, cells} = randomShape(x, y, 3 + Math.floor(4 * Math.random()), rotation);
                    renderer.setMeshData([{
                        positions,
                        cells,
                    }]);

                    renderer._draw();
                }
                requestAnimationFrame(render);
            }

            render();
        </script>
    </body>
</html>

正四边形、正五边形、正六边形每个分别要用 2、3、4 个三角形去绘制,现在只有 3fps

在这里插入图片描述

<!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>动态批量绘制2</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="500" height="500"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);

            const vertex = `
                attribute vec3 a_vertexPosition;

                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);
                }

                varying vec3 vColor;

                void main() {
                    vec2 pos = a_vertexPosition.xy;
                    float t = a_vertexPosition.z / 10000.0;

                    float alpha = 6.28 * random(vec2(uTime, 2.0 + t));
                    float c = cos(alpha);
                    float s = sin(alpha);

                    mat3 modelMatrix = mat3(
                        c, -s, 0,
                        s, c, 0,
                        2.0 * random(vec2(uTime, t)) - 1.0, 2.0 * random(vec2(uTime, 1.0 + t)) - 1.0, 1
                    );
                    vColor = vec3(
                        random(vec2(uTime, 4.0 + t)),
                        random(vec2(uTime, 5.0 + t)),
                        random(vec2(uTime, 6.0 + t))
                    );
                    gl_Position = vec4(modelMatrix * vec3(pos, 1), 1);
                }
            `;

            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                varying vec3 vColor;
                
                void main() {
                    gl_FragColor.rgb = vColor;
                    gl_FragColor.a = 1.0;
                }
            `;

            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            // 将图形的顶点和索引全部合并起来批量创建图形
            function createShapes(count) {
                // 创建两个类型数组 positions 和 cells
                const positions = new Float32Array(count * 6 * 3); // 最多6边形
                const cells = new Int16Array(count * 4 * 3); // 索引数等于3倍顶点数-2

                let offset = 0;
                let cellsOffset = 0;
                for(let i = 0; i < count; i++) {
                    const edges = 3 + Math.floor(4 * Math.random());
                    const delta = 2 * Math.PI / edges;

                    for(let j = 0; j < edges; j++) {
                        const angle = j * delta;
                        positions.set([0.1 * Math.sin(angle), 0.1 * Math.cos(angle), i], (offset + j) * 3);
                        if(j > 0 && j < edges - 1) {
                            cells.set([offset, offset + j, offset + j + 1], cellsOffset);
                            cellsOffset += 3;
                        }
                    }
                    offset += edges;
                }
                return { positions, cells };
            }
            
            // 一次性渲染出来
            const COUNT = 3000;
            const { positions, cells } = createShapes(COUNT);

            renderer.setMeshData([{
                positions,
                cells,
            }]);

            function render(t) {
                renderer.uniforms.uTime = t;
                renderer.render();
                requestAnimationFrame(render);
            }

            render(0);
        </script>
    </body>
</html>

采用动态批量绘制之后:
在这里插入图片描述

透明度与反锯齿

透明度

在 WebGL 中,我们要处理半透明图形,可以开启混合模式(Blending Mode)让透明度生效。

gl.enable(gl.BLEND);

如果不需要处理半透明图形,尽量不开启混合模式,混合颜色本身有计算量,开启混合模式会造成一定的性能开销。

反锯齿

反锯齿(英语:anti-aliasing,简称AA),也译为抗锯齿或边缘柔化、消除混叠、抗图像折叠有损等。它是一种消除显示器输出的画面中图物边缘出现凹凸锯齿的技术,那些凹凸的锯齿通常因为高分辨率的信号以低分辨率表示或无法准确运算出3D图形坐标定位时所导致的图形混叠(aliasing)而产生的,反锯齿技术能有效地解决这些问题。它通常被用在数字信号处理、数字摄影、电脑绘图与数码音效等方面,柔化被混叠的数字信号。

在获取 WebGL 上下文时,关闭反锯齿设置也能减少开销、提升渲染性能。

const gl = canvas.getContext('webgl', { antiAlias: false }); // 不消除反锯齿

Shader 的效率

为了尽可能合并数据,动态批量绘制图形,要尽量使用同一个 WebGLProgram,并且避免在绘制过程中切换 WebGLProgram。但这也会造成着色器本身的代码逻辑复杂,从而影响 Shder 的效率。

最好的解决办法就是尽可能拆分不同的着色器代码,然后在绘制过程中根据不同元素进行切换。

另外,shader 代码不同于常规的 JavaScript 代码,它最大的特性是并行计算,因此处理逻辑的过程与普通的代码不同。

比如下面 shader 代码,无论是 if 还是 else 分支,在 glsl 中都会被执行,最终的值则根据条件表达式结果不同取不同分支计算的结果。

if(Math.random() > 0.5) {
  ...
} else {
  ...
}

因为 GPU 是并行计算的,也就是说并行执行大量 glsl 程序,但是每个子程序并不知道其他子程序的执行结果,所以最优的办法就是事先计算好 if 和 else 分支中的结果,再根据不同子程序的条件返回对应的结果。

上面的代码可以使用 step 函数来解决问题,这样性能就会好一些。代码如下:

gl_FragColor = vec4(1) * step(random(st), 0.5);

总结

WebGL 的性能优化原则就是尽量发挥出 GPU 的优势。

核心原则有两个:

  1. 尽量减少 CPU 计算次数
  2. 减少每一帧的绘制次数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/103887.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot+Vue实现前后端分离的高校思政课实践教学管理系统

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

基于SSM的大学生心理健康系统设计与实现

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 网站前台&#xff1a;关于我们、联系信息、文章信息、咨询师信息、服务信息、测试信喜 管理员功能&#xff1a; 1、管理…

继续谈谈从Rxjava迁移到Flow的背压策略

前言 对于背压问题不久前就讨论过了&#xff0c;这里就不过多介绍了&#xff0c;总之它是一个非常复杂的话题&#xff0c;本文的主要目的是分析我们如何从Rxjava迁移到Flow并且使用其背压方案&#xff0c;由于本身技术的限制以及协程内部的复杂性&#xff0c;不会做过多的深入…

下载安装PyTorch

1、下载并安装Visual Studio Code选择合适版本安装 2、下载安装conda并配置环境 下载方式一&#xff1a;官网下载 下载方式二&#xff1a;清华镜像安装 3、conda配置环境 打开电脑高级系统配置点开系统环境变量&#xff1a; 找到path然后点击Edit或者直接双击&#xff1a; 之后…

利用LSTM识别篇章关系实战代码+数据

1.显式篇章关系分类概述 案例知识点: 任务描述:篇章关系分析是自然语言中处理篇章级基础语言分析任务,其目的是利用规则或机器学习等计算机处理手段判别篇章各组成成分之间的修辞逻辑关系,从而从整体上理解篇章。其中论元之间有连接词连接的此类关系称为显式篇章关系。本教…

实验七、MOS管分压式偏置共源放大电路的静态和动态参数

一、题目 搭建MOS管分压式偏置共源放大电路。利用Multisim研究下列问题&#xff1a; &#xff08;1&#xff09;确定一组电路参数&#xff0c;使电路的 QQQ 点合适。 &#xff08;2&#xff09;若输出电压波形底部失真&#xff0c;则可采取哪些措施&#xff1f;若输出电压波形…

Mysql概念知识

Mysql数据库基础知识为什么要使用数据库数据保存在内存数据保存在文件数据保存在数据库什么是SQL&#xff1f;什么是MySQL?数据库三大范式是什么mysql有关权限的表都有哪几个MySQL的binlog有几种录入格式&#xff1f;分别有什么区别&#xff1f;数据类型mysql有哪些数据类型引…

计算机毕设Python+Vue学生资源管理系统(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

websocket使用方法

前言 最近项目用到了websocket接口&#xff0c;用来做长连接&#xff0c;监听服务器数据变化&#xff0c;保持各终端数据同步。 用下来发现确实很好用&#xff0c;避免了轮询&#xff0c;开销小&#xff0c;而且最重要的是没有同源策略限制。 websocket WebSocket 是一种在…

Nginx-安装和部署全过程

前言 OpenResty是一个基于Nginx与 Lua 的高性能 Web 平台&#xff0c;其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。openresty官网&#xff1a;OpenResty - 中文官方站 …

软件著作权到底享有哪些权利?

根据《计算机软件保护条例》相关规定 软件著作权人享有下列各项权利: &#xff08;一&#xff09;发表权&#xff0c;即决定软件是否公之于众的权利&#xff1b; &#xff08;二&#xff09;署名权&#xff0c;即表明开发者身份&#xff0c;在软件上署名的权利&#xff1b; &a…

iOS的启动优化

应用的启动优化 当我们参与到大型应用的时候 会遇到一些启动时间过长的情况 这时候就需要使用到相关的操作。 总结来说&#xff0c;main()方法调用前&#xff0c;启动过程大体分为如下步骤&#xff1a; 先是LLVM把项目翻译成IR文件然后到backend&#xff0c;PRE_MAIN,main。 …

感冒咳嗽土法子

目录介绍 01.常见的感冒药02.止咳的土方法03.感冒的土方法 01.常见的感冒药 感冒是生活中最常见的疾病 患者往往会有&#xff1a;头昏、发烧、浑身酸痛、鼻塞、流鼻涕等症状 注意点 注意休息&#xff0c;适当补充水分&#xff0c;保持室内空气流通。 常见感冒药 主要成分的作…

CSS规范

CSS规范 命名规范 页面外围控制整体布局宽度&#xff1a;wrapper、页头&#xff1a;header、页面主体&#xff1a;main、内容&#xff1a;content、页脚&#xff1a;footer、导航&#xff1a;nav、主导航&#xff1a;mainbav、子导航&#xff1a;subnav、顶导航&#xff1a;t…

利用FormData上传本地文件

前言 最近接了个小项目&#xff0c;有个用客户端本地文件的需求。 正常这种需求都是前台传文件&#xff0c;后台去解析。 但这次C的老哥非让我给文件路径&#xff0c;说公司平台有解析文件的能力。 我说web不是桌面端&#xff0c;拿不到真实路径&#xff0c;他还不信&#…

已解决1. Downgrade the protobuf package to 3.20.x or lower.

已解决TypeError: Descriptors cannot not be created directly. If this call came from a _pb2.py file, your generated code is out of date and must be regenerated with protoc > 3.1.0If you cannot immediately regenerate your protos, some other possible worka…

【Ctfer训练计划】——(二)

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座右…

kubernetes管理应用配置之ConfigMap和Secret

目录 一、ConfigMap 二、Secret 一、ConfigMap 应用部署的一个最佳实践是将应用所需的配置信息与程序进行分离&#xff0c;这样可以使得应用程序被更好地复用&#xff0c;通过不同的配置也能实现更灵活的功能。 将应用打包为容器镜像后&#xff0c;可以通过环境变量或者外挂文…

【MySQL】3.MySQL表操作

文章目录1.0 MySQL表操作详解1.1 MySQL创建表1.2 MySQL查看表结构1.3 MySQL修改表1.0 MySQL表操作详解 1.1 MySQL创建表 指令:create table table_name(field1 datatype, field2 datatype) character collate engine; 后面三个参数可以省略&#xff0c;MySQL会自动调用默认…

开源绘图神器,Labplot的初步使用

文章目录数据生成图像定制多图绘制Labplot是一款开源且免费的科学绘图软件&#xff0c;除了Windows外&#xff0c;对Linux的Debian、Ubuntu、Fedora等发行版均有着良好的支持。 其下载地址为&#xff1a;LabPlot2下载&#xff0c;安装过程注意选择中文。 数据生成 打开软件后…