實現在小遊戲 WebGL2 上渲染 SVG?svg-webgl-loader 正式開源!

語言: CN / TW / HK

大廠技術    堅持周更    精選好文

大家平時可能接觸過 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);

API 相關

使用:

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 和 主體邏輯。

  • 主體邏輯約佔75%,主要是解析 svg 的各不同型別的路徑標籤、三角化等,優化空間不太大。

  • axios 佔約25%,後續可考慮分離出來或者實現一個普通的 ajax 即可。

渲染耗時

挑選了一個簡單幾何形狀和一個複雜圖形,分別看看耗時情況:

路徑複雜度 表現 解析/渲染耗時
簡單形狀 一個rect 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 的出生是因為興趣使然,所以在功能上依然還有很多可擴充套件及優化的地方,具體列舉如下。

可拓展功能

  1. 支援渲染合批

  2. 支援嵌入其他webgl環境

  3. 支援自定義曲線離散化功能

  4. 曲線自適應離散化方案

  5. 支援節點引用和複用

  6. 支援自定義圖形大小和縮放比例

  7. 支援SVG動畫

  8. 支援SVG IMG標籤

現有功能優化

  1. 分離曲線表達、離散化、三角化功能

  2. 重構Parse方法,支援棧解析SVG文字

  3. 增加GPU計算座標、顏色等資訊

  4. 包大小及耗時優化

svg-webgl-loader 中涉及到了大量 SVG 、Canvas 與 WebGL 相關的知識,探究它們的原理是困難而有趣的,如果你也有興趣一起探索關於它們的奧妙,也可以聯絡小編拍磚哦~

同時,如果你對 svg-webgl-loader 感興趣的話,別忘了去到對應的地址給我一個 Star 以鼓勵我們改進的更好哦~

Reference

[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

:heart: 謝謝支援

以上便是本次分享的全部內容,希望對你有所幫助^_^

喜歡的話別忘了 分享、點贊、收藏 三連哦~。

歡迎關注公眾號 「ELab團隊」 收貨大廠一手好文章~

我們來自位元組跳動,是旗下大力教育前端部門,負責位元組跳動教育全線產品前端開發工作。我們圍繞產品品質提升、開發效率、創意與前沿技術等方向沉澱與傳播專業知識及案例,為業界貢獻經驗價值。包括但不限於效能監控、元件庫、多端技術、Serverless、視覺化搭建、音影片、人工智慧、產品設計與營銷等內容。