PixiJS 源码解读:绘制矩形,底层都做了什么?

news2025/4/17 4:33:31

大家好,我是前端西瓜哥,今天带大家看一下 PixiJS 的源码实现。

PixiJS 是一个非常流行的 Canvas 库,start 数将近 4w。

使用 PixiJS 简单易用的 API,我们可以在浏览器页面的 Canvas 元素上高性能地绘制图形,实现流畅的动画。它的底层是 WebGL。

用 PixiJS 绘制一个矩形,代码实现为:

const app = new PIXI.Application({
  width: 500,
  height: 300,
});
document.body.appendChild(app.view);

const graph = new PIXI.Graphics();
graph.beginFill(0xff0044); // 填充色
graph.drawRect(10, 10, 100, 80);
graph.endFill();

app.stage.addChild(graph);

渲染结果:

这些代码的底层究竟做了什么呢?这次西瓜哥就带大家来一探究竟。

使用的 PixiJS 版本为 7.2.4。

Application 的初始化

首先是调用 Application 类的构造函数,创建 app 对象。

下面是 Application 构造函数的代码。

export class Application {
  // 创建 stage
  public stage: Container = new Container();
  // ...
  constructor(options) {
    options = Object.assign(
      {
        // 是否强制使用 Canvas 2D,否则如果支持 WebGL,用 WebGL
        // 默认为 false,且已经废弃 Canvas 2D,仅 pixi.js-legacy 可用
        forceCanvas: false, 
      },
      options
    );
	
    // 选择渲染器
    this.renderer = autoDetectRenderer(options);

    // 插件初始化
    Application._plugins.forEach((plugin) => {
      plugin.init.call(this, options);
    });
  }
}

主要做了以下几件事。

  1. 初始化 this.stage 为一个新的 Container 对象,将其作为根容器,之后我们绘制的矩形会放置于其下;

  2. 选择渲染器 renderer,有两种: Renderer(基于 WebGL) 和 CanvasRenderer(基于 Canvas 2D)。最新版 PixiJS 只内置了 Renderer。如果你希望在 WebGL 不可用时回退为 CanvasRenderer,需要改用 pixie.js-legacy 库。

  3. 调用 Renderer 的构造函数。它的属性 view 会指向一个 canvas 元素,Application 的 view 通过 getter 的代理方式拿到这个 view;

  4. 调用 Application 中注册插件的 init 方法,进行初始化。

Application 默认内置两个插件:

  • TickerPlugin:不停地在绘制下一帧前调用(基于 requestAnimationFrame)传入的回调函数,PixiJS 会在这里指定下一帧数要绘制的新内容;
  • ResizePlugin:监听容器尺寸变化,重绘画布。

创建图形

const graph = new PIXI.Graphics();

创建一个 Graphics 对象。这个 Graphics 对象下可以绘制任何图形,这里我只绘制一个矩形。

graph.beginFill(0xff0044); // 填充色

该方法会给 Graphics 对象的 _fillStyle 设置为指定的颜色值。传入的颜色值会进行标准化(normalize)。

Pixijs 实现有自己的风格:喜欢用类似 _varX 的方法保存 “私有” 变量,然后提供对应的 setter 和 getter 去读写这个内部变量。

getter 可能不提供,这样一个属性就会变成只读属性。有些 getter 里会做懒加载,在第一次读取的时候再初始化,比如 Texture.WHITE。

如果我们不指定颜色,这个 _fillStyle 会使用默认值,且其 visible 属性为 false,表示图形没有填充色,也会在之后的渲染阶段跳过填充的逻辑。

然后是创建一个矩形。

graph.drawRect(10, 10, 100, 80);

上面代码其实调用的是:

return this.drawShape(new PIXI.Rectangle(x, y, width, height));

首先创建一个 Rectangle 对象。

然后基于该 Rectangle 对象、之前设置的 fillStyle、lineStyle、matrix 创建一个 GraphicsData 对象,最后添加到给 rect._geometry.graphicsData 数组上。

总之就是将这个矩形的数据记录下来,之后 PixiJS 会基于这些值构造出绘制 WebGL 可以直接使用的数据。

然后是重置填充色。

rect.endFill();

将 rect 的 _fillStyle 设置为默认值:

public reset() {
  this.color = 0xFFFFFF;
  this.alpha = 1;
  this.texture = Texture.WHITE;
  this.matrix = null;
  this.visible = false;
}

最后是把 rect 添加到容器 app.stage 下。

app.stage.addChild(rect);

对应的源码是:

export class Container extends DisplayObject {
  // ...

  addChild(...children) {
    if (children.length > 1) {
      // 有多个图形要添加,会遍历调用当前 addChild 方法
      for (let i = 0; i < children.length; i++) {
        this.addChild(children[i]);
      }
    } else {
      const child = children[0];
      if (child.parent) {
        child.parent.removeChild(child);
      }

      child.parent = this;
      this.sortDirty = true; // 表示没有排序

      child.transform._parentID = -1;

      this.children.push(child);

      this._boundsID++;

      // 触发子节点改变的相关事件
      this.onChildrenChange(this.children.length - 1); 
      this.emit("childAdded", child, this, this.children.length - 1);
      child.emit("added", this);
    }
    return children[0];
  }
}

