- Published on
PixiJS 源码揭秘 - 6. 探索复杂图形 Graphics 的设计与实现
- Authors
- Name
- 青雲
概述
Graphics类是PixiJS中的一个核心渲染类,它提供了一套完整的2D图形绘制系统。主要用途包括:
- 绘制基础图形,如线条、圆形、矩形等
- 为这些图形添加颜色和填充效果
- 创建复杂的遮罩(mask)
- 定义复杂的点击区域(hitArea)
其设计理念包括:
- 链式调用:所有绘图方法都返回this,支持方法链式调用
- 分离关注点:将绘图指令(Graphics)和实际渲染逻辑(GraphicsContext)分离
- 资源共享:支持多个Graphics实例共享同一个GraphicsContext,优化性能
- 灵活性:支持基础图形绘制、复杂路径创建、填充和描边样式自定义
核心架构详解
目录结构
主要类文件
Graphics.ts
- Graphics类的主要实现
- 提供用户级API接口
GraphicsContext.ts
- 图形上下文的核心实现
- 负责实际的绘图操作
BatchableGraphics.ts
- 可批处理的图形实现
- 优化渲染性能
系统文件
GraphicsContextSystem.ts
- 图形上下文系统
- 管理图形上下文的生命周期
GraphicsPipe.ts
- 图形渲染管道
- 处理图形的渲染流程
类型定义
FillTypes.ts
- 填充样式相关类型定义
- 定义了填充和描边的样式接口
- const.ts
- 定义模块使用的常量
- 配置参数等
构建命令 (buildCommands/)
- 负责将高级绘图命令转换为底层渲染指令
buildCommands/
├── buildLine.ts // 线条构建
├── buildPixelLine.ts // 像素线条构建
├── buildCircle.ts // 圆形构建
└── ... // 其他图形构建命令
路径系统 (path/)
- 处理路径的创建和管理
- 实现各种路径绘制算法
path/
├── GraphicsPath.ts // 图形路径实现
├── ShapePath.ts // 形状路径
└── roundShape.ts // 圆角形状实现
SVG支持 (svg/)
- 提供SVG路径解析和渲染支持
svg/
├── SVGParser.ts // SVG解析器
└── ... // 其他SVG相关实现
填充系统 (fill/)
- 处理各种填充样式和模式
fill/
├── FillPattern.ts // 填充模式
└── ... // 其他填充相关实现
工具函数 (utils/)
- 提供各种辅助功能和工具函数
utils/
├── convertFillInputToFillStyle.ts // 填充样式转换
└── ... // 其他工具函数
Graphics的组成与设计
核心组件
- Graphics类
export class Graphics extends ViewContainer implements Instruction {
public readonly renderPipeId: string = 'graphics'
private _context: GraphicsContext
private readonly _ownedContext: GraphicsContext
}
- 继承自ViewContainer,获得视图容器的基本能力
- 实现Instruction接口,支持渲染指令系统
- 提供用户友好的绘图API
- 管理绘图上下文的生命周期
- GraphicsContext
export class GraphicsContext extends EventEmitter {
public static defaultFillStyle: ConvertedFillStyle
public static defaultStrokeStyle: ConvertedStrokeStyle
// 事件系统
update: GraphicsContext
destroy: GraphicsContext
}
- 继承自EventEmitter,提供事件通知机制
- 实际执行绘图指令的核心引擎
- 管理图形的状态和生命周期
架构特点
双Context设计
private _context: GraphicsContext; // 当前使用的上下文
private readonly _ownedContext: GraphicsContext; // 实例拥有的上下文
- 支持上下文共享机制
- 允许多个Graphics实例共用同一个GraphicsContext
- 优化性能和内存使用
事件驱动更新
this._context.on('update', this.onViewUpdate, this)
- 通过事件系统实现状态同步
- 当GraphicsContext发生变化时自动触发视图更新
可配置性
export interface GraphicsOptions extends ContainerOptions {
context?: GraphicsContext
roundPixels?: boolean
}
- 支持自定义GraphicsContext
- 提供像素对齐等渲染选项
GraphicsContext详解
GraphicsContext是实际执行绘图操作的核心类。
核心功能
样式管理
public static defaultFillStyle: ConvertedFillStyle;
public static defaultStrokeStyle: ConvertedStrokeStyle;
- 提供默认填充和描边样式
- 支持颜色、透明度、纹理等属性
指令系统
export type GraphicsInstructions = FillInstruction | StrokeInstruction | TextureInstruction
- 支持填充、描边、纹理等多种绘图指令
- 使用类型联合确保指令类型安全
优化机制
状态管理
- 使用脏标记机制(dirty flags)追踪状态变化
- 维护状态栈用于保存和恢复绘图状态
- 支持共享上下文以复用绘图指令
批处理系统
// 'auto':根据上下文自动决定是否进行批处理
// 'batch':强制进行批处理
// 'no-batch':禁用批处理
export type BatchMode = 'auto' | 'batch' | 'no-batch'
- 支持自动/手动/禁用三种批处理模式
- 使用 BatchableGraphics 管理可批处理的图形
- 通过 GraphicsContextSystem 统一管理渲染数据
资源共享
- 支持多个Graphics实例共享同一个GraphicsContext
- 类似于精灵共享纹理的概念
- 减少GPU资源占用和内存消耗
几何数据管理
- 在 GpuGraphicsContext 中集中管理几何数据
- 支持顶点、UV和索引数据的复用
- 通过 GraphicsContextRenderData 管理渲染指令
渲染流程
指令转换流程
Graphics类接收API调用
// Graphics类提供用户友好的API
export class Graphics extends ViewContainer implements Instruction {
private _context: GraphicsContext
// 通过_callContextMethod方法将API调用转发给Context
private _callContextMethod(method: keyof GraphicsContext, args: any[]): this {
;(this._context[method] as any)(...args)
return this
}
}
转换为标准化指令
// GraphicsContext中的指令类型定义
export type GraphicsInstructions =
| FillInstruction // 填充指令
| StrokeInstruction // 描边指令
| TextureInstruction // 纹理指令
// 指令示例
interface FillInstruction {
action: 'fill' | 'cut'
data: {
style: ConvertedFillStyle
path: GraphicsPath
hole?: GraphicsPath
}
}
几何处理
GraphicsContextSystem处理
export class GraphicsContextSystem {
// 管理GPU上下文
private _gpuContextHash: Record<number, GpuGraphicsContext> = {}
// 管理渲染数据
private _graphicsDataContextHash: Record<number, GraphicsContextRenderData> = Object.create(null)
}
路径计算
export class ShapePath {
// 存储图形基元
public shapePrimitives: { shape: ShapePrimitive; transform?: Matrix }[] = []
// 用于碰撞检测和边界计算
private readonly _bounds = new Bounds()
// 当前多边形
private _currentPoly: Polygon | null = null
}
渲染优化
批处理系统
export class GraphicsPipe implements RenderPipe<Graphics> {
// 批处理哈希表
private _graphicsBatchesHash: Record<number, BatchableGraphics[]> = Object.create(null)
public validateRenderable(graphics: Graphics): boolean {
const context = graphics.context
const gpuContext = this.renderer.graphicsContext.updateGpuContext(context)
// 检查是否可以批处理
if (gpuContext.isBatchable) {
return true
}
return false
}
private _addToBatcher(graphics: Graphics, instructionSet: InstructionSet) {
// 实现批处理逻辑
const batches = (this._graphicsBatchesHash[graphics.uid] ||= [] as BatchableGraphics[])
}
}
状态缓存
export class GraphicsContext {
// 缓存变换矩阵
private _transform: Matrix = new Matrix()
// 缓存样式
private _fillStyle: ConvertedFillStyle
private _strokeStyle: ConvertedStrokeStyle
// 状态栈用于保存和恢复状态
private _stateStack: {
fillStyle: ConvertedFillStyle
strokeStyle: ConvertedStrokeStyle
transform: Matrix
}[] = []
}
渲染模式优化
export type BatchMode = 'auto' | 'batch' | 'no-batch'
export class GraphicsContext {
public batchMode: BatchMode = 'auto'
// 脏标记用于优化更新
public dirty = true
private _boundsDirty = true
}
总结
- Graphics组件
- User API Layer:提供用户友好的绘图API
- Event System:处理更新和状态变化事件
- GraphicsContext组件
- Instruction Manager:管理绘图指令
- State Manager:管理绘图状态
- 两个数据存储:Instructions和State Stack
- GraphicsPipe组件
- Batch Manager:处理批处理逻辑
- Validation:验证可渲染对象
- Batch Hash:存储批处理数据
- GraphicsContextSystem组件
- GPU Context Manager:管理GPU上下文
- Render Data Manager:管理渲染数据
- 两个数据存储:GPU Context Hash和Render Data Hash
- Renderer组件
- 支持多种渲染管线:WebGL、WebGPU和Canvas
下图展示了用户API如何转换为底层指令、状态管理和批处理的优化过程和各个组件之间的交互关系。
Graphics的设计具有以下特点:
- 分层设计
- Graphics层:提供用户API
- GraphicsContext层:管理绘图指令
- GraphicsPipe层:处理渲染和批处理
- GraphicsContextSystem层:管理GPU资源
- 性能优化
- 使用批处理减少绘制调用
- 状态缓存避免重复计算
- 脏标记机制优化更新
- 支持自动/手动批处理模式
- 灵活性
- 支持多种渲染后端(WebGL/WebGPU/Canvas)
- 可自定义渲染适配器
- 支持复杂的图形操作和样式
绘图功能全解析
路径系统
Graphics提供了完整的路径绘制系统。
基础路径操作
beginPath() // 开始新路径
moveTo(x, y) // 移动到指定点
lineTo(x, y) // 绘制直线到指定点
closePath() // 闭合路径
曲线绘制
// 圆弧
arc(x, y, radius, startAngle, endAngle, counterclockwise?)
arcTo(x1, y1, x2, y2, radius)
// 三次贝塞尔曲线
public bezierCurveTo(
cp1x: number, cp1y: number, // 第一控制点
cp2x: number, cp2y: number, // 第二控制点
x: number, y: number, // 终点
smoothness?: number // 平滑度
): this {
this._ensurePoly();
const currentPoly = this._currentPoly;
// 处理曲线生成
buildAdaptiveBezier(
currentPoly.points,
currentPoly.lastX, currentPoly.lastY,
cp1x, cp1y, cp2x, cp2y,
x, y,
smoothness,
);
return this;
}
// 二次贝塞尔曲线
public quadraticCurveTo(
cpx: number, cpy: number, // 控制点
x: number, y: number, // 终点
smoothing?: number // 平滑度
): this {
buildAdaptiveQuadratic(
this._currentPoly.points,
currentPoly.lastX, currentPoly.lastY,
cpx, cpy, x, y,
smoothing,
);
return this;
}
SVG路径支持
arcToSvg(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y)
Pixel Line
Pixel Line是PixiJS中的一种特殊线条绘制方式,它主要用于绘制像素精确的线条。与普通线条不同,Pixel Line不会进行抗锯齿处理,适合需要精确像素控制的场景。
export function buildPixelLine(
points: number[],
closed: boolean,
vertices: number[],
indices: number[]
): void
- points: 存储线条各个点的坐标
- 偶数索引存储x坐标
- 奇数索引存储y坐标
- closed: 控制线条是否闭合
- true: 首尾相连
- false: 保持开放
- vertices: 存储处理后的顶点信息
- indices: 存储顶点的连接顺序
pixelLine 的处理流程:
if (!lineStyle.pixelLine) {
buildLine(points, lineStyle, false, close, vertices, indices)
} else {
buildPixelLine(points, close, vertices, indices)
topology = 'line-list' // 使用 line-list 拓扑
}
- 当 pixelLine: true 时,使用 buildPixelLine 和 line-list 拓扑
- 这确保了线条以精确的像素级别渲染
- 不会受到变换和缩放的影响
网格绘制
function drawGrid(graphics: Graphics, width: number, height: number, cellSize: number) {
graphics.setStrokeStyle({
width: 1,
color: 0xcccccc,
pixelLine: true, // 关键是这个属性
})
// 先画所有的路径
// 绘制垂直线
for (let x = 0; x <= width; x += cellSize) {
graphics.moveTo(x, 0)
graphics.lineTo(x, height)
}
// 绘制水平线
for (let y = 0; y <= height; y += cellSize) {
graphics.moveTo(0, y)
graphics.lineTo(width, y)
}
// 最后一次性stroke所有路径
graphics.stroke()
}
像素艺术画
// 像素画绘制函数
function drawPixelArt(graphics, pixelData, pixelSize, offsetX = 0, offsetY = 0) {
// 设置填充样式
graphics.setFillStyle({
color: 0x000000, // 黑色填充
})
// 设置描边样式
graphics.setStrokeStyle({
width: 1,
color: 0x333333,
pixelLine: true,
})
// 遍历像素数据
for (let y = 0; y < pixelData.length; y++) {
for (let x = 0; x < pixelData[y].length; x++) {
if (pixelData[y][x]) {
const px = offsetX + x * pixelSize
const py = offsetY + y * pixelSize
// 绘制一个填充的矩形
graphics.beginPath()
graphics.rect(px, py, pixelSize, pixelSize)
graphics.fill()
graphics.stroke()
}
}
}
}
图形绘制系统
提供了丰富的预定义图形。
基础图形
rect(x, y, w, h) // 矩形
circle(x, y, radius) // 圆形
ellipse(x, y, radiusX, radiusY) // 椭圆
高级图形
roundRect(x, y, w, h, radius) // 圆角矩形
regularPoly(x, y, radius, sides, rotation?, transform?) // 正多边形
roundPoly(x, y, radius, sides, corner, rotation?) // 圆角多边形
自定义图形
poly(points, close?) // 自定义多边形
roundShape(points, radius, useQuadratic?, smoothness?) // 圆角自定义图形
样式系统
填充样式(FillStyle)
const fillStyle = {
color: 0xffffff, // 颜色
alpha: 1, // 透明度
texture: Texture.WHITE, // 纹理填充
matrix: null, // 变换矩阵
fill: null, // 自定义填充
}
描边样式(StrokeStyle)
const strokeStyle = {
width: 1, // 线宽
color: 0xffffff, // 颜色
alpha: 1, // 透明度
alignment: 0.5, // 线条对齐
miterLimit: 10, // 斜接限制
cap: 'butt', // 线帽样式
join: 'miter', // 连接样式
}
变换系统
Graphics类提供了强大的变换系统,允许开发者对图形进行旋转、缩放、平移等操作。
变换矩阵基础
Matrix类结构
在PixiJS中,所有的变换操作都基于Matrix类:
class Matrix {
a: number // 水平缩放
b: number // 水平倾斜
c: number // 垂直倾斜
d: number // 垂直缩放
tx: number // 水平移动
ty: number // 垂直移动
}
矩阵变换原理
2D变换矩阵是一个3x3的矩阵,用于执行以下操作:
- 平移(Translation)
- 旋转(Rotation)
- 缩放(Scale)
- 倾斜(Skew)
变换矩阵的数学表达式:
[a b 0]
[c d 0]
[tx ty 1]
矩阵组合
- 多个变换可以通过矩阵乘法组合
- 变换的顺序会影响最终结果
- 组合顺序:从右到左应用变换
核心变换方法
// 位置变换
graphics.position.set(x, y)
// 旋转变换
graphics.rotation = angle
// 缩放变换
graphics.scale.set(scaleX, scaleY)
// 倾斜变换
graphics.skew.set(skewX, skewY)
状态管理
// 保存变换状态
const savedState = {
position: graphics.position.clone(),
rotation: graphics.rotation,
scale: graphics.scale.clone(),
skew: graphics.skew.clone(),
}
// 恢复变换状态
graphics.position.copyFrom(savedState.position)
graphics.rotation = savedState.rotation
graphics.scale.copyFrom(savedState.scale)
graphics.skew.copyFrom(savedState.skew)
最佳实践与使用建议
性能优化建议
Context复用
// 对于相同的图形,使用共享Context
const sharedContext = new GraphicsContext()
const instances = Array.from({ length: 10 }, () => new Graphics({ context: sharedContext }))
批处理使用
// 大量简单图形时启用批处理
const graphics = new Graphics()
graphics.batched = true
graphics.beginFill(0xff0000)
for (let i = 0; i < 1000; i++) {
graphics.drawRect(i * 10, 0, 8, 8)
}
资源管理
// 及时清理不需要的资源
graphics.destroy({ context: true })
常见使用场景
UI元素绘制
const button = new Graphics().beginFill(0x0000ff).roundRect(0, 0, 100, 40, 5).endFill()
复杂形状
const star = new Graphics()
.beginFill(0xffff00)
.regularPoly(100, 100, 50, 5, Math.PI / 2)
.endFill()
动画效果
const animatedCircle = new Graphics()
app.ticker.add((delta) => {
animatedCircle
.clear()
.beginFill(0xff0000)
.circle(100, 100, Math.sin(Date.now() / 1000) * 20 + 50)
.endFill()
})
综合示例:时钟
// 创建时钟图形
const clockGraphics = new Graphics()
app.stage.addChild(clockGraphics)
// 绘制时钟刻度
function drawClockFace(graphics) {
// 绘制外圈
graphics
.setStrokeStyle({
width: 4,
color: 0x000000,
})
.beginFill(0xffffff)
.drawCircle(0, 0, 200)
.endFill()
// 绘制刻度
for (let i = 0; i < 12; i++) {
const angle = (i * Math.PI * 2) / 12
// 绘制小时刻度
graphics.setStrokeStyle({
width: 4,
color: 0x000000,
})
const startX = Math.sin(angle) * 180
const startY = -Math.cos(angle) * 180
const endX = Math.sin(angle) * 200
const endY = -Math.cos(angle) * 200
graphics.moveTo(startX, startY).lineTo(endX, endY)
// 绘制分钟刻度
for (let j = 1; j < 5; j++) {
const minuteAngle = angle + (j * Math.PI * 2) / 60
const minStartX = Math.sin(minuteAngle) * 190
const minStartY = -Math.cos(minuteAngle) * 190
const minEndX = Math.sin(minuteAngle) * 200
const minEndY = -Math.cos(minuteAngle) * 200
graphics.moveTo(minStartX, minStartY).lineTo(minEndX, minEndY)
}
}
// 绘制中心点
graphics.beginFill(0x000000).drawCircle(0, 0, 8).endFill()
}
// 绘制时针
function drawHourHand(graphics, hours, minutes) {
const angle = ((hours + minutes / 60) * Math.PI * 2) / 12
const endX = Math.sin(angle) * 100
const endY = -Math.cos(angle) * 100
graphics
.setStrokeStyle({
width: 8,
color: 0x000000,
cap: 'round',
})
.moveTo(0, 0)
.lineTo(endX, endY)
.stroke() // 添加 stroke() 调用
}
// 绘制分针
function drawMinuteHand(graphics, minutes, seconds) {
const angle = ((minutes + seconds / 60) * Math.PI * 2) / 60
const endX = Math.sin(angle) * 140
const endY = -Math.cos(angle) * 140
graphics
.setStrokeStyle({
width: 4,
color: 0x000000,
cap: 'round',
})
.moveTo(0, 0)
.lineTo(endX, endY)
.stroke()
}
// 绘制秒针
function drawSecondHand(graphics, seconds, milliseconds) {
const angle = ((seconds + milliseconds / 1000) * Math.PI * 2) / 60
const backX = Math.sin(angle) * -20
const backY = -Math.cos(angle) * -20
const endX = Math.sin(angle) * 160
const endY = -Math.cos(angle) * 160
graphics
.setStrokeStyle({
width: 2,
color: 0xff0000,
cap: 'round',
})
.moveTo(backX, backY)
.lineTo(endX, endY)
.stroke()
}
// 绘制数字
function drawNumbers(graphics) {
const radius = 160
const fontSize = 24
for (let i = 1; i <= 12; i++) {
const angle = (i * Math.PI * 2) / 12
const x = Math.sin(angle) * radius
const y = -Math.cos(angle) * radius
const text = new Text({
text: i.toString(),
style: {
fontFamily: 'Arial',
fontSize: fontSize,
fill: 0x000000,
align: 'center',
},
})
text.anchor.set(0.5)
text.position.set(x, y)
graphics.addChild(text)
}
}
// 更新时钟
function updateClock() {
const now = new Date()
const hours = now.getHours() % 12
const minutes = now.getMinutes()
const seconds = now.getSeconds()
const milliseconds = now.getMilliseconds()
clockGraphics.clear()
// 设置时钟位置到画布中心
clockGraphics.position.set(app.screen.width / 2, app.screen.height / 2)
// 绘制时钟面
drawClockFace(clockGraphics)
// 绘制指针
drawHourHand(clockGraphics, hours, minutes)
drawMinuteHand(clockGraphics, minutes, seconds)
drawSecondHand(clockGraphics, seconds, milliseconds)
// 绘制数字
drawNumbers(clockGraphics)
}
// 添加阴影效果
function addShadowEffect() {
const shadow = new Graphics()
shadow.beginFill(0x000000, 0.1).drawCircle(0, 0, 205)
app.stage.addChildAt(shadow, 0)
shadow.position.set(app.screen.width / 2 + 5, app.screen.height / 2 + 5)
}
// 初始化
addShadowEffect()
// 启动动画循环
app.ticker.add(updateClock)
总结
PixiJS的Graphics类是一个功能强大、设计优雅的2D图形绘制系统。通过合理的架构设计和性能优化,它既保证了API的易用性,又确保了渲染性能。开发者可以根据具体需求,选择适当的API和优化策略,创建高效的图形渲染应用。