雕刻式绘画引擎:从颜色容器到表面几何
2026/9/24 5:04:05 网站建设 项目流程

前几天我在 Hacker News 的 Show HN 板块看到一个绘画引擎项目,标题写着“A painting engine that lets you sculpt drawings”。第一眼看上去,这像又一个数字绘画作品集,但真正引发我思考的是标题里的动词:sculpt,不是 paint,不是 edit,而是雕刻。对经常做图形、前端或者创意工具开发的人来说,这个措辞值得停下来想一想。如果它只是一个带橡皮擦、笔刷叠加效果的绘画应用,完全没有必要用“雕刻”这个词;既然把绘图过程描述为雕刻,说明它的核心交互不是往画布上铺颜色,而是对一个“有表面、有厚度、可塑形”的对象做减法式修改。

我只凭项目标题无法断言它内部用的是体素、深度场还是网格变形,也不清楚它到底跑在浏览器还是桌面端,因为全文可用的公开材料有限。但这类“雕刻式绘画引擎”是一个很值得展开的技术主题。它背后牵扯到画布的数据表示、笔刷语义、法线计算、光照反馈,以及实时渲染选型,这些内容恰恰是很多从传统绘图工具转过来的开发者容易忽略的地方。读完这篇文章,你会理解 painting engine 这一类技术演示真正要解决的问题是什么,知道如何判断一个绘画引擎是“换了交互外壳”还是“换了数据内核”,并且我会用一个可在本地直接运行的 HTML 原型,把雕刻式绘画的最小实现讲清楚。

1. 一个绘画引擎的“雕刻”到底意味着什么

传统的数字绘画工具,比如 Photoshop、Procreate、Krita,它们的底层模型高度一致:画布本质上是一个二维像素数组,用户用各种笔刷往数组里写入颜色,橡皮擦则把颜色恢复成透明或背景色。无论笔刷效果做得多复杂,这个循环都没有变。也就是说,传统绘图工具的“画布”只是颜色的容器,创作者的每次操作最终都在改变一个平面上的颜色分布。这是一个非常简单、稳定,同时也非常平面的模型。

雕刻式绘画不同的地方在于,它把画布从“颜色容器”升维成了“表面几何”。用户画的不是颜色,而是决定某个局部是凸起还是凹陷、是尖锐还是平滑、是迎着光还是背光。这更像雕塑家面对一块黏土:他不是在每个位置刷上颜色,而是在不断改变材料的形状。换句话说,雕刻式绘画要给画布增加一个传统位图没有的维度——高度、厚度、密度或者到表面最近边界的距离。没有这个额外维度,所有视觉上的“雕刻感”都只是骗眼睛的滤镜。

这个区别很容易被低估。很多人看到雕刻式绘画演示,第一反应是“这不就是给笔刷加一个立体阴影效果吗”,但真正的改动发生在更底层。要让用户感觉自己在雕刻,系统必须做到三件事:第一,每个笔触都要真实影响到表面状态的数值,而不是只影响最终显示的像素;第二,无操作时表面应该保持可感知的体积和光照,而不是一块平坦位图;第三,操作要有连续的物理感——向下压和向上拉应该作用于同一个表面属性,并且效果是平滑、可累积的。这三点是交互层、数据层和渲染层同时要配合完成的,不是一个后处理滤镜能替代的。

所以我在评价这类 Show HN 技术演示时,会先问一个特别基础的问题:它的笔刷是知识改变了像素颜色,还是改变了驱动像素颜色的表面数据?如果改变的是后者,它才配叫“sculpt”;如果只是给位图画笔加了一个方向性的光照贴图,那它仍然是一个传统绘画工具。

2. painting engine 与传统绘图工具的本质差别

