在摄影和计算机图形学领域,Bokeh(散景)效果特指镜头虚化区域的光斑呈现方式。这种效果不仅能突出画面主体,还能通过光斑形状和亮度分布营造独特的视觉氛围。通过Shader实现Bokeh效果,我们可以在实时渲染中模拟专业相机的大光圈虚化特性,为3D场景增加电影级景深表现。
传统后处理景深效果往往只做简单模糊处理,而物理准确的Bokeh Shader需要处理三个核心问题:光圈形状对光斑的影响、景深范围内外的过渡处理、以及高光区域的亮度衰减。接下来我将拆解一个基于Unity URP管线的实现方案,包含完整的Shader代码解析和参数调优指南。
Bokeh效果最显著的特征就是虚化光斑的形状,这直接由镜头光圈叶片数量决定。在Shader中,我们通过极坐标采样实现多边形光斑:
hlsl复制// 六边形光圈示例
float HexagonalBokeh(float2 uv, float rotation) {
float pi = 3.1415926;
float angle = atan2(uv.y, uv.x) + rotation;
float radius = length(uv);
// 将极坐标六等分
angle = mod(angle, pi/3) - pi/6;
return radius * cos(angle);
}
关键参数说明:
rotation:控制光圈旋转角度,避免光斑方向单一pi/3:将圆周六等分形成六边形,改为pi/4可得八边形cos(angle):确定多边形边界的距离场注意:实际实现时需要将距离场结果归一化到0-1范围,作为后续混合的蒙版
物理正确的景深需要基于镜头参数计算模糊半径:
hlsl复制float CalculateBlurRadius(float depth, float focusDistance, float aperture) {
float coc = abs(aperture * (focusDistance - depth) / (depth * (focusDistance - aperture)));
return clamp(coc, 0.0, MAX_BLUR_RADIUS);
}
参数关系表:
| 参数 | 作用 | 典型值 |
|---|---|---|
| focusDistance | 对焦平面距离 | 1.0-10.0 |
| aperture | 光圈系数 | 0.1-2.0 |
| MAX_BLUR_RADIUS | 最大模糊半径 | 0.05 |
真实镜头中高光区域的Bokeh会更明亮,需要单独提取处理:
hlsl复制float3 HighlightBokeh(float2 uv, float radius, float intensity) {
float threshold = 0.8; // 高光阈值
float3 color = SampleMainTexture(uv);
float luminance = dot(color, float3(0.2126, 0.7152, 0.0722));
if (luminance > threshold) {
float falloff = 1.0 - smoothstep(0.0, radius, length(uv));
return color * intensity * falloff;
}
return 0;
}
hlsl复制Shader "Custom/BokehDepth"
{
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_BlurRadius ("Blur Radius", Range(0, 0.1)) = 0.05
_Aperture ("Aperture", Float) = 0.5
_FocusDistance ("Focus Distance", Float) = 5.0
_BokehSides ("Bokeh Sides", Int) = 6
}
SubShader {
Pass {
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
// 完整实现代码...
// 包含前述算法模块
}
}
}
csharp复制// C#脚本中启用深度纹理
camera.depthTextureMode = DepthTextureMode.Depth;
csharp复制[Range(0.1f, 10f)] public float focusDistance = 5f;
void Update() {
material.SetFloat("_FocusDistance", focusDistance);
}
csharp复制void OnRenderImage(RenderTexture src, RenderTexture dest) {
Graphics.Blit(src, dest, bokehMaterial);
}
采用双重采样策略平衡质量与性能:
hlsl复制// 优化后的采样循环
for (int i = 0; i < SAMPLE_COUNT; i++) {
float angle = 2 * PI * i / SAMPLE_COUNT;
float2 offset = float2(cos(angle), sin(angle)) * radius;
float2 uv = iUV + offset * _MainTex_TexelSize.xy;
// 使用预计算的mipmap
color += SAMPLE_TEXTURE2D_X_LOD(_MainTex, sampler_LinearClamp, uv, 1);
}
针对移动平台的特殊处理:
| 风格类型 | Aperture | Blur Radius | Sides | 适用场景 |
|---|---|---|---|---|
| 电影感 | 1.2 | 0.08 | 7 | 角色特写 |
| 梦幻系 | 2.0 | 0.12 | 0 | 场景氛围 |
| 写实系 | 0.8 | 0.05 | 6 | 建筑展示 |
边缘锯齿问题:
性能瓶颈定位:
光斑不自然:
动态光圈系统:
镜头像差模拟:
物理镜头库:
在实际项目中,Bokeh效果需要与抗锯齿方案(如TAA)配合使用。我发现将模糊半径控制在屏幕宽度的5%-8%,同时保持高光区域的亮度在1.5-2倍范围内,能获得最自然的视觉效果。对于移动平台,可以考虑将Bokeh计算移到低分辨率层,再通过上采样混合,性能可提升40%以上。