Skip to content

Latest commit

 

History

History
222 lines (161 loc) · 8.82 KB

README.zh_CN.md

File metadata and controls

222 lines (161 loc) · 8.82 KB

一个无限画布教程

Build Status Coverage Status

什么是无限画布?infinitecanvas 对“无限”的描述如下:

  • 高扩展性。用户可以以非线形的形式自由组织内容结构。
  • 缩放。模拟真实世界中的“放大”纵览全局和“缩小”观察细节。
  • 直接操作。提供对于基础图形的直观编辑能力,包括移动、成组、修改样式等。
  • 实时协作。

你一定见过甚至使用过各种包含无限画布的应用,infinitecanvas 上就展示了从设计工具到创意画板在内的众多案例,其中不乏一些知名产品包括 FigmaModyfiMotiffrnotetldrawexcalidraw等等。

作为一个前端,我对其中涉及到的渲染技术很感兴趣。尽管 tldrawexcalidraw 等普遍使用易用性更高的 Canvas2D / SVG 技术,但 JS 和 Rust 生态中也有很多编辑器、设计工具使用更底层的渲染技术对 2D 图形进行 GPU 加速,以获得更好的性能和体验:

  • Figma 使用 C++ 编写了一个 tile-based 的渲染引擎,编译成 WASM 后调用 WebGL 渲染
  • Motiff 同样使用 WebGL 实现了一个 tile-based 渲染引擎
  • Modyfi 使用了 Rust 生态中的 wgpu,同样编译成 WASM 后调用 WebGL2 渲染
  • Zed 使用 GPUI 渲染矩形、阴影、文本、图片等 UI。
  • velloxilem 实验性地使用了 Compute Shader 进行 2D 渲染。

因此在这个教程中,我希望实现以下特性:

  • 使用 @antv/g-device-api 作为硬件抽象层,支持 WebGL1/2 和 WebGPU。
  • 参考 mapboxFigma,尝试使用 Tile-based 渲染。
  • 使用 SDF 渲染圆、椭圆、矩形等。
  • GPU 加速的文本和贝塞尔曲线渲染。
  • 使用 rough.js 支持手绘风格。
  • 使用 CRDT 支持协同 Yjs

未来我希望将画布的渲染部分用 Rust 重写,目前项目的完成度还比较低:

  • wgpu 是非常可靠的硬件抽象层,甚至可以为 piet 实现后端。
  • Shader 基本可以复用。
  • 手绘风格可以使用 rough-rs
  • y-crdtYjs 的 Rust 实现。

让我们开始吧!

开始

课程项目使用了 pnpm workspace,因此需要先安装 pnpm

pnpm i

进入课程目录后,启动 Vite 开发服务器

cd packages/lesson_001
pnpm run dev

或者你也可以本地运行这个教程站点

pnpm run build
cd packages/site
pnpm run dev

课程 1 - 初始化画布 🔗

  • 基于 WebGL1/2 和 WebGPU 的硬件抽象层
  • 画布 API 设计
  • 实现一个简单的插件系统
  • 基于硬件抽象层实现一个渲染插件

课程 2 - 绘制圆 🔗

  • 向画布中添加图形
  • 使用 SDF 绘制一个圆形
  • 反走样

课程 3 - 变换和场景图 🔗

  • 变换。让图形支持平移、缩放、旋转、斜切变换
  • 实现场景图

Lesson 3

课程 4 - 相机 🔗

  • 相机是什么
  • 投影变换
  • 相机变换。通过一个插件实现平移、旋转和缩放功能
  • 相机动画。平滑过渡到任意相机状态

课程 5 - 绘制网格 🔗

  • 绘制直线网格。使用 Line Geometry 或者屏幕空间技术
  • 绘制点网格
  • 为 Geometry 绘制 wireframe

Lesson 5 grids

Lesson 5 wireframe

课程 6 - 事件系统 🔗

  • 参考 DOM API 实现事件系统
  • 如何拾取一个圆形
  • 实现一个拖拽插件
  • 支持双指缩放手势

课程 7 - Web UI 🔗

  • 使用 Lit 和 Shoelace 开发 Web UI
  • 实现画布组件,监听页面宽高变换
  • 实现缩放组件

课程 8 - 性能优化 🔗

  • 什么是 Draw call
  • 使用剔除减少 draw call
  • 使用合批减少 draw call
  • 使用空间索引提升拾取效率

Lesson 8

课程 9 - 绘制椭圆和矩形 🔗

  • 推导椭圆和圆角矩形的 SDF 表示
  • 为 SDF 增加外阴影和内阴影
  • 如何判定任意点是否在椭圆或圆角矩形内

Lesson 9 - drop shadow

Lesson 9 - inner shadow

课程 10 - 图片导入和导出 🔗

  • 将画布内容导出成 PNG,JPEG 和 SVG 格式的图片
  • 在画布中渲染图片
  • 拓展 SVG 的能力,以 stroke-alignment 为例

Lesson 10 - import and export images

课程 11 - 测试与服务端渲染 🔗

  • 基于 Jest 的测试环境搭建,包含本地和 CI 环境
  • 使用单元测试提升代码覆盖率
  • 视觉回归测试
    • 基于 headless-gl 的 WebGL1 服务端渲染方案
    • 基于 Playwright 的 WebGL2 / WebGPU 端到端测试方案
  • E2E 测试
  • 浏览器兼容性测试
  • 在 WebWorker 中渲染画布

课程 12 - 绘制折线 🔗

  • 为什么不直接使用 gl.LINES?
  • 在 CPU 或者 Shader 中构建 Mesh
  • 在 Shader 中构建顶点与接头、反走样、绘制虚线
  • 如何计算折线的包围盒?

Lesson 12 - polyline

课程 13 - 绘制 Path 与手绘风格 🔗

  • 尝试使用 SDF 绘制
  • 通过三角化绘制填充部分,使用折线绘制描边部分
    • 支持 earcut 和 libtess.js 两种三角化方案
    • 正确处理路径中的孔洞
    • 支持 fillRule 属性
  • 实现一些手绘风格图形

Lesson 13 - path

课程 14 - 画布模式 🔗

  • 实现 zIndexsizeAttenuation
  • 增加手型和选择画布模式。

Lesson 14 - canvas mode

课程 15 - 绘制文本 🔗

  • 什么是 TextMetrics,如何在服务端和浏览器端获取
  • 什么是 Shaping?实现 letterSpacing 与 kerning
  • 处理段落
    • 分段与自动换行
    • 支持 BiDi
    • 处理复合字符
    • 支持 text-align
  • 如何生成 SDF atlas 并使用它绘制
  • 如何使用 ESDT 和 MSDF 提升文本渲染质量
  • 如何绘制 Bitmap 格式的字体
  • 如何绘制 emoji

Lesson 15 - text

Lesson 15 - text

课程 16 - 文本的高级特性 🔗

  • 使用贝塞尔曲线渲染文本,使用 OpenType 和 Harfbuzz 进行字形处理
  • 渲染 TeX 公式
  • 文本装饰和阴影
  • 使用 Web Font Loader 加载 Web 字体

Lesson 16 - text