大家平时可能接触过 SVG,但是可能对 WebGL 不是很了解,SVG 主要是一种矢量图格式,给定对应的数据就可以绘制点、线和图形等,基于 XML 标记语言编写,可以在很小的体积下就能得到高清晰度图形,且支持任意形式的放大缩小而不失真;而 WebGL 是 HTML5 之后浏览器支持的一套图形的 API。
而 SVG 要么通过 DOM 支持,要么通过 Canvas Path2D 支持,在只有 WebGL2 的 Context 下却因为缺少相应的 API,所以无法支持 SVG,但团队成员在平时研究 3D / Threejs 源码时,发现源码中有类似将 SVG 渲染到 WebGL2 Context 上的逻辑,很感兴趣,所以就花时间抽出一个库,提供方便的 API 实现 SVG 在 WebGL2 Context 上的渲染:「svg-webgl-loader」,目前项目已经开源,感兴趣的同学可以去到如下地址查看代码
Github:https://github.com/elab-opensource/svg-webgl-loader[1]
NPM:svg-webgl-loader[2]
我们提供了 NPM 包安装和 CDN 导入两种使用方式,其中 NPM 包安装如下:
npm i svg-webgl-loader # yarn add svg-webgl-loader
CDN 链接如下:
<script src="https://unpkg.com/svg-webgl-loader/dist/js/index.umd.js"></script>
如果是通过 NPM 包安装,那么在你项目中正常导入使用即可:
import svgWebglLoader from "svg-webgl-loader";
如果你是通过 CDN 的形式引入,那么则可以以如下形式使用:
<script src="https://unpkg.com/svg-webgl-loader/dist/js/index.umd.js"></script>
const svgWebglLoader = window.svgWebglLoader
一个标准的使用方式则为传入对应 SVG 内容以及待渲染的 Canvas 节点,以及配置对应的参数:
import svgWebglLoader from "svg-webgl-loader";
import svgUrl from "./img/test.svg";
// 加载解析svg数据
const svgData = await svgWebglLoader(svgUrl);
// 绘制
svgData.draw({
canvas,
loc: {
x: 0,
y: 0,
width: 300,
height: 300,
},
});
一个实际的例子可以参考 CodeOpen 链接:
https://codepen.io/yh418807968/pen/GREMPXw?editors=1011[3]
如果需要,可以通过以下形式修改背景颜色:
let gl = canvas.getContext('webgl');
gl.clearColor(1, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
使用:
const svgData = await svgWebglLoader(svgUrl);
svgData.draw(drawParams)
输入参数:
interface drawParams {
canvas: HTMLCanvasElement; // 待绘制的画布canvas
loc?: {
// 绘制区域
x: 0,
y: 0,
width?: 300, // 绘制宽度,默认为svg图片本身宽度
height?: 300, // 绘制宽度,默认为svg图片本身宽度
};
config?: {
needTrim?: true, // 是否需要去除svg边缘空白,默认false
needFill?: true, // 是否需要填充,默认true
needStroke?: true, // 是否需要描边, 默认true
};
}
调用之后产出的结果为渲染了 SVG 内容的 Canvas 对象,如果传入了对应的 canvas
参数,则为此传入的 Canvas 对象,否则会是在内部新建的 Canvas 对象。
svg-webgl-loader 主要是参考 Threejs 中加载渲染 SVG 的方案[4]:通过解析路径、路径离散化、三角化,将 SVG 分解为众多三角形,从而使得其可以采用 WebGL Shader 渲染。
以如下 SVG 为例:
<svg width="400" height="400" viewPort="0 0 400 400" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<circle
style="stroke:yellow; fill:blue; stroke-width: 3;"
cx="61"
cy="48"
r="30" />
<polygon transform="translate(0, 60)" points="60,20 100,40 100,80 60,100 20,80 20,40" style="stroke:yellow; fill:blue; stroke-width: 3;"/>
</svg>
其中包含一个圆形和一个多边形,解析流程主要如下:
解析路径,即为 circle 和 polygon
填充:如果有 fill 属性,则进行填充
分别对 circle 和 polygon 路径进行离散化,获得 pointList
对 pointList 进行三角化
传入三角化数据,采用 shader 进行绘制
描边:如果有 fill 属性,则进行描边
类似于填充,分别对 circle 和 polygon 路径进行离散化,获得 pointList
根据 stroke-width,计算 pointList 对应的内外路径 innerPointList 和 outPointList
按一定规律连接内外点,并采用 shader 进行绘制
大致流程参考下图:
❝注意:图中三角化、离散化等数据不是准确数据,只是解释说明大致流程。
❞
包大小约 90 KB,主要分为 2 部分:axios 和 主体逻辑。
挑选了一个简单几何形状和一个复杂图形,分别看看耗时情况:
路径复杂度 | 表现 | 解析/渲染耗时 | |
---|---|---|---|
简单形状 | 一个rect | t | 18ms |
复杂图形 | 约240条path | 150ms |
可以看到解析/渲染时间大致在 20~150ms 间,视觉上基本没有延时或卡顿。不过,由于 GPU 的并行绘制方式,绘制时间其实很短,以上时间几乎都是数据解析时间(即在主线程上进行),因此在耗时上的后续优化方向主要是将一些循环计算等转移到 shader 中,由 GPU 统一并行处理。
耗时计算方式:
❝开始时间:网络请求完成,即获得 SVG 文件字符串为 startTime;
❞
❝由于 GPU 采用并行绘制,耗时很短且几乎不受数据大小影响,因此此处直接以 draw Call 命令调用完成的时间为 endTime。
❞
svg-webgl-loader 目前可以渲染绝大部分 SVG,你可以通过体验它了解 SVG 和 WebGL 的一部分原理,同时有助于你日后在使用它们或了解 Threejs 相关的原理。
svg-webgl-loader 的出生是因为兴趣使然,所以在功能上依然还有很多可扩展及优化的地方,具体列举如下。
svg-webgl-loader 中涉及到了大量 SVG 、Canvas 与 WebGL 相关的知识,探究它们的原理是困难而有趣的,如果你也有兴趣一起探索关于它们的奥妙,也可以联系小编拍砖哦~
同时,如果你对 svg-webgl-loader 感兴趣的话,别忘了去到对应的地址给我一个 Star 以鼓励我们改进的更好哦~
[1]https://github.com/elab-opensource/svg-webgl-loader:https://github.com/elab-opensource/svg-webgl-loader
[2]svg-webgl-loader:https://www.npmjs.com/package/svg-webgl-loader
[3]https://codepen.io/yh418807968/pen/GREMPXw?editors=1011:https://codepen.io/yh418807968/pen/GREMPXw?editors=1011
[4]方案:https://github.com/mrdoob/three.js/blob/dev/examples/webgl_loader_svg.html
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8