柔光(Soft Light / Soft Focus)是摄影中经典的光学效果,通过让画面高光部分轻微溢出、整体细节适度柔化,达到皮肤通透、氛围朦胧的视觉感受。在胶片时代,摄影师会在镜头前加装柔焦镜片来实现这种效果;而在iOS开发中,我们完全可以借助Core Image框架,用高斯模糊提取画面的低频信息,再通过混合模式把它叠回原图,模拟出同样的光学质感。这篇文章从原理讲到实现,最后给出一份可以直接跑起来的Swift代码。

柔光效果的成像原理与实现思路
真实的柔焦镜头之所以能产生柔光,是因为光线在镜片间发生了扩散,高光区域的能量向四周弥散,而暗部区域受到的影响相对较小。这就解释了为什么柔光照片看起来“亮的地方发光、暗的地方保持干净”。如果在软件层面直接对整张图做模糊再叠加,画面会发灰、发闷,就是因为暗部也被均匀提亮了,违背了柔光的物理特性。
所以软件实现柔光的正确思路是分三步走:第一步,用高斯模糊提取图像的低频成分,得到一张模糊图;第二步,把模糊图与原图做混合模式合成,让高光区域被扩散出去的光斑提亮,暗部几乎不受影响;第三步,叠加一层轻微的光照效果,增加画面的通透感和空气感。这三步在Core Image里都有现成的滤镜支持,核心是理解它们的叠加顺序和参数调节范围。
这里最关键的滤镜是CISoftLightBlendMode和CIScreenBlendMode。Soft Light模式的行为类似于Photoshop中的柔光图层,混合结果取决于上层图像的明暗:上层偏亮时下层被提亮,上层偏暗时下层被压暗,中间调变化平缓,非常适合做自然的柔化叠加。Screen模式则是“变亮合成”,任何颜色与黑色混合保持原样,与白色混合变成白色,天然具有保护暗部的特性,很多商业滤镜的Orton效果(奥顿效果)就是靠Screen混合实现的。
关键滤镜详解:高斯模糊与混合模式
用CIGaussianBlur提取低频信息
CIGaussianBlur是整个柔光管线的起点,它只有一个重要参数inputRadius,控制模糊半径,单位是像素。半径越大,模糊越强,最终柔光效果越明显。需要注意的是,Core Image的坐标系以像素为单位,同一半径在不同分辨率的图片上表现不同,所以在实际项目中建议按图片尺寸做归一化,比如把半径设为图片短边的某个比例,这样滤镜在不同设备上表现才一致。
另一个容易踩的坑是模糊会扩展图像边界。CIGaussianBlur处理后输出图像的尺寸会比原图大,边缘还会出现透明拉伸。解决办法是在模糊之后用CIClamp滤镜把图像边缘向外延伸,或者直接对结果调用cropping(to:)裁回原始区域,否则叠加混合时会出现黑边或透明边。
混合模式的选择:SoftLight还是Screen
Core Image提供了CISoftLightBlendMode和CIScreenBlendMode两个候选。两者的输入参数相同:inputImage作为背景图,inputBackgroundImage作为混合源。实践中有一种非常流行的组合方式:先用Screen混合把模糊图层叠回原图,制造高光溢出;再用Soft Light在模糊层与原图之间做精细柔化,最后用透明度控制整体强度。这种“Screen打底、SoftLight修饰”的组合出来的肤色通透不发灰,比单一混合模式自然得多。
如果想进一步控制柔光的强度,Core Image没有直接的“不透明度”参数,但可以用CISourceOverCompositing配合CIColorMatrix把模糊层整体乘以一个alpha系数再合成,等效于调节柔光层的不透明度,这样用户就能在界面上拖动滑块实时调整效果强弱了。
叠加光照效果增强氛围
纯模糊加混合有时会显得画面“肉”而缺乏层次,这时可以叠一层CIHighlightAdjust,它专门调整高光区域的亮度,参数inputHighlight取值范围0到1,能让高光更亮、扩散感更强。如果想要暖色调的朦胧感,还可以用CIColorControls轻微降低饱和度,再用CITemperatureAndTint加一点点暖色偏移,模拟老式柔焦镜头的成像味道。参数都不需要太激进,柔光效果的精髓在于克制。
完整Swift实现代码
下面这段代码把上面提到的所有环节串成一个可复用的滤镜链,输入一张CIImage,输出带柔光效果的图像。intensity参数控制柔光强度,取值0到1,方便接到滑块上做实时预览。
import CoreImage
import CoreImage.CIFilterBuiltins
import UIKit
enum SoftLightFilter {
/// 生成柔光效果图像
/// - Parameters:
/// - input: 原始图像
/// - intensity: 柔光强度 0.0 ~ 1.0
static func apply(to input: CIImage, intensity: CGFloat = 0.6) -> CIImage? {
guard intensity > 0 else { return input }
// 模糊半径按图片短边比例计算,保证不同分辨率下表现一致
let shortSide = min(input.extent.width, input.extent.height)
let radius = shortSide * 0.02 * intensity
// 1. 高斯模糊提取低频信息
let blurFilter = CIFilter.gaussianBlur()
blurFilter.inputImage = input
blurFilter.radius = Float(radius)
guard var blurred = blurFilter.outputImage else { return nil }
// 2. 裁剪回原始尺寸,避免边缘透明扩展
blurred = blurred.cropped(to: input.extent)
// 3. Screen混合:高光溢出,暗部受保护
let screenFilter = CIFilter.screenBlendMode()
screenFilter.inputImage = blurred
screenFilter.backgroundImage = input
guard let screened = screenFilter.outputImage else { return nil }
// 4. 用透明度控制柔光强度
let alphaMatrix = CIColorMatrix()
let fade = 1.0 - intensity * 0.5
alphaMatrix.setValue(CIVector(x: 0, y: 0, z: 0, w: 0),
forKey: "inputRVector")
alphaMatrix.setValue(CIVector(x: 0, y: 0, z: 0, w: 0),
forKey: "inputGVector")
alphaMatrix.setValue(CIVector(x: 0, y: 0, z: 0, w: 0),
forKey: "inputBVector")
alphaMatrix.setValue(CIVector(x: 0, y: 0, z: 0, w: fade),
forKey: "inputAVector")
alphaMatrix.setValue(screened, forKey: kCIInputImageKey)
guard let faded = alphaMatrix.outputImage else { return nil }
// 5. 原图叠加柔光层
let composite = CIFilter.sourceOverCompositing()
composite.inputImage = faded
composite.backgroundImage = input
guard let result = composite.outputImage else { return nil }
// 6. 高光增强,让光感更通透
let highlight = CIFilter.highlightAdjust()
highlight.inputImage = result
highlight.highlight = Float(0.1 * intensity)
// 7. 轻微降饱和,模拟柔焦镜头质感
let colorControls = CIFilter.colorControls()
colorControls.inputImage = highlight.outputImage
colorControls.saturation = Float(1.0 - 0.08 * intensity)
return colorControls.outputImage
}
}
使用时只需要把UIImage转成CIImage,调用上面的方法,再用CIContext渲染回UIImage即可。滤镜链的构建是延迟执行的,真正消耗性能的只有最后渲染那一步,所以预览时可以先用低分辨率图渲染,确认效果后再用原图输出成品。
性能优化与实时预览实践
如果滤镜只是用于静态图片导出,性能压力不大,交给CIContext渲染一次就行。但如果要做相机实时预览,每秒要处理30到60帧,就必须认真对待性能。首先,创建CIContext是一个昂贵操作,务必全局复用一个实例,推荐用MTLCreateSystemDefaultDevice()创建基于Metal的上下文,渲染效率比CPU上下文高一个量级。
其次,高斯模糊是整条链路中最耗时的环节。可以在预览阶段降低输入分辨率,比如把模糊半径和输入图同时缩小一半,视觉效果差异不大,但计算量直接降到四分之一。另外,CIFilter对象本身可以重复使用,只需要更新参数而不必每帧重建滤镜链,Core Image内部会自动复用已编译的着色程序。
还有一种进阶方案是把整条滤镜链编译成CIColorKernel自定义Metal着色器,把模糊、混合、高光调整全部在一段shader里完成,避免中间纹理的反复读写。对于美颜相机这类对功耗敏感的场景,这条路线能把单帧处理时间压缩到几毫秒以内。此外,如果需要把滤镜效果导出为可分享的样式,可以用CIFilter的序列化能力,或者干脆把参数封装成JSON配置,方便服务端下发动态更新滤镜。
最后提一个调试技巧:开发阶段可以临时用CIImage的applyingFilter链式写法逐层输出中间结果,分别查看模糊层、Screen混合层和最终合成层,快速定位“画面发灰”或“黑边”这类问题出在哪一环。柔光滤镜的参数没有绝对标准,不同风格的图片适合的强度和半径差异很大,把关键参数暴露成可配置项,再配合LUT调色,就能扩展出一整套风格化滤镜产品。
Core Image柔光滤镜高斯模糊修改时间:2026-09-12 16:58:41