🎨 Rough.js 手绘渲染引擎 · ELI5 视觉手账

如何用冷冰冰的数学代码
画出呼吸感十足的“手绘草图”?

深度解读 Excalidraw 底层灵魂 Rough.js:从一条直线的几何扰动,到阴影排线与手账美学的算法揭秘!

💡 一句话看懂核心主线
计算机天生擅长画绝对精确、笔直、对称但冷冰冰的矢量图形;而人类徒手画画时,手部肌肉永远存在微小的颤抖、重叠涂画的草稿复线与参差不齐的排线阴影!Rough.js(<9kB)正是把这种“不完美的生命力”提炼成了纯数学算法——通过 Bowing(微弓弧度)、Roughness(粗糙度双线扰动)与 Hachure(铅笔交叉阴影),把冰冷的点线面瞬间变为生动有温度的手绘草图!
小蓝:几何骨架 (Geometry)
冷冰冰的数学坐标与理想几何形状(正方形、标准圆、点到点精确线段)。
小橙:手绘抖动器 (Rough Engine)
给完美线段注入微小随机扰动、贝塞尔弯曲弧度与重复描摹草稿复线。
小绿:阴影彩笔 (Hachure Stylist)
拒绝机械色块,用手绘素描交叉排线(zigzag、cross-hatch、dots)填满图形。
红虚线:死板工业直角
缺乏温度的死板直角、绝对平滑的机械色块与压抑创意的视觉强迫症。

⛓️ 十级演进算法链:手绘感是如何被数学一步步算出来的?

演进 01

破除僵硬:从绝对笔直到微随机抖动

前代痛点:传统 Canvas `ctx.lineTo()` 是一条完美直角像素线,看起来像一把钢铁直尺,冰冷生硬毫无生气。
【传统绘图:钢尺直线】
lineTo(x2, y2)
绝对笔直单调乏味像冰冷钢尺
【突破:给端点注入随机偏移】
起点终点微幅抖动打破呆板
解决了什么:引入高斯随机扰动,微调连线两端的真实坐标,消除数学上的绝对对称。
代价是什么:如果抖动幅度没有约束,直线就会碎成锯齿甚至断成乱麻。
演进 02

手腕重力:Bowing 曲线微弓算法

前代痛点:光有端点微调还不够,人在白板上快速划线时,手腕会像圆规一样天然画出一段微微凸起的圆弧。
【僵硬:两点之间傻折线】
生硬折角并不符合人体工学
【突破:贝塞尔曲线微弯 (Bowing)】
Control Point
模拟手腕挥动自然的微弯弧度
解决了什么:通过在中点垂直法线方向施加 `bowing` 偏移控制点,生成柔和的二次贝塞尔微弯曲线。
代价是什么:绘制每根线段需要额外计算三角函数与曲率导数,计算量有所增加。
演进 03

草图精髓:双重描摹复线 (Double Stroke)

前代痛点:真正的画师徒手画草图时,绝不会一笔定型,而是下意识来回描上两遍,单单一根线条看起来太单薄。
【以前:孤零零一根线】
单线描摹一眼就被识破是电脑画
【突破:两次轻微交错描摹】
两条线相互交织极具手稿神韵
解决了什么:Rough.js 对每一条线执行两次拥有不同随机种子的微弯绘制,形成绝妙的素描手稿层次。
代价是什么:图元绘制的 Path 指令与点数量翻倍,SVG 文件体积会相应增大。
演进 04

粗犷可控:Roughness 自由度调节阀

前代痛点:有的场景需要严谨一点的架构草图,有的场景则需要极其奔放夸张的儿童涂鸦,写死的抖动参数无法自适应。
【局限:写死固定抖动】
固定抖动常量
要么太工整要么太杂乱无法调整
【突破:0 到 3 连续粗糙谱系】
r=0.5 r=2.5
从精致手绘到放飞自如随意微调
解决了什么:抽象出 `roughness` 参数(默认 1),线性控制噪声偏移量与线端延伸,丰俭由人。
代价是什么:如果将 roughness 开得过大(>3),矩形角点可能会发生大幅度错位分裂。
演进 05

告别油漆桶:Hachure 铅笔排线素描

前代痛点:手绘线条配上传统软件毫无生气的平涂纯色块,视觉上极其割裂突兀,就像给水彩画贴了一块塑料胶带。
【割裂:手绘画框包纯色块】
油漆桶平涂死黑破坏手稿意境
【突破:斜向交错排线填充】
模拟铅笔手涂排线留白呼吸感
解决了什么:独创 Hachure 阴影填充算法,用等间距且带随机粗糙度的斜向直线切片填充几何体。
代价是什么:复杂多边形的排线需要做大量射线相交(Ray Casting)裁剪运算。
演进 06

百变笔触:Zigzag、Cross-Hatch 与波点