很多 CSDN 读者对 3D 建模软件更熟悉,但对“引擎(engine)”这个词出现在绘图工具里会有一些困惑。按我个人的理解,painting engine 并不是只指某个绘画 App 的代号,而是指一条完整的处理链路:输入事件、笔刷模型、画布数据更新、实时渲染、撤销重做、画布导出。传统的数字绘画软件也包含这条链路,但它最重要的数据是颜色。当代绘画 App 的笔刷之所以能模拟水彩、油画、喷枪,靠的是在颜色合成规则上做文章,比如更复杂的透明度计算、颜色扩散、纹理扰动。

雕刻式绘画引擎要共享这条链路,却把中间最关键的一环换掉了。传统绘图链路中,画布数据是一个二维颜色数组,渲染时直接把颜色贴到屏幕上。雕刻式绘画链路中,画布数据至少包含一个高度信息数组,渲染时必须先根据高度估算表面的法线方向,再去计算光照,最后把光照结果映射成屏幕上的像素。读者可以把传统的绘图引擎理解为:数据是 RGBA,渲染是显示 RGBA。而雕刻式绘画引擎是:数据里有一个值是深度或者高度,渲染时还要加上一个表面着色过程。

这里可以做一个更具体的对比。假设用户用普通绘画工具在画布上画一条竖线,最终画布 RGB 数组里多了一组深色像素;假设同一个动作发生在雕刻式绘画引擎里,会发生变化的是表面高度场数组。随后,渲染器看到这一段表面变低了,于是计算这一段的法线偏转,让光照结果在这段表面形成凹陷阴影。对用户来说,他体会到的差异不仅仅是“这里有一道深色笔触”,而是“这里像是真的被刻进去了一道”。

另外,传统绘图工具和雕刻式绘画在操作语义上也完全相反。传统绘画的核心是添加颜色,颜色一旦铺上去就基本不可逆,所以软件需要反复提醒用户存盘、建图层。雕刻的本质则是对已有材料做减法,创作者首先面对的是一个完整、均匀、可用的表面,他通过去除材料来生成图形;在某些雕刻工具里也可以做加料操作,但减法语义始终是核心。因此,一个好的雕刻式绘画引擎,用户会非常依赖撤销栈和材料恢复能力,这在产品设计上和普通绘画工具并不一样。

我始终觉得,看透这个区别是理解这一类绘画引擎的关键:传统绘图是“在一个平面上安排颜色”,雕刻式绘图是“在一个表面上重塑几何”。前者是一个图像处理问题,后者是一个图形学加空间交互的问题。

3. 雕刻式绘制背后的数据模型与实现路径

既然雕刻式绘画需要超出颜色的维度,那接下来的问题就是:这个超出颜色维度的“表面”用什么数据结构表达?这是任何绘画引擎开发者都要做的第一个架构决定。虽然我不知道具体项目的内部实现,但常见的技术路径基本可以归纳为四条。

第一,2.5D 高度场方案。它用一个二维数组保存高度值,每个画布像素只有一维额外信息,表示该点的表面高度。渲染时可以用高度场生成法线,并配合光照产生浮雕感。这个方案的优点是内存占用小、实现简单、用 GPU 处理非常高效;缺点是只能表现一个从固定方向看过去的表面,无法真正确切地雕刻出类似水滴、悬垂这类需要三维结构的形状。对于“sculpt drawings”这个场景来说,高度场其实非常合适,因为 drawings 本身有强烈的正面观看习惯。

第二,体素方案。把画布想象成一个三维网格,每个小格子记录一个密度、占据或者颜色值,笔刷在体素空间里作布尔运算,最后通过行进立方体等算法提取表面并渲染。体素的表达能力最强,可以做出真实的三维凹陷、洞穴、悬垂,缺点是内存占用和算法复杂度高,而且很难在普通浏览器里做到较高分辨率下的流畅实时雕刻。如果做出来的 demo 覆盖的是厚实雕塑感,体素是常见选择。

