炫光(Bloom)
焦点不完美(胶片与眼睛)
泛光效果是由光学设计缺陷产生的。这种现象在镀膜质量欠佳的旧镜头中尤为显著。当光线通过这些透镜时,寄生照明现象会在内部产生,并随后在薄膜乳剂层中发生散射。最终,漫反射光晕会出现在光源周围、场景的明亮区域以及高对比度边缘。
虽然泛光效果与胶片本身没有直接关联,但正是胶片的特性使其呈现出独特的视觉魅力。额外的光散射源于乳剂层的轻微不均匀性,当卤化银形成晶粒簇时,这种光散射效应会随着显影过程进一步加剧。
因此,原本平滑规则的光学表现逐渐变得非线性且富有层次,展现出更自然的视觉效果。
使用老式镜头、特殊光学镜头或散景滤镜(如Pro-Mist)时,泛光效果最为显著。由于这种效果的产生机制既涉及光学系统也依赖胶片乳剂特性,因此在采用小画幅胶片(特别是8毫米胶片)的拍摄场景中尤为突出:
8 mm 电影胶片扫描
泛光现象的一个物理基础在于,现实中的镜头永远无法实现完美聚焦。即使是理论上的完美镜头,也会将入射图像与艾里斑(由点光源通过圆形孔径产生的衍射图案)进行卷积处理。在常规条件下,这种光学瑕疵并不明显,但当遇到强光源时,这些瑕疵就会显著显现。因此,强光物体的影像会呈现出超越其自然边界的视觉效果。
艾里斑函数具有快速衰减的特性,但其尾部扩散范围极宽(理论上具有无限宽度)。当图像相邻区域的亮度值处于相近范围时,艾里斑引起的模糊效应并不显著;但在明亮区域与暗部区域交界处,艾里斑的尾部会变得可见,并且其影响范围可以远远超出原亮部区域。
在HDR图像处理过程中,可以通过在像素值归一化前应用特定算法来模拟该现象:对于高精度镜头使用艾里斑窗函数进行卷积运算,或通过高斯模糊算法模拟普通镜头的成像效果。传统非HDR成像系统无法完整复现该效果,因为光晕溢出程度与光源亮度呈正相关关系。
例如在室内拍摄时,透过窗户可见的室外景物亮度可能达到室内物体的70-80倍。当相机按照室内场景设定曝光参数时,窗外高亮区域的影像经艾里斑卷积处理后,就会产生光晕溢出窗框的视觉效果。
艾里斑
艾里斑是点光源通过衍射受限成像时,由于衍射而在焦点处形成的光斑。中央是明亮的圆斑,周围有一组较弱的明暗相间的同心环状条纹,把其中以第一暗环为界限的中央亮斑称作艾里斑。这个光斑的大小可以用下面的公式来估计:
CCD 传感器饱和度(数码相机)
CCD 图像中的光晕
数码相机中的光晕是由光电二极管中的电荷溢出引起的,光电二极管是相机图像传感器中的感光元件。[3]当光电二极管暴露在非常明亮的光源下时,积累的电荷会溢出到相邻像素中,从而产生光晕效果。这被称为“电荷出血”。
泛光效果在像素较小的相机中更为明显,因为电荷消散的空间较小。高 ISO 设置也会加剧这种情况,这会增加相机对光线的敏感度,并可能导致更多的电荷积累。
计算机中的泛光
泛光(有时称为光泛光或发光)是一种计算机图形效果,用于视频游戏、演示和高动态范围渲染 (HDRR),用于再现真实摄像机的成像伪影。该效果会产生从图像中明亮区域的边界延伸的光条纹(或羽化),从而产生极亮的光线压倒相机或眼睛捕捉场景的错觉。在 Tron 2.0 的作者于 2004 年发表了一篇关于该技术的文章后,它被广泛用于视频游戏。
实现思路
- 1.首先根据一个阙值提出图像中的较亮区域,并将它们存储在一张渲染纹理中
- 2.利用高斯模糊对这张渲染纹理进行模糊处理,模拟光线扩散的效果
- 3.与原图混合

前置知识
- HDR与LDR
- 高斯模糊
HDR与LDR
- LDR(Low Dynamic Range,低动态范围)
- JPG\PNG格式图片
- RBG范围在[0,1]之间
- 用0-1表达真实世界的色彩是不够的
- HDR(High Dynamic Range,高动态范围)
- HDR\EXR格式图片
- RGB范围可在[0,1]之外 如果不用HDR,只能提取亮度小于1的区域,可能路灯与水洼的亮度一样,都会产生bloom效果,但是使用HDR,就可以提取更亮的地方,让更亮的区域产生bloom效果。超过1的地方

高斯模糊(Gaussian Blur)
- 一种图像模糊
- 减少图像噪声,降低细节层次
公式:
σ 是标准方差(一般取值为1),x和y对应了当前位置到卷积核中心的整数距离,只需要计算高斯核中各个位置对应的高斯值,需要对高斯值进行归一化,让每个权重除以所有权重的和,
- 卷积
-
- 一种图像操作
- 需要一个卷积核(3*3等等)
Feature Map的值为0是
- 每个位置对应相乘,最后相加
- 计算高斯核
- 计算核中心(0,0)
- 核大小
- 标准方差:1.5
⭐归一化的作用是:为了保证卷积后的图像不会变暗

