Noise 噪点
给画面加随机扰动,做胶片颗粒、老电视雪花、磨砂质感。
node.cache();
node.filters([Konva.Filters.Noise]);
node.noise(0.4); // 默认 0.2
扰动是对称的
源码里的计算是:
const amount = this.noise() * 255;
const half = amount / 2;
data[i] += half - 2 * half * Math.random();
也就是每个通道加上一个 [-half, +half] 区间内的随机值。
noise(0.4) 对应 half = 51,扰动范围是 ±51。
因为是对称的,画面的平均亮度不变,只是方差变大。 这和"加噪让图变亮/变暗"的直觉不同——噪点只影响层次,不影响曝光。
三个颜色通道各自独立取随机数,所以噪点是彩色的, 不是灰度的颗粒。想要灰度噪点,得自己写 自定义滤镜,让三个通道共用同一个随机值。
alpha 通道不受影响(步长是 4,跳过了第四个字节)。
每次运行都会重新随机
Math.random() 在滤镜函数里调用,意味着每次滤镜执行都会得到一组新的噪点。
静态场景下这没关系——滤镜只在 cache() 之后跑一次。
但在动画里,如果每帧都触发滤镜重跑,噪点会每帧都变,
画面看起来在剧烈闪烁。
这有时正是你要的(老电视雪花效果),有时则是个 bug (你只想要静态的胶片颗粒,结果它在抖)。区别在于你是否在每帧改属性。
想要静态颗粒:cache() 一次、设一次 noise(),之后别再碰它。
想要动态雪花:在动画里反复设置 noise() 触发重算,
或者干脆预生成几帧噪点贴图循环播放(性能好得多,见下)。
常见问题
noise 的取值范围是多少?
默认 0.2,没有硬性上限。实用范围:
0.05以下——很轻微,适合给纯色区域加一点质感避免色带0.2(默认)——明显但不喧宾夺主0.5以上——画面开始被噪点主导1.0——扰动范围 ±127,原图基本被淹没
为什么加了噪点图片看起来发灰?
不是发灰,是对比度的感知下降。噪点把原本平滑的区域打散, 边缘和细节被随机值淹没,视觉上就"糊"了。
如果想要颗粒感但不损失锐度,把 noise 调小(0.1 以下),
再配合 Contrast 补回一点对比度。
能只给暗部加噪点吗?
内置滤镜不行,它对所有像素一视同仁。
真实胶片的颗粒在中间调最明显、在纯黑和纯白处最弱。 要模拟这个特性需要自定义滤镜,按像素亮度调制噪点强度:
function FilmGrain(imageData) {
const d = imageData.data;
for (let i = 0; i < d.length; i += 4) {
const lum = (d[i] + d[i + 1] + d[i + 2]) / 765; // 0 ~ 1
const weight = 4 * lum * (1 - lum); // 中间调最大
const n = (Math.random() - 0.5) * 60 * weight;
d[i] += n; d[i + 1] += n; d[i + 2] += n; // 共用随机值 = 灰度噪点
}
}
性能提示
Noise 每个像素要调用三次 Math.random(),
这是它比其他逐像素滤镜贵的原因——随机数生成的成本远高于一次乘法。
一张 1000×1000 的图在 2x 缓存下是一千二百万次 Math.random() 调用。
因此有几条针对性的优化:
共用一个随机值。 如果不需要彩色噪点,三个通道用同一个随机数, 调用次数立刻降到三分之一,而且效果更接近真实胶片颗粒。
别在动画里重跑噪点滤镜。 这是最贵的用法:每帧一千多万次随机数生成。
需要动态噪点时,正确做法是预生成几张噪点贴图
(比如 5 张 256×256 的噪点 PNG),
用一个 Konva.Image 以 globalCompositeOperation: 'overlay' 叠在上层循环播放。
成本从"每帧一千万次随机"降到"每帧一次贴图绘制",差着几个数量级。
降低缓存分辨率。 噪点本身就是随机的,
cache({ pixelRatio: 1 }) 造成的画质损失完全看不出来,
但工作量直接降到四分之一。这个滤镜是降分辨率收益最大的之一。