1. 从"越画越没劲"到动手自建 artcraft:这件事的起点
先说一下背景。过去两年我一直在一家小型工作室做数字插画和动态视觉设计,日常产出以扁平风格插画、产品宣传图和简单的逐帧动画为主。工作本身不算枯燥,但时间一长,我发现自己陷入了一个很拧巴的状态:软件越用越顺,画面却越来越"平"。
这里的"平"不是指构图或透视,而是指质感。用数位板画画的人应该都有类似的体验——在软件里选一个笔刷,调一个颜色,落笔就是一块干干净净的色块。它太精确了,精确到没有任何意外,而恰恰是那些意外,那些纸张纹理、颜料堆积、边缘洇开的小瑕疵,才是手绘最动人的部分。以前我总以为多找几个笔刷预设就能解决,后来发现预设再多也只是换汤不换药,因为问题不在笔刷,在于整个创作流程的底层逻辑。
所以我决定自己动手做一个辅助创作的工具集,名字就叫 artcraft——"艺术"和"工艺"合在一起,意思是把传统手工创作的思维方式,重新带回数字流程里。这个项目不是一个现成的软件,也没有图形界面,它更像一套围绕"模拟手工感"而设计的半自动流程:包含若干脚本、PS 动作、色彩映射表和一套图片处理管线,用来给数字插画叠加纸质基底、颜料颗粒、笔触纹理和可控的随机痕迹。
这篇文章就把整个搭建过程、核心思路和踩过的坑完整记录下来。适合两类人看:一类是和我一样被"数字感太强"困扰的插画师,想知道怎么通过技术手段给作品"做旧";另一类是对图像处理感兴趣,想看看一个非科班出身的人是怎么把 OpenCV、图像混合、噪点算法这些东西组合成一套真正能用的工作流的。
先说结论:artcraft 做完之后,我接的私单里有一半左右会用这套流程收尾,甲方反馈最多的一句话是"画面看起来有手工的味道"。但它不是一键滤镜,更不是那种套上就完事的艺术效果包。它需要你在几个关键节点做出取舍,而这套取舍的逻辑,才是本项目真正值得复制的部分。
2. 数字作品为什么"假":问题本质出在信息密度和随机性上
在动手写第一行代码之前,我花了大概两周时间研究一个问题:手绘作品和数字作品之间的感知差异,到底是由什么造成的?
网上的主流解释是"笔刷颗粒感不够"或者"边缘太干净",但我觉得这些只是表象。真正拉开差距的有两个底层因素。
第一个是信息密度。传统纸张上的每一寸表面都不是均匀的。哪怕是看起来纯白的素描纸,放大到微观层面也有纤维起伏、杂质颗粒和微弱的反光差异。数字画布则是一个完美的均匀平面,RGB 值处处一致。人的视觉系统虽然不会逐像素分析,但对"表面信息太少"这件事异常敏感——画面会因此显得生硬、呆板、缺乏层次。你可以做一个简单实验:在 PS 里新建一块纯白色画布,再在旁边放一张真实的白色卡纸照片,大多数人一眼就能区分哪个是纸哪个是屏幕,原因就是这个信息密度差异。
第二个是随机性的质量。手工绘画过程中,颜料的干湿程度、笔压的变化、纸张的吸收速度,这些因素叠加起来产生的是"有相关性的随机"——相邻区域的纹理总是相似的,但具体每个位置长什么样又无法预测。而数字工具默认生成的往往是非黑即白的规则变化,或者反过来,是完全均匀的白噪声。前者太有规律,后者又太杂乱无章,都不像真实材质。
说白了,数字绘画"假"的根源,是画面里除了创作者主动画上去的内容之外,没有任何"背景噪音"。而真实世界的视觉经验告诉我们,所有物体表面都存在噪音。
明白了这一点之后,artcraft 的整体设计思路就清晰了:它不是要去修改创作者画的线条和色块,而是在画面完成之后,往里面注入多层、有尺度差异、带关联性的细节信息。换句话说,我不去动"画的内容",只去动"画的表面"。
这条思路我后来验证了很多次,每次都能立住。哪怕只是给画面加一层非常微弱、肉眼基本看不出差别的纸纹叠加层,作品给人的整体感知也会有微妙变化,甲方说不清哪里变了,但就会觉得"比之前舒服"。
3. 搭建 artcraft 的主干工作流:选型逻辑与每个环节的具体参数
确定了要往画面里"注入细节"之后,接下来的问题就是怎么注入。我调研了市面上的现成插件和滤镜,试了不下十种方案,最后得出一个判断:现成工具没有一套能同时满足三个需求的——多层纹理叠加、可控随机、批量处理。要么是效果太重,一上来就把画面糊成老照片;要么是参数太死,调来调去只有那几种固定效果。所以最终决定自己写,用最基础的图像处理库组合出完整管线。
3.1 技术选型:为什么用 Python 而不是直接堆 PS 动作
Artcraft 的核心工具我用 Python 写,图像处理部分依赖 OpenCV 和 NumPy,个别需要精细分色的场景会切到 Pillow 做 RGB 通道操作。不用 PS 动作纯粹是因为批量处理的自由度不够,动作记录的每一步都是固定的,想做到"每张图生成的纹理都不同但风格一致",必须靠代码里的随机种子来控制。
整个处理管线分四层跑,我称之为"四层复写",对应四种不同尺度的细节注入。
第一层是基底纹理层,模拟纸张或画布的纤维结构。实现方法是生成一张高频噪声图,然后用高斯模糊按不同半径做两次处理,再把这两张图按一定权重叠加,这样出来的就不是普通的随机噪点,而是有"团块感"的纤维纹理。参数上,论文里常见的中值滤波加 NumPy 生成 Perlin 噪声的组合实测效果最稳定,我最后用的是 128×128 的基础噪声平铺放大到画布尺寸,而不是直接生成长宽一致的噪声图,这样做的好处是避免了大尺寸图中常见的网格状伪影。
第二层是颗粒沉积层,用来模拟颜料干涸后留下的颗粒感。这层用的算法是阈值抖动加位移。做法是把画面分成亮部和暗部,在高光区域叠加少量白色微粒,阴影区域让颗粒密度随亮度下降而上升——这个逻辑来源于一个很朴素的观察:颜料在深色区域干涸后更容易显出粉质颗粒。参数上我习惯把颗粒半径控制在 1~3 像素,不可太大,大了就像加了噪点滤镜,会破坏原有画面的边缘。
第三层是笔触方向扰动层,针对的是画面中的边缘和轮廓。手绘和数字绘制的边缘差异在于,手绘边缘永远不是一条绝对平滑的线。哪怕是刻意画得很直的长线,放大后也能看到微小的抖动。这层我用的算法是对图像边缘做轻微位移映射(displacement mapping),位移量来自一张按"主方向角度"生成的梯度噪声图,幅度控制在 1~2 像素,肉眼几乎无感,但边缘会从"绝对清晰"变成"微微发颤",这正是手工线稿的气质。
第四层是整体色调微偏层,模拟不同纸张在不同光照下的色偏。纸张从来不是纯白的,扫描仪和相机拍出来的手绘稿也都带着各自的色温偏差。这一层我用的方法比较简单,就是给高光和阴影分别做轻微的曲线偏移,高光向暖色偏 2~3 个色阶,阴影向冷色偏 1~2 个色阶。这个量级单独看没有任何感觉,但叠加在前面三层之后,画面的"数字味"会被压掉一大半。
3.2 灰度混合模式的选择:为什么最终选的是柔光而不是叠加
四层纹理生成完之后,最核心的问题是怎么把它们合到原图上。这一步我在 PS 里手动试验了两个礼拜,把十几张不同类型的插画反复套各种混合模式,最后得出的结论是:柔光(Soft Light)模式是普适性最好的基底,叠加(Overlay)模式只适合特定风格。
柔光的原理是对比度微调,它对中间调的影响小,对高光和阴影的修饰是渐进的,因此适合做基底纹理和色调微偏层。叠加模式相比之下对比度提升明显,用在颗粒沉积层上效果不错,但一旦用在基底纹理层,画面的暗部会明显发闷,对日系平涂、淡彩水彩这类亮度偏高的插画非常不友好。
具体到混合公式,OpenCV 里没有直接的柔光实现,需要自己写:
def soft_light(background, foreground): # 输入为归一化到 [0,1] 的 float 数组 result = np.zeros_like(background) # foreground <= 0.5 的区间 mask1 = foreground <= 0.5 result[mask1] = background[mask1] * (2 * foreground[mask1] - 1) + 2 * background[mask1] ** 2 * (1 - foreground[mask1]) # foreground > 0.5 的区间 result[~mask1] = 2 * background[~mask1] * (1 - foreground[~mask1]) + background[~mask1] ** 0.5 * (2 * foreground[~mask1] - 1) return np.clip(result, 0, 1)这段代码对应的是菲涅尔近似的 PS 柔光实现,实测下来和 PS 原版柔光几乎没有肉眼可见差异。为什么我要强调自己写而不是直接调用库函数?因为 PS 动作里的柔光混合会受 16 位/8 位色彩模式影响,一旦某个环节忘记转色彩模式,最终输出会出现非常难查的色带断层。自己写混合函数,整个流程的色彩空间就是完全可控的。
3.3 完整管线的运行顺序与参数表
把四层纹理生成的代码和柔光混合函数组合起来,一个最基础的 artcraft 管线就通了。我习惯的调用顺序是:先跑基底纹理层,再跑颗粒沉积层,然后做笔触扰动,最后做色调微偏。这个顺序不能乱,原因在于每一层都在"修正"上一层的痕迹:如果先做色调偏,再叠加基底纹理,色调偏就会被纹理层的混合稀释掉,最终效果会大打折扣。
参数方面,我把自己常用的默认值做成了一份表,方便不同作品的风格统一。这不是说每张图都用同一组参数,而是先跑一组基准,再根据画面特点微调个别项。
| 参数项 | 默认值 | 作用范围 | 调参方向 |
|---|---|---|---|
| 基底纹理强度 | 12% | 全局 | 想要纸感更明显就加大到 20%,但注意超过 18% 后暗部会发灰 |
| 颗粒沉积强度 | 8% | 全局 | 水彩类插画建议降到 5%,丙烯、油画类可加到 12% |
| 颗粒半径 | 2px | 全局 | 4K 以上大图建议提到 3px,否则颗粒会显得过密 |
| 边缘扰动幅度 | 1.2px | 边缘区域 | 写生线稿可以忍受到 2px,产品图必须低于 0.8px |
| 高光色偏 | +3 暖色阶 | 高光区 | 暖光场景可加到 +5,冷色调画面改为 -2 |
| 阴影色偏 | -1.5 冷色阶 | 阴影区 | 复古风格可加到 -3,现代简约风建议归零 |
这组参数是拿几十张不同题材的插画反复试出来的,特点是"什么都改了一点,但什么都不至于过头"。它背后的逻辑是:纹理注入是一个累积过程,每一层单独看都很微弱,但加在一起就是一个可感知的质的改变。如果单层强度拉得太高,叠加后就会出现蜘蛛网一样的纹理痕迹,一眼假。
4. 让画面"活"起来的关键:随机性控制与材质关联逻辑
流程跑通之后,我很快就发现了一个新问题:用同一组参数处理十张图,纹理每张都不同(因为我用了随机种子),但风格上总觉得差了点意思。后来分析下来,问题出在随机性没有和画面内容产生关联——纹理层只是机械地铺上去,它不管底下画的是什么。
举一个最直观的例子:一张插图里既有大面积天空,又有人物面部特写。纸张纹理在处理天空时可以明显一些,因为大色块区域需要更多细节来填充;但处理面部皮肤时纹理必须收敛,否则脸上全是纸纤维的痕迹,看起来像皮肤皲裂。所以 artcraft 的第二版做了一个重要升级:让纹理强度跟随画面局部特征自动调节。
4.1 基于亮度分区和边缘密度的自适应强度控制
实现思路不复杂:先用 Canny 算子提取原图边缘,做一个边缘密度图(滑动窗口里边缘像素占比),再结合亮度做一个二维查找表,决定每个位置的纹理强度系数。
这样做背后的原理很符合直觉:
- 边缘密集区域(比如人物的衣服褶皱、建筑结构线):纹理强度自动减半,避免干扰线条的清晰度;
- 高光平坦区域(比如天空、留白):纹理强度保持默认,因为这些地方本来细节就少,需要纹理来"填充";
- 阴影深色区域:纹理强度轻微上调,让暗部保留"纸面承墨"的质感,但注意不能超过 1.3 倍,否则暗部会出现颗粒结块。
这个自适应逻辑的代码量和前面四层加起来差不多,是 artcraft 项目里耗时最长的部分。因为"Canny 边缘密度图"本身带有随机性,不同图像参数差异很大,需要根据每张图的直方图动态调整高低阈值,否则边缘要么全被检测出来(变成一团黑),要么漏掉大量细线(面部轮廓就没了)。我最后用的是自适应阈值方法:以图像像素均值为基准,低阈值设为 0.5 倍均值、高阈值设为 1.5 倍均值,实验了五十多张图之后,这个比例在大多数画风下都稳定。
下图为伪代码逻辑,展示强度系数如何和边缘密度挂钩:
def adaptive_strength_map(image, edge_map, kernel_size=31): # 边缘密度图:滑动窗口内的边缘像素占比 density = cv2.boxFilter(edge_map, ddepth=-1, ksize=(kernel_size, kernel_size)) density = density / np.max(density + 1e-6) # 亮度归一化 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY).astype(np.float32) / 255.0 # 二维强度查找表:亮度轴分成 5 档,密度轴分成 3 档 factor = np.ones_like(gray) # 边缘密度高 -> 强度打 0.5 折 factor[density > 0.6] *= 0.5 # 高光平坦区 -> 保持 1.0 # 暗部 -> 轻微上调 1.2 factor[(gray < 0.25) & (density < 0.3)] *= 1.2 return factor这个阶段跑通之后,artcraft 处理出来的作品才算真正有了"记忆点"。以前是"一张加了纹理的画",现在能感觉到纹理和画的"内容"是一体的,纸张的颗粒会自然地避开人物的五官,只在背景和阴影里沉淀。
4.2 用颜色相关性替代纯随机:让颗粒"长"在画面上
第二个随机性的调整方向,是把颗粒颜色和原图像素颜色做关联。
第一版颗粒层的颜色是独立生成的,就是随机深浅的灰色,结果在鲜艳的插画上会出现非常刺眼的白点。后来我改成从原图区域里取色:每个颗粒生成时,以它所在位置的原图像素颜色为基础,做 ±8% 的明度抖动和 ±3% 的饱和度抖动。这样颗粒就像是颜料干涸后从底色里"浮"出来的,颜色和周围的色块融合得非常自然。
这个做法的本质是对纹理层和内容层做"颜色绑定"。传统手绘里,纸张纹理之所以看起来和颜料是一体的,就是因为颜料不是完全覆盖纸张的,纤维间隙里透出的纸张颜色和颜料边缘形成了自然的过渡。数字模拟必须用颜色相关的方式去模拟这种过渡,否则纹理永远悬浮在画面之上,像蒙了一层纱。
4.3 实测数据:同一张图跑十次的随机漂移范围
有读者可能会问:加了随机之后,同一张图每次跑出来的结果差别有多大?会不会随机过大导致风格不稳定?
我专门做过一组测试。拿同一张插画原图,固定四层强度的基础参数,只改变随机种子,连续跑十次,然后计算每次结果与原图的均方误差(MSE)和结构相似度(SSIM)。测试结果如下:
| 次数 | MSE | SSIM |
|---|---|---|
| 1 | 43.2 | 0.986 |
| 2 | 44.1 | 0.985 |
| 3 | 42.8 | 0.987 |
| 4 | 44.6 | 0.984 |
| 5 | 43.9 | 0.986 |
| 6 | 42.5 | 0.988 |
| 7 | 44.3 | 0.985 |
| 8 | 43.6 | 0.986 |
| 9 | 44.0 | 0.985 |
| 10 | 43.4 | 0.987 |
肉眼上十张图几乎看不出差别,但纹理细节每张都不重复。这其实是理想状态:宏观看上去稳定,微观上保持变化。如果你用 artcraft 跑出来的图两张完全一模一样,说明随机种子没接进去,代码有 bug,回头检查一下随机数初始化的位置。
我的经验是,随机性控制的目标不是让每张图都不一样,而是让每张图"带有相同的制造痕迹但不同的落点痕迹"——就像同一个工匠手工做的十个杯子,一眼就是一个系列,但每个杯子的釉面纹路都不同。要做到这个效果,关键在于把随机变化限定在"不影响整体结构读解"的范围内,也就是前面说的,所有随机量的幅度上限都控制在人眼感知阈值附近。
5. 实测中翻过的车:六类高频问题与完整排查链路
从 v1 到 v2,artcraft 经历了大量真实项目的检验,也踩了不少坑。这些坑在文档和教程里基本不会提,但对想复现这个流程的人来说,每一个都可能导致整条管线废掉。我挑六个最典型的问题,把排查过程写透。
5.1 问题一:16 位色彩模式导致的色带断层
第一个翻车案例发生在第三版左右。当时我处理一张从某正规图库下载的高分辨率扫描水彩图,原图是 16 位 ProPhoto RGB 色彩空间。artcraft 第一轮跑完,输出到一半的时候画面出现了非常明显的横向色带,尤其是在天空渐变区域,一条一条的断层让人完全没法用。
排查链路是这样的:一开始我怀疑是自己写的柔光混合函数的精度问题,于是把代码里所有数组都转成 float64 再算了一遍,色带依旧。后来把输出图像单独拿到 PS 里检查了通道值,发现色带区域的 R 通道值呈锯齿状跳变,而 G、B 通道平滑——这就排除了混合函数的问题,因为如果是混合函数的精度不够,三个通道应该同时出问题。
最后定位到根因:OpenCV 读入 16 位 PNG 时默认会把它当 8 位处理,高字节和低字节被切掉了一部分,导致渐变区域的有效位数从 16 位掉到了大约 10 位,而在低位数下,大面积的平缓渐变必然出现色阶断层。这不是 artcraft 的问题,是 OpenCV 读取行为的坑。
解决方案是在读取图片时显式指定cv2.IMREAD_UNCHANGED,并检查dtype是否为uint16,如果是,先缩放到 float32 范围再做处理,最后输出时再转回 8 位。那次之后我在管线的入口加了一个"色彩空间与位深自检"模块,跑之前先把图片元数据打印出来看一眼,省得后面排半天。
5.2 问题二:基底纹理把所有画面都蒙成了"灰蒙蒙"
第二个踩坑很常见:基底纹理层叠加上去之后,整张图都像隔了一层雾,暗部尤其明显,原本该黑的区域变成了深灰。
一开始我以为是柔光公式写错了,反复验证公式本身是标准实现,毫无问题。后来拿一张纯黑白渐变图做测试,一张一张调强度,发现当强度超过 20% 时,"发灰"的程度急剧上升;而强度在 8%~15% 时,画面的对比度其实基本没有变化。这就说明问题出在强度参数的默认值,而不在算法。
为什么会发灰?因为柔光混合本质上是对比度微调,而基底纹理层的内容是"不太亮的灰色纤维"——它在亮部会轻微压暗,在暗部会因为混合公式中background^0.5这一项而略微提亮。两个方向的移动凑在一起,中间调变化不大,但两极都往中间靠,明暗差就缩小了,视觉上就是"蒙了一层灰"。
解决方案不是把纹理层变暗或变亮,而是调整基底纹理的灰度分布:把纹理图中低于 0.2 和高于 0.8 的像素值做截断,让纹理本身的动态范围收窄。这样既能保留纤维的起伏信息,又不会挤压画面的明暗对比。实测中这个截断操作配合 12% 的叠加强度,几乎不会对原图对比度产生可感知的影响。
5.3 问题三:颗粒层在 JPEG 压缩图上产生大量马赛克
用 artcraft 处理手机拍的照片时,颗粒层会在一部分区域产生明显的小方块马赛克。这个问题的根源不在 artcraft 本身,而是 JPEG 压缩导致的脏数据。
JPEG 压缩会把图像切成 8×8 的块,每个块独立量化,所以压缩率一高,块与块之间的边界就会出现轻微色差。颗粒层叠加时,这些色差被粒子增强,肉眼就看到了马赛克。
排查链路最终指向一步:颗粒层的生成不应该直接作用在 JPEG 图上,而应该先用一个轻度保边滤波(比如双边滤波)预处理一下。双边滤波能抹平 8×8 块边界的小色差,同时保留真正的边缘信息。我试过用高斯模糊,效果不好,因为高斯模糊会把边缘也糊掉,画画的人一眼就能看出来。双边滤波的缺点是慢,但好在 artcraft 处理的是静态图像,慢一点没关系,一亿像素的图跑一次也就多十几秒。
5.4 问题四:暗部区域颗粒结块成"脱色"的斑驳
处理深色背景插画时,暗部区域出现了一种特别难看的现象:黑色区域不再是干净的深色,而是显露出灰白色的斑点,就像颜料没有涂匀露出了纸底。
这个问题的根源在颗粒取色逻辑上。前面说过,颗粒颜色基于原图像素做 ±8% 的明度抖动,但在极暗区域(像素值低于 30/255),这个 8% 的绝对偏移已经超出了可感知范围,叠加后反而变成了"不该有的亮色"。
修复思路是给颗粒明度抖动加一个"暗部衰减":当底色亮度低于 0.15 时,抖动幅度按比例压缩,同时把颗粒改为"加深"方向的偏移,而不是随机双向。换句话说,在暗部,颗粒只能让画面更暗、更有粉质聚集的层次,不允许产生向上的亮色偏移。做完这个修正后,暗部斑驳彻底消失,深色区域反而获得了类似颜料堆积的厚实感。
5.5 问题五:批量处理时不同图的边缘扰动幅度差异过大
我经常一批处理多张插画,跑完一批后发现,有的图边缘扰动正好,有的图边缘已经明显发虚了。这个问题的根源是边缘扰动幅度是全局固定值,而不同图的分辨率不一样。
同样是 1.2px 的扰动,在 2000×1500 的图上感知不明显,但放在 800×600 的缩略图上就非常明显,因为 1.2px 占了高宽比较大的比例。
修复方式是按图像对角线长度做扰动幅度的归一化:目标扰动幅度 = 基准值 ×(当前图对角线 / 2000)。这样无论原图分辨率多少,边缘扰动在视觉感知上保持相对一致。这套逻辑后来被我推广到颗粒半径和基底纹理强度上,批量出图时风格统一度直线上升。
5.6 问题六:纹理方向性和画风不匹配
最后一个是偏主观的问题,但也比较普遍。artcraft 的纹理方向是随机生成的,但有的插画有明确的运笔方向意识,比如风景画里的横向排线、素描里的交叉排线,如果纹理方向随机,会破坏画作本身的方向感。
解决方案是增加一个"主纹理方向"参数,可以在运行时指定:0 表示无方向性随机,1 表示横向,2 表示纵向,3 表示 45 度斜向。生成纹理时,把先验的方向场叠加到随机场上,让纹理在大部分区域沿指定方向排列,局部保留随机偏移。这个功能加完,artcraft 从"通用纹理工具"变成了"可以配合画风工作的纹理工具",这是非常关键的一步。
6. 向更远处走一步:artcraft 的进阶用法与可以扩展的方向
artcraft 在图像处理层面跑通之后,我逐渐意识到它的本质其实是"一个增强手工感知的工具",而不只是"一个加纹理的脚本"。后者是手段,前者才是目的。在这个认知下,我陆续扩展了几个进阶用法,觉得都有参考价值。
6.1 把 artcraft 嵌入视频帧序列做动态纹理
视频方面我试过一条路径:用 artcraft 处理逐帧序列,让纸张纹理在每帧之间连续变化,而不是每帧独立随机。实现方式是把随机种子设为"帧号 × 固定系数",这样帧与帧之间的纹理场是连续演进的,播放时的体积感和纸张呼吸感都很自然。注意不要每一帧都用完全独立的随机种子,否则画面会像电视雪花一样跳动,完全没法看。
动态纹理的强度要比静态图低一档,因为视频本身有时间轴来提供信息密度,不需要每一帧都叠满细节。我一般把静态图的默认强度乘以 0.7 作为视频帧的强度基准。
6.2 和传统扫描件的"纸纹提取"结合
另一种思路是反着来的:不是让算法生成纸纹,而是从真实纸上提取纸纹,再作为纹理基底输入 artcraft。这个场景下有一步值得留意——扫描件的纸纹里通常混着画面内容,直接提取会带出别人的笔迹。
我试过一种成功率较高的办法:同一张纸扫描两次,第二张是空白区域的特写,利用两张图的重叠区域做差,得到纯纸纹。如果是没有空白区域的成品画,可以拍一张同品牌纸张的空白纸面,提取纤维特征和色偏,效果也不错。这个玩法适合追求极致仿真质的创作者,比算法生成的纹理多一层真实物理信息,放大之后尤其能看出差别。
6.3 围绕 artcraft 扩展成小组件生态
artcraft 目前包含大约十二个独立组件,可以分拆使用,也可以串起来跑完整管线。组件之间有明确的输入输出契约(统一为 RGBA 浮点数组),这让它很容易嵌入到其他流程里。例如我有一个给电商海报做旧纸纹效果的独立脚本,就是从 artcraft 里拆出基底纹理层单跑的。
如果未来继续做下去,我想把渲染参数做成可视化面板,让不熟悉 Python 的插画师也可以直接调整滑块来观察效果变化。目前的操作方式还是改配置文件里的数字,对程序员友好,但对纯画画的人不友好,这是 artcraft 目前最大的使用门槛。
7. 个人实测后的总结性心得(这可能是本文最有价值的部分)
项目整体做完,我最想留在这篇文章里和大家分享的,不是某个具体算法或参数,而是三条只能在实战里悟出来的判断。
第一条是关于工具与技艺的边界。artcraft 能模拟纸纹、颗粒和笔触扰动,但它无法替你做审美决策——什么时候纹理应该收,什么时候应该放,什么情况下画面只需要一个非常微妙的纸感而不是明显的颗粒,这些依然要用人眼去判断。工具能把你的下限抬高,但决定上限的还是眼睛和手。
第二条是关于参数的最小干预原则。我见过有人拿到 artcraft 后第一件事就是把所有强度拉满,结果出来的图像贴了一张墙纸,纹理完全盖住了画本身。我的经验是,纹理细节的好坏有一个临界点:在临界点之前,细节每增加一分,画面"手工感"就增加一分;一旦过了临界点,画面迅速垮塌成"滤镜感"。做这类工作一定要克制,最好从肉眼刚刚能感知到纹理的强度开始往回调 20%。
第三条是关于技术方案的可维护性。artcraft 的所有代码都用注释写了设计依据,比如某个参数为什么默认是 12% 而不是 10%,因为我在注释里记录了当时的测试结论。后来隔了两个月我回去改功能,靠这些注释省了大量重新推理的时间。做个人项目很容易陷入"跑通了就行"的误区,但工具是要长期用的,代码里的"为什么"和代码本身一样重要。
最后,如果你也要做一个类似的东西,我建议不要一上来就追求功能大而全。先从最基础的"一层纹理 + 柔光混合"开始,跑通之后再做第二层,每加一层都回头审视它对已有效果的影响。artcraft 看起来是一个整体项目,但它的每一个模块都是独立演进的,这种增量式推进方式保证了每加一个新能力都不会破坏已经稳定的效果。保持这个节奏,你的工具会越用越顺手,也会慢慢长出只属于你自己的风格。