告别直角锯齿:直角网格的审美审美疲劳
自 20 世纪 70 年代点阵屏幕诞生以来,像素化(Pixelation)一直是数字艺术最核心的视觉图腾。然而在现代高分辨率显示器上,单纯将照片降采样成低分辨率色块的「朴素马赛克」正面临严峻的审美局限:
- 生硬的 90° 直角阶梯折线:无论是人像面部边缘还是自然风景的起伏地平线,斜向轮廓都会被强行锯齿化,破坏主体的有机流动感;
- 平坦天空的割裂感:在平滑渐变的天空或雾气区域,相近颜色的方形格子互相堆叠,既缺乏宏观秩序,又显得廉价凌乱;
- 死板的黑色网格线:为了突出像素感,许多传统滤镜会机械地在每个方格边缘画一条黑线,导致整个画面被切碎成死气沉沉的方块矩阵。
在设计艺术探索中,台湾生成艺术创作者 William Liang(@william77.fun)提出过一种极具启发性的视觉语言:用程序将摄影作品转译为正交圆角世界——平坦的天空如同层层退晕的梯田等高线,边缘的转折全部带有温润的圆弧导角,细密的水面波光则如同密布微型焊盘与走线的硅基芯片。
为了在 质感工坊 (Texture Studio) 中忠实复刻这一极具先锋感的生成艺术质感,我们构建了 Capsule Pixel(胶囊像素) 引擎。该引擎完全由纯 Canvas 2D 驱动,无需外部依赖或复杂的向量几何库,实现了 100% 确定性的像素级生成效果。
图 1: Yan 胶囊像素引擎的四阶架构流程 — 从亮度色阶合并到网格 SDF 双向圆角倒角、双重波形浮雕反差描边与芯片同心微核生成。
视觉对照:朴素马赛克 vs 胶囊像素引擎
在深入公式细节之前,我们先在包含大片绿色草坡与戴斗笠旅人的高反差测试图上对比两种处理方式:
![]()
图 2: 高反差草坡人像裁切对比 — (01) 原始照片;(02) 传统直角马赛克,斗笠与草坡轮廓被生硬锯齿粉碎,天空被切碎成死板格子;(03) Yan 胶囊像素引擎,大平坦区融合成平滑导弧的多边形,人物剪影清晰锐利,细节呈现芯片走线般的机械美感。
仔细观察上图中间的 // 02: 朴素马赛克:
- 旅人的斗笠与人像剪影完全被锯齿淹没:生硬的直角阶梯割裂了人物轮廓,观者很难在第一时间辨别斗笠边缘的圆润曲线;
- 草地与天空充斥着无意义的高频方块接缝:原本和谐连贯的背景被分割成数百个刺眼的微小色块。
而在右侧的 // 03: 胶囊像素 中:
- 天空平缓的受光面自动消融了内部接缝,聚合为宏大的等高线阶梯;
- 每一个直角拐角无论是凸起还是凹陷,全部拥有精确的圆弧导角(
R ≈ 0.28 · cellSize); - 紧贴外层暗缝的内嵌亮白高光线,赋予全图一种仿佛在亚克力板上雕刻电路板的立体浮雕质感。
这一质感是如何通过仅 40 毫秒的 CPU 纯像素运算实现的?以下是完整的四阶段算法解析:
阶段 1:自适应色阶聚合与正交连通域合并
胶囊像素的第一步,是消除平坦区域里多余的格子边界。
我们将画面划分为边长为 cellSize(默认 14px,根据图像尺度通过 scaleFor 动态缩放)的正交网格阵列。在每个单元格内,计算平均 RGB 与感知明度:
L = 0.299R + 0.587G + 0.114B
然后,根据对比度参数将明度量化为离散的等高色阶 Q(通常划分为 8 到 16 档):
Q = round((L / 255) × Levels)
当相邻网格单元的量化色阶 Q 相等时,它们在拓扑上被视为属于同一个连通多边形(Polyomino)。这意味着:它们之间的交界线将被彻底抹除。
// 1. 网格单元颜色均值与色阶量化
const gridR = new Float32Array(gw * gh)
const gridG = new Float32Array(gw * gh)
const gridB = new Float32Array(gw * gh)
const gridQ = new Int32Array(gw * gh)
for (let gy = 0; gy < gh; gy++) {
const y0 = gy * cellSize, y1 = Math.min(h, y0 + cellSize)
for (let gx = 0; gx < gw; gx++) {
const x0 = gx * cellSize, x1 = Math.min(w, x0 + cellSize)
let rSum = 0, gSum = 0, bSum = 0, count = 0
for (let y = y0; y < y1; y++) {
const row = y * w * 4
for (let x = x0; x < x1; x++) {
const idx = row + x * 4
rSum += data[idx] ?? 0
gSum += data[idx + 1] ?? 0
bSum += data[idx + 2] ?? 0
count++
}
}
const gIdx = gy * gw + gx
const r = rSum / count, g = gSum / count, b = bSum / count
gridR[gIdx] = r; gridG[gIdx] = g; gridB[gIdx] = b
const lum = 0.299 * r + 0.587 * g + 0.114 * b
gridQ[gIdx] = Math.round((lum / 255) * levels)
}
}
这一机制让开阔的天空、湖面与大块阴影自动聚合成宏伟的多边形,保留下来的边界则完美勾勒出画面最关键的明暗转折线。
阶段 2:网格有向距离场(Grid SDF)与双向圆角倒角
如果仅仅合并单元格,得到的依然是一堆边缘带有硬折角的多边形。胶囊像素的灵魂在于对每一个直角进行精密的圆角导弧(Filleting)。
传统 CAD 或矢量图形系统做倒角需要提取轮廓线段并计算圆弧求交,这在单帧几十万像素的实时图像处理中是无法接受的。胶囊像素通过网格拓扑有向距离场(Grid SDF)在单像素扫描中以 O(1) 常数时间完成倒角计算:
设当前像素所在单元格为 (g_x, g_y),格内局部相对坐标为 (u, v) ∈ [0, cellSize-1]^2。我们检查其 8 个正交与对角邻居的色阶是否与当前格不同:diffT, diffB, diffL, diffR, 以及 diffTL, diffTR, diffBL, diffBR。
1. 直边距离
若某条边是区域边界(例如顶部上方邻居色阶不同 diffT == true),则当前点到该直边的垂直距离为 u。
2. 外凸角圆角(Convex Fillet)
当两个正交相邻边均为边界(例如上方与左侧均不同 diffT && diffL),在左上角区域(u < R 且 v < R),直角需要被外凸圆弧切去:
从圆弧圆心 (R, R) 到当前像素的几何距离为 sqrt((R-u)^2 + (R-v)^2)。
因此,当前像素到外凸圆角边界的真实距离更新为:
d = R - sqrt((R - u)^2 + (R - v)^2)
3. 内凹角圆角(Concave Fillet)
当上方和左侧与当前格相同(!diffT && !diffL),但对角线左上方不同(diffTL == true)时,这里形成了一个内凹拐弯角!
内凹圆角的圆心恰好在拐角顶点 (0, 0),当前像素到该拐角边界的距离为:
d = min(d, sqrt(u^2 + v^2))
// 圆角半径设定为格子宽度的 28%
const R = cellSize * 0.28
let d = cellSize * 2 // 初始化为足够大
// 直边距离
if (diffT && u < d) d = u
if (diffB && (cellSize - 1 - u) < d) d = cellSize - 1 - u
if (diffL && v < d) d = v
if (diffR && (cellSize - 1 - v) < d) d = cellSize - 1 - v
// 外凸角倒角
if (diffT && diffL && u < R && v < R) d = R - Math.hypot(R - u, R - v)
if (diffT && diffR && u < R && (cellSize - 1 - v) < R) d = R - Math.hypot(R - u, R - (cellSize - 1 - v))
if (diffB && diffL && (cellSize - 1 - u) < R && v < R) d = R - Math.hypot(R - (cellSize - 1 - u), R - v)
if (diffB && diffR && (cellSize - 1 - u) < R && (cellSize - 1 - v) < R) d = R - Math.hypot(R - (cellSize - 1 - u), R - (cellSize - 1 - v))
// 内凹角倒角
if (!diffT && !diffL && diffTL) d = Math.min(d, Math.hypot(u, v))
if (!diffT && !diffR && diffTR) d = Math.min(d, Math.hypot(u, cellSize - 1 - v))
if (!diffB && !diffL && diffBL) d = Math.min(d, Math.hypot(cellSize - 1 - u, v))
if (!diffB && !diffR && diffBR) d = Math.min(d, Math.hypot(cellSize - 1 - u, cellSize - 1 - v))
这套纯几何闭式解无需任何条件分支跳转或复杂迭代,瞬间赋予了所有多边形如同液体胶囊般的平滑外轮廓。
阶段 3:双重反差波形浮雕描边(Dual-Stroke Relief)
在物理世界中,雕版印刷或蚀刻电路板的边缘不会是一条生硬平涂的单色线条,而是一道具有立体起伏的波形响应:
胶囊像素借鉴了 拉普拉斯高斯算子(Laplacian of Gaussian, LoG) 的墨西哥帽波形分布,在距离边界不同 d 处叠加两重反差描边:
- 外侧暗色缝隙(Dark Seam):在紧贴边界的微小带状区域(
d < 1.15 · kSize),将原本颜色压暗 80% 以上,模拟出正交拼版交界处的物理凹槽与阴影缝; - 紧贴内侧的高光白线(White Highlight Stroke):在距离边缘约
1.3 \sim 1.8像素的位置(d ≈ strokeW),以高斯铃形曲线注入一根高亮纯白线条; - 对比度自适应衰减:描边强度与交界两侧相邻格子的实际色彩欧氏距离
\ΔC成正比。黑塔与天空等高反差边缘呈现饱和的黑白硬边,而天空内部平缓的等高阶梯则自动弱化为半透明微弱暗纹,不会破坏大局平整度。
const kSize = cellSize / 14
const contrastK = Math.max(0.18, Math.min(1.0, (edgeDelta / 75.0) * contrastBoost))
// 1. 外层暗缝 (Seam)
const seamW = Math.max(0.75, 1.15 * kSize)
const seamAlpha = Math.max(0, Math.min(1, seamW - d)) * 0.85 * contrastK
r *= (1 - seamAlpha); g *= (1 - seamAlpha); b *= (1 - seamAlpha)
// 2. 内层亮白高光线 (Stroke)
const strokeW = Math.max(1.3, cellSize * 0.13)
const varStroke = Math.max(0.45, 0.75 * kSize * kSize)
const strokeAlpha = Math.exp(-Math.pow(d - strokeW, 2) / varStroke) * (0.28 + 0.68 * contrastK)
r = r * (1 - strokeAlpha) + 255 * strokeAlpha
g = g * (1 - strokeAlpha) + 255 * strokeAlpha
b = b * (1 - strokeAlpha) + 255 * strokeAlpha
阶段 4:同心内嵌环与芯片微核点阵(Micro Core Nuclei)
在水面反光、树林倒影等高频碎纹区域,网格单元无法与邻居合并,退化为单个独立的 1× 1 方块。如果只是单纯填色,这些小方格会显得扁平单调。
我们为它们赋予了类似芯片焊盘与同心回纹的二级结构:
- 二级同心内环(Secondary Inset Ring):在
d ≈ 0.28 · cellSize处施加第二道柔和暗环,让孤立小格子看起来像带有一层深色内衬; - 中心微核点阵(Micro Core Nucleus):在每个单元格的几何中心点
((cellSize-1)/2, (cellSize-1)/2)植入一颗高斯核点。孤立单元格呈现高亮的白色芯片核,而已合并的大块区域内部则保留暗色微核,巧妙暗示出底层正交晶格的存在。
// 次级内环
const ringW = Math.max(2.8, cellSize * 0.28)
const varRing = Math.max(0.55, 0.8 * kSize * kSize)
const ringAlpha = Math.exp(-Math.pow(d - ringW, 2) / varRing) * 0.35 * contrastK
r *= (1 - ringAlpha); g *= (1 - ringAlpha); b *= (1 - ringAlpha)
// 芯片微核
const distCenter = Math.hypot(u - (cellSize - 1) / 2, v - (cellSize - 1) / 2)
const isIsolated = diffT && diffB && diffL && diffR
const microWeight = isIsolated ? 0.55 : 0.22
const coreR = Math.max(1.0, 1.8 * kSize)
const microAlpha = Math.exp(-Math.pow(distCenter, 2) / (coreR * coreR)) * microWeight * contrastK
r = r * (1 - microAlpha) + (isIsolated ? 255 : 0) * microAlpha
g = g * (1 - microAlpha) + (isIsolated ? 255 : 0) * microAlpha
b = b * (1 - microAlpha) + (isIsolated ? 255 : 0) * microAlpha
横向对比:四种离散栅格几何渲染对比
| 维度 | 传统像素画 (Pixel Art) | 沃罗诺伊马赛克 (Voronoi) | 半调网点 (Halftone) | 胶囊像素 (Capsule Pixel) |
|---|---|---|---|---|
| 空间拓扑 | 刚性笛卡尔矩形方阵 | 不规则泰森多边形凸网格 | 旋转角度 CMYK 点阵 | 自适应正交连通多边形 (Polyomino) |
| 拐角几何 | 锐利 90° 直角锯齿 | 任意钝角 / 锐角多边形折线 | 连续圆形 / 椭圆点阵 | 内外双向 SDF 平滑圆弧导角 (R=0.28s) |
| 色阶处理 | 简单均值或调色板对齐 | 单胞内单色平涂 | 网点面积调制灰度 | 自适应色阶聚合,平坦区消融接缝 |
| 轮廓浮雕 | 无,或单像素黑描边 | 几何单元交界单色缝隙 | 无轮廓(由网点密度感知) | 墨西哥帽式双重反差浮雕(暗缝+高光) |
| 微观层级 | 扁平实心填充 | 单胞纯色 | 实心墨滴 | 二级同心内环 + 芯片焊盘微核 |
| 运行开销 | 单次降采样插值 (<5ms) | Delaunay 三角剖分 (O(N\log N)) | 光栅扫描与余弦计算 (~20ms) | 局部网格拓扑查找 (O(1) 常数时间,~38ms) |
在线体验与应用场景
胶囊像素现已作为第 31 款独立滤镜入驻 质感工坊 (Texture Studio)。
推荐尝试的摄影题材
- 海平面与建筑剪影:长平线与直立灯塔/大桥在胶囊像素下会化身为极具建筑美感的几何阶田;
- 赛博朋克夜景与车流:霓虹光晕与高反差街道会被转译为发光的印刷电路板(PCB Trace);
- 山峦云海与等高线:层叠的山脉走势会自动显现出地理测绘等高线地图的秩序与韵律。
调参建议
- 细节粒度 (Detail):想要清晰的芯片电路板走线与微核,建议设在 50~70;想要大尺度的复古极简抽象色块,拉低至 30~45;
- 对比度 (Contrast):如果画面光线较平缓,适当拉高对比度可以强行激发更多的等高线层级;
- 滤镜强度 (Intensity):在 80%~100% 呈现硬核的生成艺术风貌,而在 50%~60% 则能让原图的写实影调与胶囊晶格产生奇妙的双重曝光。
在电脑浏览器中,你可以免注册直接拖入任意照片进行实时调优并导出全分辨率 PNG;而在 Yan 手机端 App 中,该算法已针对移动 GPU 与 CPU 预烘表进行极限优化,支持在相机取景框内以 60fps 实时预览,随手捕捉充满几何美感的赛博胶囊世界。