瓷青色很适合安静的创作界面。做一张带晕染感的底图时,我会把“看起来像釉色”与“模拟烧制过程”分开:前者可以用很少的像素操作完成,后者涉及材料与物理,不该靠一个透明度公式来冒充。
先把颜色合成算对
下面在不透明白纸上叠一层半透明蓝色。计算采用普通 alpha 合成,结果仍为不透明 RGB;这个前提很重要。如果底图本身透明,就需要连同输出 alpha 一起计算,不能照搬这个简化公式。
色块内的透明度缓慢变化,靠近边缘增加深蓝比例,形成视觉上的积色。距离场只是圆形边界到当前像素的距离,不包含真实液体流动、蒸发或毛细作用。把它叫“瓷青晕染近似”就足够,不需要一个伪装成科学的引擎名字。
像素输出可以交给 Canvas 的ImageData。为便于离线复现,本例直接写 PPM。色彩按字节值混合,尚未在线性光空间合成,因此也不把结果称为色彩管理参考实现。
用透明度端点检查
alpha=0 应完全保留底色,alpha=1 应完全成为前景色。只要这两个端点不对,后面的纸纹再漂亮也没有意义。随后再比较轻、中、重三档积色,避免每次同时改颜色、噪声和轮廓,最后无法解释是哪项产生了效果。
运行node glaze.js输出glaze.ppm,检查端点、通道范围和确定性。没有运行 Canvas、GPU、真实颜料或窑烧测量,也没有声称性能优势。小小一片蓝色能让页面安静下来,前提是代码也保持清楚。
可独立运行的核心例子
const fs=require('node:fs'); function overOpaque(bg,fg,a) { if(!(a>=0&&a<=1))throw Error('alpha无效'); return bg.map((v,i)=>Math.round(v*(1-a)+fg[i]*a)); } function glaze(w,h) { const out=[]; for(let y=0;y<h;y++)for(let x=0;x<w;x++) { const d=Math.hypot((x-w/2)/(w*.4),(y-h/2)/(h*.4)); if(d>1){out.push(255,255,255);continue;} const edge=Math.max(0,(d-.8)/.2),a=.28+.4*edge; out.push(...overOpaque([255,255,255],[34,85,138],a)); } return out; } const bg=[240,241,242],fg=[10,20,30]; if(overOpaque(bg,fg,0).join()!=bg.join()||overOpaque(bg,fg,1).join()!=fg.join())throw Error('端点错误'); const rgb=glaze(96,96); if(rgb.length!==96*96*3||rgb.some(x=>x<0||x>255)||rgb.join()!=glaze(96,96).join())throw Error('像素错误'); fs.writeFileSync('glaze.ppm','P3\n96 96\n255\n'+rgb.join(' '));console.log('已生成 glaze.ppm');内图用花纹说明叠层;最小代码只生成圆形色块,不复刻花纹。