第三,SDF 符号距离场方案。每个画布位置距离最近表面边界的带符号距离,SDF 天然擅长表达光滑合并、倒角,也很适合做抗锯齿和光线步进渲染。用 SDF 的绘画引擎更像是在“雕刻一个隐式几何体”,用户操作可以被数学化为对距离场的加加减减。它的优势是渲染质量高、合并形状方便,劣势是实时编辑的算法实现门槛较高,而且在大尺寸画布上做逐像素距离场更新,对性能优化要求高。

第四,网格方案。直接在一个三角网格上做逐顶点位移或者局部细分,雕刻笔触会移动顶点位置。这是 Blender、ZBrush 等专业三维雕刻软件熟悉的模型,表达能力也最强,但要做成绘画引擎,需要考虑用户用笔在屏幕上划过时如何近似映射到网格局部,以及笔触经过后的撤销和细分策略。它的工程复杂度通常更高。

这四条路径的取舍并不简单。用一张表可以把它们在雕刻式绘画引擎中的表现放在一起看:

实现路径数据维度适合场景主要优势主要瓶颈
高度场2D 数组持有高度平面浮雕、印章、贴图开销小,思路清晰无法表现真实悬垂结构
体素3D 网格持有密度厚实立体雕塑可表达任意三维形状内存和实时性能压力大
SDF二维/三维距离场隐式几何、光滑合并渲染质量高、天然抗锯齿编辑算法和性能优化复杂
网格三角网格持有坐标高精度雕刻表达力最强笔触映射和细分实现复杂

从绘画引擎这个角度考虑,用户手感和最终渲染效果之间隔着完整的一条数据链路。无论选哪条路径,最终都要面对如何在数据更新后快速重算法线、重绘画面,以及如何让用户的操作自然映射到数据结构上的问题。如果你在分析一个技术演示,看到它的可视化风格很细腻,也可以先不急着惊叹,而是去猜它更可能采用哪种数据方案,这会帮助你对它的能力边界有一个非常快的判断。

4. 如何评价一个绘画引擎类 tech demo

Show HN 上每天都会冒出来各种创意工具 demo,对开发者来说,真正高效的做法是先在脑内跑一遍审读清单,再决定要不要花时间深入。针对绘画引擎类技术演示,我会关注六个维度。

第一,demo 是否允许你直接交互?如果只有一段视频、几张效果图,那么它的体验闭环很可能还没打通,或者说创作者并不愿意暴露操作延迟。真正好的交互类 demo,一定要让访问者亲手画几笔,亲自感受笔刷的响应。

第二,每一次操作是否只修改了像素颜色?你可以故意用一个很钝的笔刷在不断叠加的区域来回涂抹,观察它是否产生了类似雕塑的连续变形。如果它只是用一个方向性纹理压在颜色上,很快会出现重复感和颜色堆叠,真正的雕刻引擎会表现出渐进的深度变化。

第三,它有没有为“误操作”留出后路?绘画工具很难没有撤销、重置、清空画布这些基础能力。技术演示可以只做单一刷新键,但至少要把重置提供出来,否则用户不太愿意实验非可逆操作。

第四,实时性是否撑得起创作节奏?绘画操作必须有低延迟反馈。如果一个 600x600 的画布移动笔刷时都要等待几百毫秒,那它在交互范式上再新颖,也暂时离可用很远。对技术演示而言,能让用户以 30 帧以上完成基础笔触,是值得往下看的基线。

第五,项目的工程透明度和许可证。开源吗?GitHub 仓库是否存在?README 是否解释了原理和构建方式?许可证清晰吗?如果一个 demo 只提供了在线页面,但没有任何技术说明,那我们评估它的时候只能停留在黑盒层面,能获取的信息就非常有限。

第六,输出结果能不能进入创作流程?绘画引擎通常需要支撑导出到 PNG、SVG、法线贴图甚至 OBJ。就算只是 demo,如果它的输出不能被保存,那么用户很难在真实项目中检验这个工具的价值。