至此,我们的矩形设置好属性并添加到图形树上。

下面是渲染环节。

绘制

还记得我们初始化 Application 时,初始化的两个插件吗?

其中一个就是 TickerPlugin,它是 raf(requestAnimationFrame)的封装,会在页面绘制下一帧要之前执行回调函数。

Application 初始化时,调用了TickerPlugin.init() 方法,将 renderer 的 render 方法绑定到 Ticker 上。这样,render 就会不断地被异步调用。

class TickerPlugin {
  static init(options) {
    Object.defineProperty(this, "ticker", {
      set(ticker) {
        // 将 app.render 函数传入 ticker 的回调列表
        ticker.add(this.render, this, UPDATE_PRIORITY.LOW);
      },
      // ...
    });
    
    // 触发 ticker setter
    this.ticker = options.sharedTicker ? Ticker.shared : new Ticker();
  }
  // ...
}

render 方法:

class Application {
  // ...
  public render() {
    this.renderer.render(this.stage);
  }
}

因为渲染的过程非常长,代码逻辑太多,各种细枝末节,这里只讲大致流程,之后会写一篇文章具体讲解。

  1. 递归 app.stage 下的子 graph 对象,将其变换矩阵与父容器的做矩阵乘法(父容器的 transfrom 会影响子节点),最终计算出所有节点的最终复合变换矩阵;
  2. 之前创建的 Rectangle 对象,它的 x、y、width、height,转换为 WebGL 顶点着色器(Vertex Shader)需要的 8 个顶点数据;
  3. 对顶点应用变换矩阵;
  4. 计算好的顶点和颜色的一些中间批量数据。最后在 BatchRenderer.drawBatches() 方法中,调用了 WebGL 的 API:gl.drawElements

PixiJS 高性能的一个原因是减少 draw call,尽可能一次性批量(batch)提供大量顶点和片元给到 WebGL 去处理,充分利用 GPU 的并发计算能力。

结尾

我是前端西瓜哥,欢迎关注我,学习更多前端知识。

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

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

相关文章

基于深度学习的交通标志检测和识别(从原理到环境配置/代码运行)

项目是一个基于Python和OpenCV的交通标志检测和识别项目,旨在使用计算机视觉和深度学习技术对交通标志进行检测和分类。本文将从介绍项目原理和框架开始,详细介绍该项目的实现过程和技术细节,最后给出项目的安装和使用方法。 前后结果对比 识别前 识别后 一、 项目原理和框…

随身WIFI折腾日记(一)---霓虹灯

引言 通过对高通410芯片的随身WIFI刷写Debain系统&#xff0c;我们已经拥有了一台带4G功能的迷你ARM64单板电脑。现在我们可以基于此此平台进行一下二次开发。 随身WIFI的优势就是价格低廉&#xff0c;性能和树莓派zero2、树莓派3b差不多。 硬件配置如下&#xff1a; msm89…

随身WIFI折腾日记(二)---文件传输和软件安装

二、文件传输 我们可以通过SCP和SFTP工具和随身WIFI(USB连接)进行数据传输&#xff0c;上图以scp工具为例。 将本地电脑文件传输至随身WIFI&#xff0c;本地电脑上输入如下指令即可&#xff1a; scp /path/to/local/file user192.168.68.1:/path/to/remote/directory/注意&…

RK3568平台开发系列讲解(驱动基础篇)10min带你获取、了解与编译Kernel源代码

🚀返回专栏总目录 文章目录 一、Kernel获取二、Kernel根目录2.1 Documentation/2.1 arch/2.2 block/2.3 boot.its2.4 drivers/2.5 firmware/2.6 fs/2.7 include/2.8 init/2.9 ipc/2.10 kernel/2.11 lib/2.12 lo

(转载)从0开始学matlab(第13天)—画图进阶

我们将讨论简单的二维图象(之前已有所介绍)的附加特性。这些特性将允许我们控制 x&#xff0c;y 轴上的值的范围&#xff0c;在一个坐标系内打印多个图象&#xff0c;或创建多个图&#xff0c;或在一个图象窗口内创建多个子图像&#xff0c;或提供更加强大的轨迹文本字符控制。…

【c语言】全部知识点总结

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ …

前端如何做单元测试? 看这篇就入门了

前言 对于现在的前端工程&#xff0c;一个标准完整的项目&#xff0c;通常情况单元测试是非常必要的。但很多时候我们只是完成了项目而忽略了项目测试。我认为其中一个很大的原因是很多人对单元测试认知不够&#xff0c;因此我写了这边文章&#xff0c;一方面期望通过这篇文章…

基于DDSRF正负序分离方法的不平衡电网PQ控制策略_平衡电流控制

