0x00 About && 关于
SVG
全称是 Scalable Vector Graphics
。中文是 可缩放的矢量图形
。
SVG
是用 XML
来描述二维图形的语言。
SVG
图形对象可被组化、样式化、变形和重组,包括图象嵌套、变形处理、剪辑路径、Alpha蒙板、滤镜特效和模板对象。
SVG
的矢量特性可以让移动设备清楚地浏览 SVG
图像信息,在放大后不会出现模糊的情况。
大约在7年前,我第一次接触 svg
,出于好奇,就用 cocos2d-html5 v2.x
写了一个 SVG 解析渲染 的演示程序。当然其中的实现功能非常的有限而且问题也很多,比如只支持画轮廓,无法填充图形,大部分的命令没有解析。
然后在5年前,用 cocos2d-js v3.9
把程序重写了一下,把渲染方式升级到了 v3
的风格然后加入了颜色填充的功能。
最近在论坛搜索一些关于 cc.Graphics
帖子的时候,偶然看到有一些便朋友提到了 svg
这个东西在 creator
中的使用可能性。
于是就有了下面的这个,大幅度升级的 creator
版的 svg
扩展。
PS: 插件已经通过审核,下载地址戳这里
0x01 Pitfalls && 坑
用一句话来描述一下这个组件的作用: 读取 svg 文件,解析,使用 creator 的方式 进行渲染
。
看描述可能会觉得,这不是什么特别复杂的事情,但是如果有尝试做过相同事情的朋友,可能才会了解,这里面的坑
实在是太多了。
svg 标签属性
svg
在对 path
之类路径的表述上非常的自由
,一个 M
表达式的参数可能有各种奇怪的写法:
M 1.23 -4.56
M 1.23-4.56
M1.23 -4.56
M 1.23,-4.56
M1.23,-4.56
M 1,23 -4,56
M1,23.-4,56
M 1.2.3.4.5.6
M 1e23-4e56
svg 分组结构
svg
在整体结构上也是非常的自由,比如一个 path
标签,可能被使用在各种层级中:
svg["g"]["g"][index]["path"]["d"]
svg["g"]["g"]["path"][index]["d"]
svg["g"]["g"]["path"]["d"]
svg["g"]["path"][index]["d"]
svg 填充规则
另一个相当麻烦的东西,就是 winding rule (缠绕规则)
,用来控制 svg
里面的 fill rule (填充规则)
。比如两个包含关系的矩形,顶点描述顺序的不同,填充的结果也不同。
cc.Graphics
上面这些坑都是来自 svg
一方的,这里的问题是来自 creator
内部的。
cc.Graphics
是 creator
里的绘图组件。但是其提供的绘图接口还是非常有限的。此外,cc.Graphics
在对一些曲线绘制的图形填充的时候,也有 bug
。
取决于 svg
对象的复杂程度,cc.Graphics
. 可能会被很重度的使用,但是这在 native
平台会有报错出现。不过这个问题就在最近,已经被官方解决了: 解决方案请看这里。
上面列出的是一些主要的坑,还有无数的小坑,无数的细节调试,这里推荐一个很棒的在线 svg 路径调试工具 svg-path-editor ,在调试解析器的时候,有很多细节,都是对着这个编辑器同步单步调试来寻找问题的并解决的。
要解决这些大的问题,而且还要尽量的不修改引擎,同时支持 Web
Android
iOS
平台,还是很有挑战性的。
0x02 Demo && 演示程序
在细说组件的功能之前,先来直接看下演示程序,看下目前这个组件已经 实现了哪些功能 和 能做这些什么。
下面的部分 gif
画质略低,帧数略低,实际效果是没有任何图像模糊和卡顿的。
Shapes
基础的测试用例,这里绘制的是 svg
所有支持的基础图像类型。
Paths
基础的测试用例,这里绘制的图形都是由 svg
支持的一个重要标签 path
所内置的命令组成的。
FillRule
进阶的测试用例,演示了组件所支持的 svg
内置的 填充规则
的实际渲染结果,包括了 非零填充
,奇偶填充
。演示的例子包括了 自相交多边形
,带洞的图形
,不同路径走向的图形
。
Icons
进阶用例,所有图标都来自开源网站 game-icons.net,用于进一步验证解析库的正确性。
Cat
压力测试用例,加载解析大小达到 12M
笔画数达到 29000+
的小猫图片,主要用于测试极大文件情况下,解析库是否仍然能够完成任务以及渲染是否会出现报错的情况。(该例子不建议在手机上运行,毕竟太暴力了,但是我已经跑过,验证过渲染结果的正确性了)
Scalability
实用测试用例,svg
最多的用处之一,就是无论怎么放大,都不会像 Sprite
一样变得模糊不清。
Hanzi
实用测试用例,演示了如何通过组件提供的一些内置属性,实现汉字书写的过程渲染演示。
Tiger
实用测试用例,演示了如何通过组件提供的一些内置属性,实现复杂图像的绘制过程渲染演示。
Yoga
实用测试用例,演示了如何通过组件提供的一些内置属性,实现图像的触摸填色功能演示。
Toucan
实用测试用例,演示了如何通过组件配合 cc.RenderTexture && FBO
实现 cc.Graphics
的 纹理化
并对其使用 shader
特效的演示。
0x03 Data Structure && 数据格式
这里介绍一下,目前解析后的 svg
对象对应的数据结构。
ssr.SVG.Data.Root
对应整个 svg
文件的根节点,目前简单的保存了 svg
文件相关的一些全局信息。
// 对应 svg 中的 width 属性
width: cc.Float
// 对应 svg 中的 height 属性
height: cc.Float
// 对应 svg 中的 x 属性
x: cc.Float
// 对应 svg 中的 y 属性
y: cc.Float
// 对应 svg 中的 viewBox 属性
viewBox: cc.rect
// 对应 svg 中的 version 属性
version: cc.String
// svg 解析后的命令数组,详见下面的 Command 解释
commandArray: [ssr.SVG.Data.Command]
ssr.SVG.Data.Command
对应 svg
中的基础图形命令,路径命令,如 <rect>
<circle>
<path>
标签的内容信息。
// 命令类型枚举,详见下面的说明
type: ssr.SVG.Const
// 对应 svg 中的 fill
fillColor: cc.Color
// 对应 svg 中的 stroke
strokeColor: cc.Color
// 对应 svg 中的 stroke-width
strokeWidth: cc.Float
// svg 中命令的原生参数,解析后的字典结果,对于不同类型,其内容会不同,先下面的说明
params: cc.Object
// 当前 command 下包含的 area 数组,详见下一节的 Area 解释
areaArray: [ssr.SVG.Data.Area]
// 当前 command 下进行 FillRule 检测后,Tess2 三角化后的结果
areaTess2: [cc.Vec2]
// 当前 command 下包含的 area 数量
areaCount: cc.Integer
// 当前 command 下包含的 stroke 总数量
strokeCount: cc.Integer
// 当前命令是否已经完成渲染,前提是其包含的所有区域已经完成渲染
isFinished: cc.Boolean
/**************/
ssr.SVG.Const
// 路径命令类型
PATH
// params 内容为 <path d=""> d 属性中的原始字符串
params: cc.String
// 椭圆形状命令类型
ELLIPSE
// params 内容为 {cx cy rx ry}
params: cc.Object
// 折线命令类型
POLYLINE
// params 内容为 <polyline points=""> points 属性中的原始字符串
params: cc.Object
// 多边形命令类型
POLYGON
// params 内容为 <polygon points=""> points 属性中的原始字符串
params: cc.Object
// 直线令类型
LINE
// params 内容为 {x1 y1 x2 y2}
params: cc.Object
// 圆形命令类型
CIRCLE
// params 内容为 {cx cy r}
params: cc.Object
// 矩形命令类型
RECT
// params 内容为 {x y width height}
params: cc.Object
ssr.SVG.Data.Area
上述的 Command
对象,在 path
的情况下,通常会有一个 PathCommand
中包含多个闭合或非闭合区域的情况,这里的 Area
就是对应这些数据的。
// 当前 Area 下包含的 Stroke 数组,详见下一节的 Stroke 解释
strokeArray: [ssr.SVG.Data.Stroke]
// 当前 Area 下包含的 Stroke 总数量
strokeCount: cc.Integer
// 当前 Area 所表示的多边形(闭合或不闭合)的顶点数组,用于做触摸检测用
polygonArray: [cc.Vec2]
// 当前区域是否已经完成渲染,前提是其包含的所有笔画命令已经完成渲染
isFinished: cc.Boolean
// 当前区域是否已经完成填色,主要用于填色模式
isPainted: cc.Boolean
ssr.SVG.Data.Stroke
上述的每个 Area
对象,都会包含至少一条完整的绘图指令,在 path
的情况下,一个 Area
会包含 M
, c
, t
, z
等这些命令,每一个命令都会被作为一个 Stroke
对象存储,这也是渲染的最小单元。
// 命令类型枚举,PATH 以外命令的值同 Command.Type,Path 命令会被更细的分解,详见下面的说明
commandType: ssr.SVG.Const
// 渲染类型枚举,详见下面的说明
renderType: ssr.SVG.Const
// 渲染用数据,根据渲染类型的不同,会被以不同的方式渲染
dataArray: [cc.Vec2]
// 记录原生的指令,对于 path 中常见的连写命令,这里记录的是拆分后的结果
instrunction: cc.String
// 对每一个指令,解析后的字典结果,对于不同类型,其内容会不同,先下面的说明
params = cc.Object
// 当前笔画是否已经被渲染过
isFinished: cc.Boolean
/**************/
// 对应 path 中的 z/Z 指令
PATH_END
// 对应 path 中的 m/M 指令
PATH_MOVE
// 对应 path 中的 l/L 指令
PATH_LINE
// 对应 path 中的 c/C 指令
PATH_CURVE_C
// 对应 path 中的 s/S 指令
PATH_CURVE_S
// 对应 path 中的 q/Q 指令
PATH_CURVE_Q
// 对应 path 中的 t/T 指令
PATH_CURVE_T
// 对应 path 中的 a/A 指令
PATH_CURVE_A
// 对应 cc.Graphics.moveTo 函数
RENDER_MOVE
// 非闭合图形用,不会调用 fill,可能调用 stroke
RENDER_END
// 对应 cc.Graphics.lineTo 函数
RENDER_LINE
// 对应 cc.Graphics.moveTo / lineTo 函数
RENDER_POLYLINE
// 对应 cc.Graphics.close 函数 可能调用 stroke / fill 函数
RENDER_CLOSE
0x04 Features && 功能
File && 文件
svg
是 xml
格式的文件,而在 creator
中,json
是比较推荐的使用格式,这里为了运行效率,并没有在组件中进行 xml
到 json
的格式转换,或是 xml
格式的解析,而是通过直接读取转换好的 json
格式文件来直接进行解析。
xml
转 json
的工具,网上有很多,这里随便贴一个 XML to JSON and JSON to XML converter online。
转换好的 json
文件,就可以直接在 creator
插件中使用了。
Parser && 解析器
svg
中的标签,属性是非常多的,目前版本所支持的一些标签和属性主要有下面这些。
Shapes && 图形
基本图形标签,和他们的一些属性。
可以看到,基本图形和属性都是支持的,目前没有实现的是:
- 一个不太常用的
<rect>
中的圆角矩形属性 -
<text>
标签
// 矩形
[OK] <rect>
[OK] x
[OK] y
[OK] width
[OK] height
[NG] rx
[NG] ry
// 圆形
[OK] <circle>
[OK] x
[OK] y
[OK] r
// 椭圆
[OK] <ellipse>
[OK] cx
[OK] cy
[OK] rx
[OK] ry
// 直线
[OK] <line>
[OK] x1
[OK] y1
[OK] x2
[OK] y2
// 折线
[OK] <polyline>
[OK] points
// 多边形
[OK] <polygon>
[OK] points
// 文字
[NG] text
Path && 路径
路径命令主要有如下这些,目前已经全部支持。
// 移动命令
[OK] M moveto (x y)+
// 闭合路径命令
[OK] Z closepath (none)
// 直线命令
[OK] L lineto (x y)+
// 平行线命令
[OK] H horizontal lineto x+
// 垂直线命令
[OK] V vertical lineto y+
// 三次贝塞尔曲线命令
[OK] C curveto (x1 y1 x2 y2 x y)+
// 简版三次贝塞尔曲线命令
[OK] S smooth curveto (x2 y2 x y)+
// 二次贝塞尔曲线命令
[OK] Q Quadratic Bézier curveto (x1 y1 x y)+
// 简版二次贝塞尔曲线命令
[OK] T smooth quadratic Bézier curveto (x y)+
// 弧形曲线命令
[OK] A elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
虽然 cc.Graphics
中提供了一些看似对应的接口,如 bezierCurveTo
,quadraticCurveTo
,arc
等接口,但是在实际测试后发现,使用这些接口连续绘图,形成闭合图形后填充,会发生问题,比如对凹凸、自交多边形,带孔图形的支持。
因此,解析库采用了将所有命令 Segmentation
线段化的统一处理。组件中的 ssr.SVG.Util.Polyline
就是负责处理这个的:
Polyline.FromCubicBezier(px, py, cx1, cy1, cx2, cy2, x, y, segments)
Polyline.FromQuadBezier(px, py, cx1, cy1, x, y, segments)
Polyline.FromEllipticArc(x1, y1, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x2, y2, segments)
Polyline.FromEllipse(cx, cy, rx, ry, segments)
Polyline.FromLine(x1, y1, x2, y2, segments)
Polyline.FromCircle(cx, cy, r, segments)
这些解析后的数据,就被作为 ssr.SVG.Data.Stroke
中的核心数据,用作后续对的渲染。所有命令的解析由 ssr.SVG.Util.Path
负责:
Path.M(index, lastM, cmd, terms, configuration, outArray)
Path.L(index, cmd, terms, configuration, outArray)
Path.H(index, cmd, terms, configuration, outArray)
Path.V(index, cmd, terms, configuration, outArray)
Path.C(index, cmd, terms, configuration, outArray)
Path.S(index, cmd, terms, configuration, outArray)
Path.Q(index, cmd, terms, configuration, outArray)
Path.T(index, cmd, terms, configuration, outArray)
Path.A(index, cmd, terms, configuration, outArray)
Path.Z(index, lastM, cmd, terms, configuration, outArray)
Path.Ellipse(term, configuration, outArray)
Path.Circle(term, configuration, outArray)
Path.Line(term, configuration, outArray)
Path.Polyline(term, configuration, outArray)
Path.Polygon(term, configuration, outArray)
Path.Rect(term, configuration, outArray)
Properties && 属性
目前支持的属性不多,但是已经涵盖了一些和渲染相关的主流属性:
// 填充色
[OK] fill
// 画线颜色
[OK] stroke
// 画线宽度
[OK] stroke-width
// 填充透明度
[OK] fill-opacity
// 填充规则
[OK] fill-rule
Global && 全局
目前支持的属性不多,但是已经涵盖了一些和渲染相关的主流属性:
[OK] width
[OK] height
[OK] x
[OK] y
[OK] version
[OK] viewBox
// 分组标签
[OK] g
[NG] clip-path
[NG] mask
[NG] transform
其中,width / height / x / y
有解析但没有实际的使用,解析器通过动态计算图像的实际大小,然后将其反映到 cc.Node
的 width / height / x / y
,使得渲染后的对象的所见大小和属性值完全吻合。
transform
属性暂时也没有解析,不过通过测试已经知道,cc.Node
中的所有形变属性,都能够准确的反映在组件上。
Editor Support && 编辑器支持
Editor
中支持所见即所得,大部分的属性,都可以在编辑器中实时调试,查看效果。
0x05 API && 函数接口
这里列出的,是 ssr.SVG.Component
中的一些主要属性和接口。
// SSRSVGComponent Properties
// svg json 文件
svgJSONFile: cc.JsonAsset
// 是否开启 强制分段 功能,主要用于一些绘图过程演示,可以展现出平滑的绘图效果
segmentationOn: cc.Boolean
// 是否开启 触摸 功能,主要用于配合填色功能使用
enableTouch: cc.Boolean
// 是否开启 填色 功能,需要 enableTouch 开启
enablePaintMode: cc.Boolean
// 是否开启 合并划线 功能,对于不需要演示绘图渲染过程的使用场景,开启后可以大大减少 cc.Graphics 渲染调用次数,大幅提高性能
enableMergeStroke: cc.Boolean
// 是否开启 FillRule检测 功能,开启后,会进行强制的填充规则检测,并对区域三角化,可以先在编辑器中尝试关闭,查看效果,有需要再打开
enableFillRuleCheck: cc.Boolean
// 是否开启 全局填充色 功能,开启后无视 svg 中实际的填充色
enableGlobalFillColor: cc.Boolean
// 全局填充色 配合 enableGlobalFillColor 使用
globalFillColor: cc.Color
// 是否开启 全局划线色 功能,开启后无视 svg 中实际的划线色
enableGlobalStrokeColor: cc.Boolean
// 全局划线色 配合 enableGlobalStrokeColor 使用
globalStrokeColor: cc.Color
// 是否开启 全局划线宽度 功能,开启后无视 svg 中实际的划线宽度,可以配合填色功能使用,强制画出图像的轮廓
enableGlobalStrokeWidth: cc.Boolean
// 全局划线色 配合 enableGlobalStrokeWidth 使用
globalStrokeWidth: cc.Float
// 设置曲线的分段数,数字越大曲线越平滑但越耗性能,需要设置合适的值
segments: cc.Float
// 是否需要对绘制的图像进行水平翻转
flipX: cc.Boolean
// 是否需要对绘制的图像进行垂直翻转 (svg 中坐标系统 Y 轴向下)
flipY: cc.Boolean
// 获取当前加载,解析后的 svg 对象
getSVGObject: ssr.SVG.Data.Root
// 单步绘图时使用,获取当前绘制的命令索引(下标从 1 开始)
getCommandIndex: cc.Integer
// 单步绘图时使用,获取解析后的 svg 图像的命令数量
getCommandCount: cc.Integer
// 单步绘图时使用,获取当前绘制的命令对象
getCommandObject: ssr.SVG.Data.Command
// 单步绘图时使用,获取当前绘制命令下的,区域的索引(下标从 1 开始)
getAreaIndex: cc.Integer
// 单步绘图时使用,获取当前绘制的命令下,包含区域的数量
getAreaCount: cc.Integer
// 单步绘图时使用,获取当前绘制的区域对象
getAreaObject: ssr.SVG.Data.Area
// 单步绘图时使用,获取当前绘制的命令+区域下,笔画的索引(下标从 1 开始)
getStrokeIndex: cc.Integer
// 单步绘图时使用,获取当前绘制的区域下,包含笔画的数量
getStrokeCount: cc.Integer
// 单步绘图时使用,获取当前绘制的笔画对象
getStrokeObject: ssr.SVG.Data.Stroke
// 根据给定的触摸点坐标 (getLocation),返回所有包含触摸点的区域对象
getTouchedAreaArray(cc.Vec2 pos): [ssr.SVG.Data.Area]
// 根据给定的触摸点坐标 (getLocation),填充触摸的区域对象
fillTouchedArea(cc.Vec2 pos)
// 重置组件对象,清空已绘制区域,已加载数据
reset
// 重置当前已绘制状态,清空已绘制区域,保留加载数据,保留设置数据
resetRender
// 内部调用 resetRender 后调用 drawAll,主要用于修改解析相关配置后,重新解析并渲染
redrawAll
// 内部循环调用 draw 直至渲染结束
drawAll
// 更定索引值,绘制指定明命令
drawCommand(int commandIndex)
// 内部循环调用 draw 直至渲染结束
drawArea(int commandIndex, int areaIndex)
// 内部循环调用 draw 直至渲染结束
drawStroke(int commandIndex, int areaIndex, int strokeIndex)
// 分部调用绘图,会自动计算,以 stroke 为单位,进行一次绘图渲染,如果图像渲染结束,会返回 false,否则返回 true
draw: cc.Boolean
0x06 How to Use && 使用方法
插件的使用方式,十分的简单:
-
新建一个
cc.Node
-
挂载
ssr.SVG.Component
插件 -
将
json
格式的svg
拖放到svgJsonFile
属性上即可 -
如果需要在原生环境进行复杂图形渲染的,记得要自己合并一下官方最近修复的这个 bug
-
如果需要进行
FillRule
检测的,需要将ssr/svg/util/tess2.js
文件作为插件导入
接下来就只需要修改一些属性配置,在编辑器中查看效果即可。
0x07 Platform Support && 平台支持
目前测试过的平台,设备,相关信息。
从测试结果来看,所有功能在所有测试的平台和设备上都可以正常运行 (但是记得在 native
上运行,要自己合并一下上面提到的,官方修复的 cc.Graphics bug
)。
引擎版本
Creator v2.4.3
测试设备
Model.1
2018 产
MacBook Pro (13-inch)
macOS Catalina 10.15.6
Google Chrome 版本 85.0.4183.83(正式版本)(64 位)
Firefox 80.0.1 (64 位)
Safari 版本13.1.2 (15609.3.5.1.3)
Model.2
2014 产
iPhone 6 11.3.1 MG4H27P/A
Model.3
2017 产
iPhone X 13.3.1 MQA92CH/A
Model.4
2017 产
Huawei Honor 7X BND-AL10 Android 9.0 EMUI 9.1.0
Model.5
2018 产
Huawei Mate20 Pro LYA-AL00 Android 10.1 EMUI v10.1.0
发布平台 / 测试结果
所有测试用例,在所有平台上,设备上,所有的测试用例都可以正常的运行,并且运行,渲染结果完全一致。
即使在几百元的安卓手机上,目前相对复杂的测试用例(比如老虎的用例),则能有着不错的表现。在开启 mergeStroke
以后,内存方面有了很大的提高。
下图为 tiger
用例在 iPhoneX
上渲染结束后的结果,运行前内存值在 150
左右
enableMergeStroke Off
的情况渲染结束后
enableMergeStroke On
的情况渲染结束后
0x08 Summary && 总结
可以看到,目前这个组件对于 svg
的 解析
渲染
控制
应用
上已经做到了一个比较令人满意的程度。
当然 svg
还有一些十分有趣,值得去挖掘和实现的功能,比如 <clip-path>
,<mask>
,<text>
标签。另外还有 svg animation
矢量图动画。请注意这些功能在目前 1.0.0 版本中还不支持,因此有些网上找来的 svg
文件,如果其中包含这类标签,就会造成渲染结果和实际不同。
这些功能想要在 cocos creator
中合理的实现,可能都不会是一件简单的事情,但是应该也不会是一件不可能的事,毕竟这一版本组件所实现的有些效果,在制作初期看来,是很难做到的。
目前商店上架的是 v1.0.0
版本,下一版本会更新 顺序播放描边动画和描边练习测试
相关的应用场景,具体内容可以参考 hanziwriter 的文档。
有兴趣的朋友可以关注,购买,支持