算法实现
- 调用onRenderImage函数
- Shader:使用3个Pass完成Bloom效果
入门精要Bloom的实现
命名为Gaussian但是实现的是bloom效果
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Gaussian : PostEffectsBase
{
public Shader GaussianShader;
private Material GaussianMat = null;
// Blur iterations - larger number means more blur.
[Range(0,4)]
public int iterations = 1;
//控制渲染纹理的大小
[Range(1,8)]
public int downSample = 2;
[Range(0.0f, 4.0f)]
public float luminanceThreshold = 0.6f;
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f;
public Material material
{
get
{
GaussianMat = CheckShaderAndCreateMaterial(GaussianShader, GaussianMat);
return GaussianMat;
}
}
protected void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (material != null) {
//设置亮度阙值
material.SetFloat("_LuminanceThreshold", luminanceThreshold);
//定义长宽高
int rtW = source.width / downSample;
int rtH = source.height / downSample;
//定义buffer0缓冲区的大小,设置buffer0的过滤设置,并存储buffer0
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW,rtH,0);
buffer0.filterMode = FilterMode.Bilinear;
Graphics.Blit(source, buffer0, material, 0);
//for循环,iteration次数
for (int i = 0; i < iterations; i++)
{
///for:
///设置blurSize
///新定义RenderTexture buffer1
///Render the vertical pass
///release buffer0
///buffer1赋值给buffer0
///re-define buffer1
///Render the horizontal pass
///释放buffer0
///buffer1赋值给buffer0
material.SetFloat("_BlurSize",1.0f + blurSpread*i);
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
Graphics.Blit(buffer0,buffer1,material,1);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
Graphics.Blit(buffer0, buffer1, material, 2);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
//mat中将buffer0赋予给_Bloom
material.SetTexture("_Bloom",buffer0);
//渲染图像到des上
Graphics.Blit(source,destination,material,3);
//释放buffer0
RenderTexture.ReleaseTemporary(buffer0);
}
else
{
Graphics.Blit(source,destination);
}
}
}
Shader "Custom/Guassian"
{
Properties {
//定义贴图材质 2D
//_Bloom值 2D
//亮度阙值,float
//模糊尺寸 Float
_MainTex ("Base (RGB)", 2D) = "white" {}
_Bloom ("Bloom (RGB)", 2D) = "black" {}
_LuminanceThreshold ("Luminance Threshold", Float) = 0.5
_BlurSize ("Blur Size", Float) = 1.0
}
SubShader {
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _Bloom;
float _LuminanceThreshold;
float _BlurSize;
//定义v2f
///pos
///uv
struct v2f {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
//提取亮度的顶点着色器,appdata_img
///顶点转换,uv赋值
v2f vertExtractBright(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed luminance(fixed4 color) {
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
//提取亮度的片元着色器
///先进行纹理采样
///进行亮度提取,采样得到的亮度值减去阙值,并截取到0-1中
///得到的值和原像素值相乘,得到提取后的亮部区域
fixed4 fragExtractBright(v2f i) : SV_Target {
fixed4 c = tex2D(_MainTex, i.uv);
fixed val = clamp(luminance(c) - _LuminanceThreshold, 0.0, 1.0);
return c * val;
}
//-----------高斯模糊----------------//
struct v2fGaussian {
float4 pos : SV_POSITION;
half2 uv[5]: TEXCOORD0;
};
v2fGaussian vertBlurVertical(appdata_img v) {
v2fGaussian o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
return o;
}
v2fGaussian vertBlurHorizontal(appdata_img v) {
v2fGaussian o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
return o;
}
fixed4 fragBlur(v2fGaussian i) : SV_Target {
float weight[3] = {0.4026, 0.2442, 0.0545};
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
for (int it = 1; it < 3; it++) {
sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
}
return fixed4(sum, 1.0);
}
///--------混合亮部图像和原图像-----------
///pos和uv,这里的uv是四维xy:_MainTex zw:_Bloom
struct v2fBloom {
float4 pos : SV_POSITION;
half4 uv : TEXCOORD0;
};
//顶点着色器
v2fBloom vertBloom(appdata_img v) {
v2fBloom o;
o.pos = UnityObjectToClipPos (v.vertex);
o.uv.xy = v.texcoord;
o.uv.zw = v.texcoord; //zw是_Bloom,即模糊后的较亮区域的纹理坐标
//平台差异化处理
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0.0)
o.uv.w = 1.0 - o.uv.w;
#endif
return o;
}
fixed4 fragBloom(v2fBloom i) : SV_Target {
return tex2D(_MainTex, i.uv.xy) + tex2D(_Bloom, i.uv.zw);
}
ENDCG
//关闭ZTest ,剔除,深度写入
ZTest Always Cull Off ZWrite Off
Pass {
CGPROGRAM
#pragma vertex vertExtractBright
#pragma fragment fragExtractBright
ENDCG
}
//use pass 的名字会转换成大写字母,使用时要用大写
//usepass in GAUSSIAN_BLUR_VERTICAL
//usepass in GAUSSIAN_BLUR_HORIZONTAL
Pass {
NAME "GAUSSIAN_BLUR_VERTICAL"
CGPROGRAM
#pragma vertex vertBlurVertical
#pragma fragment fragBlur
ENDCG
}
Pass {
NAME "GAUSSIAN_BLUR_HORIZONTAL"
CGPROGRAM
#pragma vertex vertBlurHorizontal
#pragma fragment fragBlur
ENDCG
}
Pass {
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom
ENDCG
}
}
FallBack Off
}
No Comments