0.前言 对于并网逆变器而言&#xff0c;电网会存在不平衡的情况。在这种情况下&#xff0c;不平衡的电网电压可以分解成为正序、负序和零序分量。并网逆变器通常期望能够实现单位功率因数并网&#xff0c;向电网注入对称的正弦电流&#xff0c;所以此时的微电网逆变器控制策略显…

DC-9靶机-简单谈一下端口敲门技术 (Port Knocking)

前言 在打靶机DC-9时&#xff0c;爆破SSH时一直显示失败&#xff0c;经过查阅才知道原来是对端口做了“隐藏”&#xff0c;需要通过 Port Knocking 来主动开启&#xff0c;由于平时接触到的机会不多&#xff0c;所以这里简单记录一下&#xff0c;加强一下印象&#xff0c;也希…

Systrace系列7 —— Vsync 解读

本文主要是是介绍 Android 中的 Vsync 机制。文章会从 Systrace 的角度来看 Android 系统如何基于 Vsync 每一帧的展示。Vsync 是 Systrace 中一个非常关键的机制,虽然我们在操作手机的时候看不见,摸不着,但是在 Systrace 中我们可以看到,Android 系统在 Vsync 信号的指引下…

Tomcat系统架构浅析

大家好&#xff0c;我是易安&#xff01; 今天咱们就来一步一步分析Tomcat的设计思路&#xff0c;看看Tomcat的设计者们是如何设计一个复杂系统&#xff0c;怎么设计顶层模块&#xff0c;以及模块之间的关系。 Tomcat总体架构 我们知道如果要设计一个系统&#xff0c;首先是要…

特征缩放(Scale Features)、特征缩放预测​CO2 值、df列索引扩展

目录 1、特征缩放 2、预测CO2 值 3、df列索引扩展 1、特征缩放 特征缩放可以用于不同的度量单位。度量单位不同的情况下&#xff0c;特征的数值大小也会有所不同&#xff0c;这可能会影响到某些机器学习算法的表现。例如&#xff0c;如果一个特征的单位是英寸&#xff0c;而另…

DAB-DETR代码学习笔记

先上一张整体架构图 &#xff1a; 代码地址&#xff1a;GitHub - IDEA-Research/DAB-DETR: [ICLR 2022] DAB-DETR: Dynamic Anchor Boxes are Better Queries for DETR 论文地址&#xff1a; https://arxiv.org/pdf/2201.12329.pdf 文章全名《DYNAMIC ANCHOR BOXES ARE BETTER …

建模杂谈系列223 Q-Learning示例的代码拆解分析

说明 找到了一个合适的例子&#xff0c;然后我对其中的内容进行了拆解分析。我觉得代码表达的内容比伪代码清晰多了。 这次算是补砖了(监督无监督强化)&#xff0c;过去实际上接触过很多强化体系内的基本工具&#xff0c;但一直没有开始做&#xff0c;部分原因是没时间&#…

Java 与排序算法(5):归并排序

一、归并排序 归并排序&#xff08;Merge Sort&#xff09;是一种基于分治思想的排序算法。它将待排序的数组分成两个长度相等的子数组&#xff0c;然后对这两个子数组分别进行归并排序&#xff0c;最后将两个排好序的子数组合并成一个有序的数组。 具体实现过程如下&#xf…

【国内chatgpt使用方法合集】(5月22日已更新)

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

Elasticsearch文档操作:初学者指南(2023年最新版包含DSL语句的使用和RestHighLevelClient在Java中的使用)

2023年还没有学习Elasticsearch?&#xff0c;那么您将错过最强大、最通用的编程语言之一。 本文将介绍在Elasticsearch对文档分别使用DSL语句和Java High Level REST ClientAPI来对文档进行操作。获取更多信息查看官网帮助文档 运行环境&#xff1a; Linux&#xff0c;docke…

驱动开发DAY6

非阻塞IO 在应用程序中读取硬件数据时&#xff0c;无论硬件数据是否准备完毕&#xff0c;read&#xff08;&#xff09;函数不会阻塞&#xff0c;继续向下执行 阻塞IO 当应用程序中读取硬件数据时&#xff0c;在硬件数据没有准备好时&#xff0c;进程会阻塞在read&#xff08;&…

C语言——如何写出好的代码?

哈喽&#xff0c;大家好&#xff0c;今天我们来学习如何才能写出优秀的代码&#xff0c;主要讲的是assert和const的用法。 首先&#xff0c;什么样的代码才算的上是优秀的代码呢&#xff1f;应该符合下面的要求&#xff1a; 1. 代码运行正常 2. bug很少 3. 效率高 4. 可读性高 …

自抗扰PID(梯形图源代码)

有关ADRC的详细算法和源代码,请参看专栏的系列文章,这里不再赘述,常用链接如下: ADRC自抗扰控制算法(含梯形图完整源代码和算法公式)_adrc算法_RXXW_Dor的博客-CSDN博客PLC的自抗扰控制(ADRC)算法_RXXW_Dor的博客-CSDN博客_adrc算法1、自抗扰控制算法,网上很多文章有所…