图像
图像是 PixiJS 工具箱中一个复杂且容易被误解的工具。乍一看,它像一个用于绘制形状的工具。它确实是!但它也可用于生成蒙版。它是如何工作的?
在本指南中,我们将从了解其功能开始,对Graphics
对象进行解秘。
查看图像示例代码。
图像的重点是构建,而不是绘制
Graphics
类的首次用户常常难以理解其工作原理。让我们看一个示例代码片段,它创建了一个 Graphics
对象并绘制了一个矩形
// Create a Graphics object, draw a rectangle and fill it
let obj = new Graphics()
.rect(0, 0, 200, 100)
.fill(0xff0000);
// Add it to the stage to render
app.stage.addChild(obj);
该代码将起作用,你将在屏幕上看到一个红色矩形。但当你开始考虑它时,它会让你非常困惑。为什么在构造该对象时我要绘制一个矩形?绘制一些东西难道不是一个一次性操作吗?矩形是如何在第二帧绘制的?当你创建一个带有大量 drawThis 和 drawThat 调用的 Graphics
对象,然后将其用作蒙版时,情况会变得更加奇怪。什么??!
问题在于函数名称以绘制为中心,这是一项将像素放在屏幕上的操作。但尽管如此,Graphics
对象实际上是关于构建的。
让我们深入探究一下 rect()
的调用。当你调用 rect()
时,PixiJS 实际上并未绘制任何内容。相反,它将你“绘制”的矩形存储到几何图形列表中以供日后使用。如果你将 Graphics
对象添加到场景中,渲染器将出现并要求 Graphics
对象自行渲染。此时,你的矩形实际上已经被绘制了 - 以及你已添加到几何图形列表中的任何其他形状、线条等。
一旦你理解了这个原理,一切都开始更有意义了。例如,当你使用 Graphics
对象作为蒙版时,遮罩系统使用几何图形列表中的图形基元列表来限定哪些像素能到达屏幕上。无需涉及绘制。
这就是为什么将 Graphics
类视为几何构造工具而不是绘图工具有所帮助的原因。
基元的类型
Graphics
类中有很多函数,但作为一个快速的定向,以下是你可以添加的基本基元的列表
- 线
- 矩形
- 圆角矩形
- 圆
- 椭圆
- 圆弧
- 贝塞尔二次曲线
此外,还可以访问以下复杂的基元
- 圆环
- 倒角矩形
- 圆角矩形
- 正多边形
- 星形
- 圆角多边形
还支持 svg。但是由于 PixiJS 渲染孔洞的特性(注重性能),一些复杂的孔洞形状可能渲染不正确。但是,对于大多数形状来说,这将有效!
let mySvg = new Graphics().svg(`
<svg>
<path d="M 100 350 q 150 -300 300 0" stroke="blue" />
</svg>
`);
图形上下文
理解精灵与其共享纹理之间的关系,可以帮助理解 GraphicsContext
的概念。正如多个精灵可以利用单个纹理,通过不复制像素数据来节省内存一样,一个 GraphicsContext 可以在多个 Graphics 对象之间共享。
共享 GraphicsContext
意味着转换图形指令到 GPU 就绪几何图形的密集任务只需执行一次,而结果会被重复利用,就像纹理一样。考虑以下方法之间的效率差异
不共享上下文而创建单独的圆
// Create 5 circles
for (let i = 0; i < 5; i++) {
let circle = new Graphics()
.circle(100, 100, 50)
.fill('red');
}
与共享 GraphicsContext
// Create a master Graphicscontext
let circleContext = new GraphicsContext()
.circle(100, 100, 50)
.fill('red')
// Create 5 duplicate objects
for (let i = 0; i < 5; i++) {
// Initialize the duplicate using our circleContext
let duplicate = new Graphics(circleContext);
}
现在,对于圆形和正方形,这可能不是什么大问题,但当你使用 SVG 时,不每次都重建而改为共享 GraphicsContext
变得非常重要。为了获得最佳性能,建议预先创建你的上下文并重复使用它们,就像纹理一样!
let circleContext = new GraphicsContext()
.circle(100, 100, 50)
.fill('red')
let rectangleContext = new GraphicsContext()
.rect(0, 0, 50, 50)
.fill('red')
let frames = [circleContext, rectangleContext];
let frameIndex = 0;
const graphics = new Graphics(frames[frameIndex]);
// animate from square to circle:
function update()
{
// swap the context - this is a very cheap operation!
// much cheaper than clearing it each frame.
graphics.context = frames[frameIndex++%frames.length];
}
如果在创建 Graphics
对象时未明确传递 GraphicsContext
,则内部将拥有自己的上下文,可通过 myGraphics.context
访问。 GraphicsContext 类管理由 Graphics 父对象创建的几何图元列表。图元功能会直接传给内部上下文。
let circleGraphics = new Graphics()
.circle(100, 100, 50)
.fill('red')
等同于
let circleGraphics = new Graphics()
circleGraphics.context
.circle(100, 100, 50)
.fill('red')
调用 Graphics.destroy()
将销毁图形。如果通过构造函数将上下文传递给它,那么销毁操作将由你完成。但是,如果上下文是内部创建的(默认方式),则销毁时 Graphics 对象将销毁其内部的 GraphicsContext
。
作为显示的图像
知道了 Graphics
类的作用后,我们再来看看它的使用方法。使用 Graphics
对象时最明显的方法是将动态生成的形状绘制到屏幕上。
这很容易。创建对象,调用各个生成函数来添加自定义图元,然后将对象添加到场景图。每一帧,渲染器会要求 Graphics
对象进行自渲染,每个图元及关联的线条和填充样式都会被绘制到屏幕上。
作为遮罩的图形
你也可以将 Graphics 对象作为一个复杂的遮罩。要做到这一点,请像往常一样构建对象和图元。接下来,创建一个将包含遮罩内容的 Container
对象,并将它的 mask
属性设置为你的 Graphics 对象。现在,容器的子项会被裁剪,使其仅显示在你创建的几何图形内。该技术适用于基于 WebGL 和 Canvas 的渲染。
查看 遮罩示例代码。
注意事项和陷阱
Graphics
类很复杂,因此在使用它时需要了解一些注意事项。
内存泄漏:调用 destroy()
销毁所有不再需要的 Graphics
对象以避免发生内存泄漏。
孔洞:你创建的孔洞必须完全包含在形状中,否则该形状可能无法正确进行三角测量。
更改几何图形:如果你想更改 Graphics
对象的形状,不必删除它并重新创建它。相反,你可以使用 clear()
函数重置几何图形列表中的内容,然后根据需要添加新图元。这样做时,请注意性能问题。
性能:Graphics
对象通常性能很高。然而,如果构建了非常复杂的几何图形,则可能会超出允许在渲染期间进行批处理的阈值,这会对性能产生负面影响。对于批处理而言,最好使用多个 Graphics
对象,而不是使用具有许多形状的单个 Graphics
。
透明度:由于 Graphics
对象按顺序渲染其基本体,因此在将混合模式或部分透明度与重叠几何图形结合使用时,务必小心。诸如 ADD
和 MULTIPLY
的混合模式将作用于每个基本体,而不是作用于最终的复合图像。同样,部分透明的 Graphics
对象将显示基本体重叠。若要将透明度或混合模式应用于单个扁平化曲面,请考虑使用 AlphaFilter 或 RenderTexture。