在天气类App、游戏场景或动态壁纸中,程序化生成的云朵比静态图片素材更灵活:它不占包体积、可以任意调整形态,还能随时间无限变化。Core Image作为iOS系统级图像处理框架,内置了柏林噪声生成器,配合GPU加速的滤镜链,完全可以在移动端实时渲染出高质量的分形云。本文围绕三个核心问题展开:如何叠加噪声生成分形云纹理、如何通过参数控制云层厚度、如何让云飘起来。

一、分形噪声的原理与云朵纹理生成
单一频率的柏林噪声看起来只是一团模糊的灰雾,真正的云之所以逼真,是因为大气中的湍流具有跨尺度的自相似性——大尺度决定云的整体轮廓,小尺度填充边缘的絮状细节。这就是分形布朗运动(fBm)的核心思想:将若干个 octave(倍频程)的噪声叠加,每个 octave 的频率翻倍、振幅减半,公式为 noise(p) + 0.5 × noise(2p) + 0.25 × noise(4p) + …。叠加的层数越多,细节越丰富,但计算量也线性增长,移动端通常取4到6层已经足够。
Core Image提供了CIFilter.perlinNoiseGenerator()(旧API为CIPerlinNoiseGenerator),它支持通过inputFrequency、inputOctaves等参数直接生成多倍频噪声,这省去了手动叠加的麻烦。下面是一段Swift代码,生成基础云纹理:
func makeCloudTexture(size: CGSize) -> CIImage? {
guard let noise = CIFilter(name: "CIPerlinNoiseGenerator") else { return nil }
// 调整噪声参数
noise.setValue(0.007, forKey: "inputFrequency") // 基础频率,越小云团越大
noise.setValue(6, forKey: "inputOctaves") // 倍频层数,细节丰富度
noise.setValue(2.0, forKey: "inputPersistence") // 持续度,越高细节越明显
let seed = Int.random(in: 0...1000)
noise.setValue(NSNumber(value: seed), forKey: "inputSeed")
guard let output = noise.outputImage else { return nil }
// 噪声是无边界的,裁剪到目标尺寸
let center = CGPoint(x: size.width / 2, y: size.height / 2)
let translated = output.transformed(by: CGAffineTransform(
translationX: center.x - output.extent.width / 2,
y: center.y - output.extent.height / 2))
return translated.cropped(to: CGRect(origin: .zero, size: size))
}
需要注意,噪声生成器输出的灰度图是彩色的偏蓝噪声,云是灰度形态,所以后续还需要做去色处理。如果想要更强烈的自相似效果,也可以手动创建多个不同频率的噪声图再用CIAdditionCompositing合成,但实测系统滤镜的octaves参数已经能覆盖绝大多数场景,手动叠加主要用在需要非均匀衰减系数的场合。
二、云层厚度控制:从灰度映射到密度调节
拿到噪声纹理后,直接显示只是一片灰雾,云的“厚度感”来自于灰度值到形态的映射方式。云越厚,内部越不透光、越白亮,边缘越稀薄。实现上分两步:第一步用CIColorControls提高对比度并去饱和,把中间灰挤压到两端;第二步用CIColorMatrix或自定义kernel做阈值软化,让低于某个灰度的区域变透明,形成轮廓清晰的云团。
func applyCloudDensity(_ input: CIImage, thickness: CGFloat) -> CIImage {
// 第一步:去色并增强对比,thickness越大对比越强,云团越紧实
let controls = CIFilter(name: "CIColorControls")!
controls.setValue(input, forKey: kCIInputImageKey)
controls.setValue(0.0, forKey: "inputSaturation") // 完全去色
controls.setValue(1.0 + thickness * 0.8, forKey: "inputContrast")
controls.setValue(0.05, forKey: "inputBrightness")
var result = controls.outputImage!
// 第二步:用gamma曲线把暗部压成透明,亮部提白
// 简单做法是再做一次CIColorControls,用brightness反向调整
let lift = CIFilter(name: "CIGammaAdjust")!
lift.setValue(result, forKey: kCIInputImageKey)
lift.setValue(0.6 + (1.0 - thickness) * 0.4, forKey: "inputPower")
result = lift.outputImage!
return result
}
thickness参数的实际含义可以这样理解:接近1时,gamma值降低,中低灰度被快速抬升,云团蓬松饱满、覆盖面积大;接近0时gamma接近1,只有噪声峰值处成云,天空显得稀薄晴朗。另外一种更物理化的做法是按高度渐变调整厚度:用CILinearGradient生成从地平线到天顶的蒙版,再与云纹理做CIBlendWithMask合成,得到底部厚顶部薄的层积云效果,这种手法在天气App中很常见。
如果需要体积感更强的云,可以把同一噪声图采样三次偏移不同距离,分别作为R、G、B通道的密度输入,模拟光线在不同深度散射后的色差。成本几乎不变,但立体感提升明显。
三、动态云飘效果的实现与性能优化
让云飘起来最优雅的方式不是移动图片,而是移动噪声的采样坐标。每种动态噪声都有时间维度,但Core Image的柏林噪声生成器本身不含时间轴,惯用做法是在输出图像上叠加一个随时间递增的平移变换,同时对noiseSeed或坐标做周期处理。关键在于保证无缝循环:如果直接平移图像,飘出边界会露馅,正确做法是让噪声纹理尺寸大于显示区域,平移量在一个安全区间内往返,或者生成两倍宽的纹理做滚动裁剪。
class CloudRenderer {
private var startTime: CFTimeInterval = 0
private let context = CIContext(options: [.useLowQualityPower: false])
func cloudImage(at time: CFTimeInterval, size: CGSize) -> CIImage? {
guard let base = makeCloudTexture(size: CGSize(
width: size.width * 2, height: size.height)) else { return nil }
// 风速控制水平飘移速度,垂直方向给一个很小的分量模拟上升气流
let windSpeed: CGFloat = 30 // 点每秒
let dx = CGFloat(time) * windSpeed
.truncatingRemainder(dividingBy: size.width)
let drifted = base.transformed(by: CGAffineTransform(translationX: -dx, y: 0))
let cropped = drifted.cropped(to: CGRect(origin: .zero, size: size))
return applyCloudDensity(cropped, thickness: 0.7)
}
}
驱动层用CADisplayLink每帧调用cloudImage(at:)并把结果交给CIContext渲染到Metal层或UIView。有一点必须强调:噪声滤镜是惰性求值的,如果每帧都重建整条滤镜链并重新生成噪声,GPU开销会显著上升。正确的做法是缓存噪声纹理的CIImage,每帧只修改末端的transform和密度参数,Core Image的kernel会复用已生成的噪声数据,帧耗可以从十几毫秒降到两毫秒以内。
性能上还有几个细节值得注意。第一,渲染尺寸不要直接用屏幕物理像素,云本身是低频内容,按逻辑分辨率渲染再由GPU缩放几乎无质量损失;第二,CIContext应当全局复用一个实例,频繁创建会反复分配GPU资源;第三,如果云飘速度很慢,可以把帧率锁定在30fps甚至更低,CADisplayLink的preferredFramesPerSecond属性即可控制,肉眼完全察觉不出卡顿,功耗却能下降近一半。
最后补充一种进阶玩法:将平移量同时作用于两个octave不同的噪声层,速度略作差异,两层云以不同速度飘动并叠加,会产生真实大气中不同高度云层的视差效果。实现上无非是重复第二节的处理流程,给每层独立的风速和厚度参数,这也是许多天气App做出“有深度天空”的秘密所在。
Core Image分形噪声iOS图像处理修改时间:2026-09-10 03:40:41