图形
¥Graphics
图形 是 PixiJS 工具箱中一个复杂且容易被误解的工具。乍一看,它像是一个绘制形状的工具。确实如此!但它也可以用来生成掩模。这是如何运作的?
¥Graphics is a complex and much misunderstood tool in the PixiJS toolbox. At first glance, it looks like a tool for drawing shapes. And it is! But it can also be used to generate masks. How does that work?
在本指南中,我们将从如何思考它的用途开始,揭开 Graphics
对象的神秘面纱。
¥In this guide, we're going to de-mystify the Graphics
object, starting with how to think about what it does.
看看 图形示例代码。
¥Check out the graphics example code.
图形是关于构建的 - 不画画
¥Graphics Is About Building - Not Drawing
Graphics
类的首次用户常常对其工作原理感到困惑。让我们看一个创建 Graphics
对象并绘制矩形的示例片段:
¥First-time users of the Graphics
class often struggle with how it works. Let's look at an example snippet that creates a Graphics
object and draws a rectangle:
// 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
对象,然后将其用作掩码时,事情会变得更加奇怪。什么???
¥That code will work - you'll end up with a red rectangle on the screen. But it's pretty confusing when you start to think about it. Why am I drawing a rectangle when constructing the object? Isn't drawing something a one-time action? How does the rectangle get drawn the second frame? And it gets even weirder when you create a Graphics
object with a bunch of drawThis and drawThat calls, and then you use it as a mask. What???
问题在于函数名称以绘图为中心,绘图是将像素放置在屏幕上的操作。但尽管如此,Graphics
项目实际上是关于构建的。
¥The problem is that the function names are centered around drawing, which is an action that puts pixels on the screen. But in spite of that, the Graphics
object is really about building.
让我们更深入地了解一下 rect()
调用。当你调用 rect()
时,PixiJS 实际上并没有绘制任何东西。相反,它将你 "drew" 的矩形存储到几何图形列表中以供以后使用。如果你随后将 Graphics
对象添加到场景中,渲染器将会出现,并要求 Graphics
对象渲染自身。那时,你的矩形实际上被绘制出来了 - 以及你添加到几何列表中的任何其他形状、线条等。
¥Let's look a bit deeper at that rect()
call. When you call rect()
, PixiJS doesn't actually draw anything. Instead, it stores the rectangle you "drew" into a list of geometry for later use. If you then add the Graphics
object to the scene, the renderer will come along, and ask the Graphics
object to render itself. At that point, your rectangle actually gets drawn - along with any other shapes, lines, etc. that you've added to the geometry list.
一旦你明白发生了什么,事情就开始变得更有意义。例如,当你使用 Graphics
对象作为遮罩时,遮罩系统会使用几何列表中的图形基元列表来限制哪些像素进入屏幕。没有涉及绘图。
¥Once you understand what's going on, things start to make a lot more sense. When you use a Graphics
object as a mask, for example, the masking system uses that list of graphics primitives in the geometry list to constrain which pixels make it to the screen. There's no drawing involved.
这就是为什么将 Graphics
类视为几何构建工具而不是绘图工具会有所帮助。
¥That's why it helps to think of the Graphics
class not as a drawing tool, but as a geometry building tool.
原语类型
¥Types of Primitives
Graphics
类中有很多函数,但作为快速入门,以下是你可以添加的基本原语列表:
¥There are a lot of functions in the Graphics
class, but as a quick orientation, here's the list of basic primitives you can add:
线
¥Line
矩形
¥Rect
RoundRect
圆圈
¥Circle
椭圆
¥Ellipse
弧
¥Arc
贝塞尔曲线和二次曲线
¥Bezier and Quadratic Curve
此外,你还可以访问以下复杂原语:
¥In addition, you have access to the following complex primitives:
环面
¥Torus
倒角矩形
¥Chamfer Rect
圆角矩形
¥Fillet Rect
正多边形
¥Regular Polygon
星星
¥Star
圆角多边形
¥Rounded Polygon
还支持 svg。但由于 PixiJS 渲染孔的方式的本质(它有利于性能),一些复杂的孔形状可能无法正确渲染。但对于大多数形状来说,这就能解决问题!
¥There is also support for svg. But due to the nature of how PixiJS renders holes (it favours performance) Some complex hole shapes may render incorrectly. But for the majority of shapes, this will do the trick!
let mySvg = new Graphics().svg(`
<svg>
<path d="M 100 350 q 150 -300 300 0" stroke="blue" />
</svg>
`);
GraphicsContext
了解 Sprite 及其共享纹理之间的关系有助于掌握 GraphicsContext
的概念。正如多个 Sprite 可以利用单个纹理,通过不复制像素数据来节省内存一样,GraphicsContext 可以在多个 Graphics 对象之间共享。
¥Understanding the relationship between Sprites and their shared Texture can help grasp the concept of a GraphicsContext
. Just as multiple Sprites can utilize a single Texture, saving memory by not duplicating pixel data, a GraphicsContext can be shared across multiple Graphics objects.
GraphicsContext
的共享意味着将图形指令转换为 GPU 就绪几何图形的密集任务只需完成一次,并且结果可以重复使用,就像纹理一样。考虑这些方法之间的效率差异:
¥This sharing of a GraphicsContext
means that the intensive task of converting graphics instructions into GPU-ready geometry is done once, and the results are reused, much like textures. Consider the difference in efficiency between these approaches:
创建单独的圈子而不共享上下文:
¥Creating individual circles without sharing a context:
// Create 5 circles
for (let i = 0; i < 5; i++) {
let circle = new Graphics()
.circle(100, 100, 50)
.fill('red');
}
与共享 GraphicsContext 相比:
¥Versus sharing a 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
就变得非常重要。为了获得最佳性能,建议预先创建上下文并重用它们,就像纹理一样!
¥Now, this might not be a huge deal for circles and squares, but when you are using SVGs, it becomes quite important to not have to rebuild each time and instead share a GraphicsContext
. It's recommended for maximum performance to create your contexts upfront and reuse them, just like textures!
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 父对象创建的几何图元列表。图形函数实际上被传递到内部上下文:
¥If you don't explicitly pass a GraphicsContext
when creating a Graphics
object, then internally, it will have its own context, accessible via myGraphics.context
. The GraphicsContext class manages the list of geometry primitives created by the Graphics parent object. Graphics functions are literally passed through to the internal contexts:
let circleGraphics = new Graphics()
.circle(100, 100, 50)
.fill('red')
与...一样:
¥same as:
let circleGraphics = new Graphics()
circleGraphics.context
.circle(100, 100, 50)
.fill('red')
调用 Graphics.destroy()
会破坏图形。如果通过构造函数将上下文传递给它,那么它将将该上下文的销毁留给你。但是,如果上下文是内部创建的(默认),则当销毁 Graphics 对象时,该对象将销毁其内部 GraphicsContext
。
¥Calling Graphics.destroy()
will destroy the graphics. If a context was passed to it via the constructor then it will leave the destruction the that context to you. However if the context is internally created (the default), when destroyed the Graphics object will destroy its internal GraphicsContext
.
显示图形
¥Graphics For Display
好的,现在我们已经介绍了 Graphics
类的工作原理,让我们看看如何使用它。Graphics
对象最明显的用途是将动态生成的形状绘制到屏幕上。
¥OK, so now that we've covered how the Graphics
class works, let's look at how you use it. The most obvious use of a Graphics
object is to draw dynamically generated shapes to the screen.
这样做很简单。创建对象,调用各种构建器函数来添加自定义基元,然后将对象添加到场景图中。每个帧,渲染器都会出现,要求 Graphics
对象渲染自身,并且每个图元以及关联的线条和填充样式将被绘制到屏幕上。
¥Doing so is simple. Create the object, call the various builder functions to add your custom primitives, then add the object to the scene graph. Each frame, the renderer will come along, ask the Graphics
object to render itself, and each primitive, with associated line and fill styles, will be drawn to the screen.
图形作为蒙版
¥Graphics as a Mask
你还可以使用 Graphics 对象作为复杂蒙版。为此,请照常构建对象和基元。接下来创建一个包含屏蔽内容的 Container
对象,并将其 mask
属性设置为 Graphics 对象。现在,容器的子项将被剪裁为仅在你创建的几何体内部显示。该技术适用于 WebGL 和基于 Canvas 的渲染。
¥You can also use a Graphics object as a complex mask. To do so, build your object and primitives as usual. Next create a Container
object that will contain the masked content, and set its mask
property to your Graphics object. The children of the container will now be clipped to only show through inside the geometry you've created. This technique works for both WebGL and Canvas-based rendering.
看看 屏蔽示例代码。
¥Check out the masking example code.
注意事项和陷阱
¥Caveats and Gotchas
Graphics
类是一个复杂的野兽,因此在使用它时需要注意很多事情。
¥The Graphics
class is a complex beast, and so there are a number of things to be aware of when using it.
内存泄漏:对不再需要的任何 Graphics
对象调用 destroy()
以避免内存泄漏。
¥Memory Leaks: Call destroy()
on any Graphics
object you no longer need to avoid memory leaks.
孔:你创建的孔必须完全包含在形状中,否则可能无法正确进行三角测量。
¥Holes: Holes you create have to be completely contained in the shape or else it may not be able to triangulate correctly.
改变几何形状:如果你想更改 Graphics
对象的形状,则无需删除并重新创建它。相反,你可以使用 clear()
函数重置几何列表的内容,然后根据需要添加新的图元。每帧执行此操作时请注意性能。
¥Changing Geometry: If you want to change the shape of a Graphics
object, you don't need to delete and recreate it. Instead you can use the clear()
function to reset the contents of the geometry list, then add new primitives as desired. Be careful of performance when doing this every frame.
表现:Graphics
对象通常性能相当好。但是,如果你构建高度复杂的几何体,则可能会超过渲染期间允许批处理的阈值,这可能会对性能产生负面影响。对于批处理来说,最好使用多个 Graphics
对象,而不是使用具有多个形状的单个 Graphics
。
¥Performance: Graphics
objects are generally quite performant. However, if you build highly complex geometry, you may pass the threshold that permits batching during rendering, which can negatively impact performance. It's better for batching to use many Graphics
objects instead of a single Graphics
with many shapes.
透明度:由于 Graphics
对象按顺序渲染其基元,因此在使用混合模式或具有重叠几何体的部分透明度时要小心。诸如 ADD
和 MULTIPLY
之类的混合模式将适用于每个图元,而不是最终的合成图片。同样,部分透明的 Graphics
对象将显示图元重叠。要将透明度或混合模式应用到单个展平表面,请考虑使用 AlphaFilter 或 RenderTexture。
¥Transparency: Because the Graphics
object renders its primitives sequentially, be careful when using blend modes or partial transparency with overlapping geometry. Blend modes like ADD
and MULTIPLY
will work on each primitive, not on the final composite image. Similarly, partially transparent Graphics
objects will show primitives overlapping. To apply transparency or blend modes to a single flattened surface, consider using AlphaFilter or RenderTexture.