分形火焰是一种基于迭代函数系统的随机迭代算法,通过反复应用一组仿射变换并统计迭代点的落点密度,最终渲染出具有强烈火焰质感与自相似结构的彩色图案。它最早由Scott Draves提出,如今在桌面端已有Apophysis等成熟工具,但在移动端实现的文章相对较少。本文将以iOS平台为背景,介绍如何利用Core Image的自定义内核能力,将分形火焰算法搬到iPhone与iPad上,并生成色彩绚丽的分形图案。

迭代函数系统与分形火焰的数学基础
要理解分形火焰,必须先理解迭代函数系统,也就是常说的IFS。一个IFS由若干个仿射变换组成,每个仿射变换可以表示为一个二维矩阵和一个平移向量的组合,形式为x' = a*x + b*y + e,y' = c*x + d*y + f。当我们从一个初始点出发,随机选择其中一个变换进行映射,并无限重复这个过程,这些点的轨迹会收敛到一个特殊的集合,也就是该IFS的吸引子。经典的谢尔宾斯基三角形、巴恩斯利蕨类植物,本质上都是某个特定IFS的吸引子。
传统IFS渲染通常使用确定性方法,比如逃逸时间算法,直接判断某个像素是否属于吸引子。而分形火焰在此基础上做了两个关键改动。第一是引入混沌游戏式的随机迭代,用随机数选择变换,让点自然地按照各变换的权重分布在吸引子附近。第二是在每个变换之后串联一个非线性变分函数,例如正弦、球面、漩涡等,正是这些变分函数的存在,让图像摆脱了几何图形的呆板,呈现出流动的火焰质感。
下面用Swift定义一个基本的仿射变换结构和变分函数。实际项目中,一个火焰由多个这种变换组成,每个变换还带有权重,用于控制随机选择时的概率分布。
struct AffineTransform {
var a: Double, b: Double, c: Double
var d: Double, e: Double, f: Double
var weight: Double
func apply(_ p: SIMD2<Double>) -> SIMD2<Double> {
// 标准仿射映射:旋转、缩放加平移
SIMD2<Double>(a * p.x + b * p.y + e,
c * p.x + d * p.y + f)
}
}
// 常用变分函数示例
func vary(_ type: Int, _ p: SIMD2<Double>) -> SIMD2<Double> {
let r2 = p.x * p.x + p.y * p.y
switch type {
case 0: // linear:线性,即不变换
return p
case 1: // spherical:球面,产生对称球形结构
return p * (1.0 / max(r2, 1e-9))
case 2: // swirl:漩涡,围绕原点旋转,角度随半径变化
let s = sin(r2), c = cos(r2)
return SIMD2<Double>(p.x * s - p.y * c,
p.x * c + p.y * s)
default:
return p
}
}随机迭代时有一个重要的经验:前几十次迭代的结果通常不可靠,因为初始点可能离吸引子很远,这些点被称为燃烧期样本,必须丢弃。一般丢弃前20次左右的迭代,之后每个点才真正落在吸引子附近,才有统计意义。这一点对最终图像质量影响很大,很多初学者渲染出一片噪点或杂乱线条,往往就是没有处理燃烧期。
用Core Image自定义内核实现随机迭代
Core Image在iOS上提供了多种自定义能力,从老的CIKernel到基于Metal的CIRAWKernel,再到可以对每个像素独立计算的CIImageProcessorKernel。对分形火焰这类算法来说,最自然的思路是用CIImageProcessorKernel,它允许我们拿到输出缓冲区后自行填充像素,CPU或GPU逻辑都能写进去。它相比直接写完整Metal管线的优势在于可以无缝接入Core Image的滤镜链,输出直接可以和其他CIFilter组合、调色、缩放。
CPU实现的核心逻辑是维护一个密度直方图。每个迭代点经过变换后落到平面某个位置,我们把该位置映射到直方图的格子中,累加一次命中计数。同时每个变换还携带颜色坐标,迭代过程中颜色坐标不断向当前变换的颜色插值靠拢,这样最终每个格子不仅有密度,还有一个平均颜色。渲染阶段把直方图做对数压缩映射到亮度,再乘以颜色坐标转成RGB,就得到了最终的彩色图案。
class FlameProcessor: CIImageProcessorKernel {
override func process(with inputs: [CIImageProcessorInput]?,
arguments: [String: Any]?,
output: CIImageProcessorOutput) throws {
guard let input = inputs?.first,
let outBuf = output.baseAddress else { return }
let w = output.width, h = output.height
let bytesPerRow = output.bytesPerRow
let hist = [UInt32](repeating: 0, count: w * h)
var colorSum = [SIMD3<Float>](repeating: .zero, count: w * h)
var rng = SystemRandomNumberGenerator()
var p = SIMD2<Double>.zero
var color: Double = 0
for _ in 0..<2_000_000 {
// 按权重随机选择一个变换
let t = pickTransform(by: rng)
p = t.apply(p)
color = (color + t.color) * 0.5
// 丢弃燃烧期以外的点不需要跳过,跳过的是前20次
// 此处简化,实际应先燃烧20次再统计
let ix = Int((p.x * 0.25 + 0.5) * Double(w))
let iy = Int((p.y * 0.25 + 0.5) * Double(h))
if ix >= 0 && ix < w && iy >= 0 && iy < h {
hist[iy * w + ix] &+= 1
colorSum[iy * w + ix] += SIMD3<Float>(flamePalette(color))
}
}
// 对数压缩后写入输出缓冲区
for i in 0..<(w * h) {
let alpha = logf(Float(hist[i]) + 1.0) * 60.0
let c = hist[i] > 0 ? colorSum[i] / Float(hist[i]) : .zero
let offset = i * 4
outBuf.store(SIMD4<UInt8>(
UInt8(clamping: Int(c.x * alpha)),
UInt8(clamping: Int(c.y * alpha)),
UInt8(clamping: Int(c.z * alpha)), 255),
at: offset)
}
}
}上面是简化后的示意代码,实际工程中要注意两点。其一直方图统计阶段输出的alpha通道需要保留密度信息,避免多次叠加时颜色溢出,更规范的做法是输出RGBA16Float格式的浮点纹理,由后续滤镜做色调映射。其二随机数生成器在CPU循环里会占不少时间,可以用更快的xorshift算法替代系统随机数。另外调度方面,可以把迭代任务拆到多个线程,每个线程维护独立直方图,最后合并,利用多核加速统计过程。
色彩映射与性能优化技巧
分形火焰的颜色来源于调色板,常见做法是预先构造一个256或1024条目的颜色表,颜色表可以是渐变色、正弦波混色,甚至从一张图片采样。每次迭代时,变换携带一个0到1之间的颜色索引,迭代点的颜色索引逐渐逼近所选变换的索引,最终统计每个直方图格子的平均索引再去查表。这样得到的颜色会沿着分形结构平滑过渡,看起来就像热量在火焰中自然分布。如果想让色彩更丰富,可以在查表后叠加色相偏移滤镜,Core Image自带的CIColorControls和CIVibrance就能直接增强视觉效果。
亮度映射是另一个决定画面质感的关键。直方图密度跨越好几个数量级,直接线性映射会让密集区域全白、稀疏区域全黑,所以必须用对数函数压缩。经验公式是亮度正比于ln(1 + density)乘以一个曝光系数,曝光系数可以暴露给用户调节。更进一步可以在Core Image链里追加CIExposureAdjust和CIToneCurve,把色调映射做成可调参数,实现类似摄影后期的观感控制。
性能方面,CPU统计上千万次迭代在老设备上可能需要数秒,要实时预览就必须搬到GPU。方案是用Metal计算内核做随机迭代,每个线程负责一部分迭代序列,通过原子操作累加到共享的浮点纹理上,然后把这张纹理作为输入交给Core Image做色调映射和调色。这样渲染一帧只需几十毫秒。如果暂时不写Metal,退而求其次可以降低迭代次数配合CIGaussianBlur轻微模糊来掩盖噪点,先用低质量快速预览,用户确认参数后再后台跑高质量渲染。此外迭代采样时可以采用超采样,即内部直方图分辨率是输出图像的两倍,最后缩小采样,能让边缘更细腻,这一步可以直接用CILanczosScaleTransform完成。
总结来说,在iOS上实现分形火焰的关键路径是:用IFS定义变换集合,用带权随机迭代生成密度直方图,用颜色插值和调色板完成着色,最后通过Core Image滤镜链完成色调映射与后期增强。Core Image的价值在于它让算法输出可以自然融入滤镜管线,与系统图像处理能力无缝衔接。把这个算法封装成一个CIFilter风格的自定义滤镜,配合滑块调节变换参数,就能做出一个实时生成分形艺术的App,无论用于壁纸生成、动态背景还是纯艺术创作,都有不错的表现力和可玩性。
Core Image分形火焰迭代函数系统修改时间:2026-09-15 11:54:05