前代痛点:只有单向平行排线还不够丰富,无法模拟蜡笔涂抹、草地阴影或密集深色阴影。
【单调:千篇一律单向线】
缺少纹理质感无法表达深度阴影
【突破:全套素描技法阵列】
之字形蜡笔与网格素描随心换
解决了什么:内置支持 zigzag(折线涂画)、cross-hatch(十字交叉排线)、dots(波点)等多种艺术风格。
代价是什么:密集十字排线会让小尺寸图形显得较为凌乱,需要微调 `hachureGap` 间隙。
演进 07

圆润之弧:多段贝塞尔拟合椭圆

前代痛点:直线的抖动容易做,但一个手绘圆圈怎么画?用一个大圆抖动看起来只是虚焦,完全不像人手画圆。
【失败:简单给半径加噪声】
看起来像毛刺齿轮
机械噪声直接套圆圈满是尖锐毛刺
【突破:四段交叠贝塞尔拟合】
环状微错位闭合宛如人手速绘
解决了什么:借鉴 Handy 算法,把圆和椭圆拆解为多段首尾轻微错开、曲率带微小漂移的贝塞尔曲线。
代价是什么:圆不再具备绝对对称的圆心,无法直接进行精确的同心几何相交判定。
演进 08

吞噬万物:任意 SVG 路径手绘化 (Path Parser)

前代痛点:如果只能画简单的矩形和圆形,无法将现成的复杂图标(如 AWS 云架构图、卡通头像)变成手绘风格。
【局限:只能画基础几何块】
遇到复杂外部图标直接举手投降
【突破:rc.path() 解析 SVG 语义】
将五角星与复杂图标秒变手绘风
解决了什么:移植 Mozilla 代码库中的 SVG 路径解析器,把标准 SVG `M/C/A/Z` 指令全量转化为手绘流。
代价是什么:极为复杂的超大矢量地图路径,转化和渲染时会消耗可观的 CPU 算力。
演进 09

双擎驱动:Canvas 极速像素 vs SVG 矢量保真

前代痛点:有的图形库只支持 Canvas(放大就模糊且无法单独绑定 DOM 事件),有的只支持 SVG(几千个节点卡成幻灯片)。
【偏科:死绑单一种渲染底层】
单引擎绑死
要么卡顿要么模糊无法自由选择
【突破:解耦渲染器,一套 API 双向通吃】
Canvas SVG DOM
白板实时交互用Canvas,保存高清用SVG
解决了什么:架构层将几何生成与绘图接口完全解耦,`rough.canvas()` 与 `rough.svg()` 共享同一套渲染算法。
代价是什么:开发者需要在项目中根据交互量级(海量元素选 Canvas,导出编辑选 SVG)做架构抉择。
演进 10

轻若鸿毛:<9kB 诞生即登神顶

前代痛点:很多图形渲染引擎动辄几兆大小,引入一个库导致网页首屏加载缓慢,根本无法成为轻量级组件的基础骨架。
【以前:重型引擎臃肿不堪】
> 3.5 MB
巨型依赖打包拖慢整站加载速度
【神作:极致精简赋能全球】
< 9 kB 孕育 Excalidraw 现象级应用
超轻量级体积点燃整个手绘白板生态
解决了什么:Gzip 压缩后不足 9kB,零外部冗余依赖,成为 Excalidraw、RoughViz 等上层爆款工具的心脏引擎。
代价是什么:保持极致轻量意味着它只专注绘图本分,碰撞检测、撤销重做等上层逻辑需要业务方自研。

⚖️ Rough.js 手绘美学三大数学天平

如何在完全失控的杂乱与完全死板的工业直角之间找到绝妙的艺术平衡点?

粗糙度 (Roughness)

颤抖的生命力:控制端点偏移与草图描摹的离散范围。太小变成冰冷矢量,太大变成混乱毛球,1.0 是人类手写笔记的黄金值。

弧度弯曲 (Bowing)

手腕重力学:控制直线向外微微隆起的曲率。模拟人类手臂画线时的向心加速度,让所有线条像拉满的弓弦一样充满张力。

留白与间隙 (Hachure Gap)

素描的呼吸感:控制平行排线的疏密间隔。告别毫无缝隙的色块平涂,让底层的纸张纹理透过来,营造出温暖的纸质手账感。

📋 传统 Canvas 原生绘制 vs Rough.js

核心绘制能力 传统原生 Canvas API Rough.js 手绘渲染引擎
📏 直线与边框 `ctx.lineTo()` 产生 100% 绝对平直直角,毫无视觉温度。 `rc.line()` 自动施加微弧度弯曲 + 双重草稿复线交织。
⭕ 椭圆与圆周 `ctx.arc()` 产生绝对完美的正圆,像用圆规切出的模具。 `rc.circle()` 多段微偏移贝塞尔拟合,首尾轻微错开的人手速写感。
🎨 区域填充色彩 `ctx.fill()` 油漆桶式纯色死黑平涂,封闭压抑。 `hachure`、`zigzag`、`cross-hatch` 素描斜线排线,透气留白。
📦 架构与体积 浏览器内置底层接口,极为繁琐,想做手绘必须人肉写几千行数学计算。 体积 <9kB,开箱即用,全面支持 Canvas 与 SVG 矢量双引擎。

🗺️ 官方权威核验与学习路线