这个审读清单的重点不是“挑刺”,而是帮你快速判断它是不是一个真正的 painting engine。如果它只做了颜色层的视觉效果,那么它大概率属于 shader toy,是一个视觉实验;如果它真的允许你改变表面数据,那么它就是在挑战绘画工具的底层模型,这就更有研究价值。

5. 最小示例:用一份 HTML 实现可雕刻的深度画布

如果想亲手体会“雕刻式绘画”的数据模型,不必急着去读大项目源码。下面这份代码是一个可以直接在浏览器运行的最小原型,它只用一个高度场数组存储画布内容,然后用非常朴素的光照算法把高度数据渲染成灰度表面。拖动鼠标可以让表面下压,按住 Shift 拖动可以让表面向上隆起。这个原型没有依赖任何第三方库,存成一个 HTML 文件即可打开。

5.1 代码思路

完整的 HTML 文件如下。理解它的关键在于:heights 数组才是画布的唯一事实来源,而每次渲染都是从高度场重新生成图像数据,这比传统绘画引擎中直接保存 RGBA 像素多了一个中间步骤。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>Sculpt Drawing Engine - 最小原型</title> <style> html, body { margin: 0; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #151515; color: #ccc; font-family: system-ui, sans-serif; } canvas { background: #333; touch-action: none; cursor: crosshair; } .hint { margin-top: 12px; font-size: 14px; } </style> </head> <body> <canvas id="c" width="320" height="320"></canvas> <div class="hint">拖动鼠标:下压雕刻;按住 Shift 拖动:向上隆起</div> <script> const SIZE = 320; const canvas = document.getElementById('c'); const ctx = canvas.getContext('2d'); const heights = new Float32Array(SIZE * SIZE); for (let i = 0; i < heights.length; i++) { heights[i] = 0.5; } const LIGHT = { x: -0.45, y: -0.6, z: 0.66 }; function render() { const image = ctx.createImageData(SIZE, SIZE); const px = image.data; const lenL = Math.hypot(LIGHT.x, LIGHT.y, LIGHT.z); const lx = LIGHT.x / lenL; const ly = LIGHT.y / lenL; const lz = LIGHT.z / lenL; for (let y = 0; y < SIZE; y++) { for (let x = 0; x < SIZE; x++) { const idx = y * SIZE + x; const left = heights[y * SIZE + Math.max(x - 1, 0)]; const right = heights[y * SIZE + Math.min(x + 1, SIZE - 1)]; const up = heights[Math.max(y - 1, 0) * SIZE + x]; const down = heights[Math.min(y + 1, SIZE - 1) * SIZE + x]; const h = heights[idx]; // 用有限差分估算表面法线,并放大 height 差异带来的法线偏转 const scale = 24; const nx = (left - right) * scale; const ny = (up - down) * scale; const nz = 1; const nl = Math.hypot(nx, ny, nz); const diffuse = Math.max(0, (nx * lx + ny * ly + nz * lz) / nl); const shade = 0.18 + 0.82 * diffuse; const albedo = 110 + Math.round((h - 0.5) * 140); const brightness = Math.min(255, Math.max(0, Math.round(albedo * shade))); const out = idx * 4; px[out] = brightness; px[out + 1] = brightness; px[out + 2] = brightness; px[out + 3] = 255; } } ctx.putImageData(image, 0, 0); } function sculpt(cx, cy, radius, strength) { const r2 = radius * radius; const x0 = Math.max(0, Math.floor(cx - radius)); const x1 = Math.min(SIZE - 1, Math.ceil(cx + radius)); const y0 = Math.max(0, Math.floor(cy - radius)); const y1 = Math.min(SIZE - 1, Math.ceil(cy + radius)); for (let y = y0; y <= y1; y++) { for (let x = x0; x <= x1; x++) { const dx = x - cx; const dy = y - cy; const

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询