bloom效果属于场景后期美化特效最常用而且最好用的,我记得以前做unity的时候,那个年代基本上就没有很好的场景美化规范和教程,所以市面上整体做出来的游戏或者软件场景原始效果相比现在都很差。毕竟那时候PBR渲染国内都没多少人会用,甚至都没听过,所以基本上很多场景美化效果都是通过unity自带的ImageEffect解决的,最常用的就是bloom,甭管什么单调的场景,bloom一下就会变得好看很多,而下面对bloom的实现来说明一下。
首先看下高斯滤波器,高斯滤波器在图形渲染中主要作用是高斯模糊,不同于刚开始用convolution时候使用的平均模糊滤波器,高斯模糊效果在非均匀模糊的前提下带有一定正态分布特性,这高斯滤波器的详解:高斯模糊 高斯滤波
高斯滤波具有正态分布特性(正态分布),正态分布采样曲线类似于一座山峰(和sin cos函数图像类似,当然计算公式是不一样的),百度找的图片,如下:
这是二维的正态分布图像,下面是高斯滤波方程(一维和二维参数方程表达式)和其三维正态分布图像:
u,v代表了距离中心的x,y像素距离数值,通过高斯滤波方程采样图像可以看得出来,越中心权重值越大,越边缘几乎平整了,
既然知道了公式和其意义,我们就来实现一下,如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GaussControl : MonoBehaviour
{
[SerializeField] public Material gaussMat;
private float e = 2.718281828f; /*无理数e*/
private float sigma = 1f; /*西格玛参数*/
private const int n = 3; /*n维矩阵,n为奇数*/
private float[] gaussMatrix = new float[n * n]; /*gauss矩阵*/
void Start()
{
getGaussMatrix();
getGaussNormalized();
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
gaussMat.SetFloatArray("gaussMatrix", gaussMatrix);
Graphics.Blit(source, gaussMat);
}
//对高斯矩阵数值归一化
//因为滤波点乘算法会因为滤波矩阵总体数值偏小或偏大导致得到的RGB会偏暗或偏白
//归一化后会得到变化不大的明暗度
private void getGaussNormalized()
{
float sum = 0;
for (int i = 0; i < n * n; i++)
{
sum += gaussMatrix[i];
}
for (int i = 0; i < n * n; i++)
{
gaussMatrix[i] = gaussMatrix[i] / sum;
}
}
//构建n*n的高斯采样矩阵
private void getGaussMatrix()
{
int c = n / 2;
for (int i = 0; i < n; i++)
{
for (int k = 0; k < n; k++)
{
float gauss = getGaussSamp(Mathf.Abs(i - c), Mathf.Abs(k - c));
gaussMatrix[i * n + k] = gauss;
#if UNITY_EDITOR
Debug.LogFormat("i = {0} k = {1} gauss = {2}", i, k, gaussMatrix[i * n + k]);
#endif
}
}
}
//高斯采样
private float getGaussSamp(int x, int y)
{
return 1 / (2 * Mathf.PI * sigma * sigma) * Mathf.Pow(e, -(x * x + y * y) / (2 * sigma * sigma));
}
}
首先我们通过c#代码在start中构建高斯滤波采样矩阵,同时对高斯滤波矩阵归一化,因为滤波器矩阵数值过大或过小再点乘计算后会对中心RGB照成过白或过黑,也就是过明亮或过暗淡,所以得归一化。
Shader "Unlit/GaussBlurShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_GaussSpread("Gauss Spread",Range(0,20)) = 1
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv[9] : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _MainTex_TexelSize;
int _GaussSpread; /*高斯扩散*/
uniform float gaussMatrix[9]; /*外部传递gauss矩阵*/
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
float2 uv = TRANSFORM_TEX(v.uv, _MainTex);
int c = 1;
//从上到下从左到右依次计算卷积核对应的9个像素uv
for (int x = 0; x < 3; x++)
{
for (int y = 0; y < 3; y++)
{
o.uv[x * 3 + y] = uv + _MainTex_TexelSize.xy*float2((y - c)*_GaussSpread, (c - x)*_GaussSpread); /*我们可以扩散一些像素达到更加模糊的效果*/
}
}
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = fixed4(0,0,0,0);
for (int k = 0; k < 9; k++)
{
col += tex2D(_MainTex, i.uv[k])*gaussMatrix[k];
}
col.a = 1;
return col;
}
ENDCG
}
}
}
接下来我们实现shader代码,高斯滤波矩阵通过uniform传入,其他的做法和以前滤波计算一样,除了额外增加一个_GaussSpread混合相对中心像素更远的像素值,达到更模糊的效果,具体的效果如下:
可以看得出来在一定spread范围内,高斯模糊效果明显提升,然后超过某个数值后,就产生重影了。
我看了其他高斯模糊做法,比如shader大神们使用的两个pass进行横纵高斯滤波,同时一次使用更多像素进行滤波,比如一般都是使用五个像素,而我为了节约时间使用的3*3像素矩阵,同学们有时间或者需要就改成五个双pass横纵滤波就行。
再回到Bloom效果,Bloom效果需要使用高斯模糊,我们将原始纹理“明亮”部分提取转存到另一份纹理,然后对这份纹理进行高斯模糊加“提亮”,然后“混合”到原始纹理中,则能达到Bloom效果,接下来我们一步一步实现,如下:
//pass1 只负责提取场景明亮度较高纹理像素,然后组成bloom纹理
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _BloomLuminous; /*bloom亮度阀值,超过则采样颜色*/
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
//rgb转yuv,提取y
float RgbToY(fixed4 col)
{
return 0.299 * col.r + 0.587 * col.g + 0.114 * col.b;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
float luminous = RgbToY(col);
//采样场景中亮度超过阀值的颜色
if (luminous > _BloomLuminous)
return col;
else
return fixed4(0, 0, 0, 0);
}
ENDCG
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BloomControl : MonoBehaviour
{
public Material bloomMat;
public int downSample = 2;
void Start()
{
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
int rtW = source.width / downSample;
int rth = source.height / downSample;
//首先我们降低采样纹理大小,为了效率考虑
RenderTexture bloomRT = RenderTexture.GetTemporary(rtW, rth, 0);
bloomRT.filterMode = FilterMode.Bilinear;
//使用bloom shader的亮度采样pass块对场景中亮度高于阀值的颜色值进行采样重组bloomRenderTexture
Graphics.Blit(source, bloomRT, bloomMat, 1);
//设置bloom shader的main texture和bloom texture
bloomMat.SetTexture("_MainTex", source);
bloomMat.SetTexture("_BloomTex", bloomRT);
//最后对_MainTex和_BloomTex混合产生bloom效果
Graphics.Blit(source, destination, bloomMat, 0);
}
}
首先我们使用Graphics对场景纹理进行Pass1的亮度采样,将场景纹理传递bloom shader的Pass1专门用来判断亮度采样bloom纹理,效果如下:
可以看得出来根据luminous阀值的变化,采样也会发生变化。接下来我们将场景主纹理和bloom纹理进行叠加,同时对bloom纹理进行高斯滤波,产生模糊发散的效果,如下:
//pass0 混合主纹理和高斯滤波bloom纹理产生bloom效果
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 muv : TEXCOORD0;
float2 uv[9] : TEXCOORD1;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _MainTex_TexelSize;
sampler2D _BloomTex;
float4 _BloomTex_ST;
int _BloomSpread; /*bloom扩散*/
float _BloomWeight; /*bloom权重*/
//3*3高斯滤波矩阵
static float gaussMatrix[9] = {
0.05854983,
0.09653235,
0.05854983,
0.09653235,
0.1591549,
0.09653235,
0.05854983,
0.09653235,
0.05854983
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.muv = TRANSFORM_TEX(v.uv, _MainTex);
//采样3*3像素uv矩阵
int c = 1;
for (int x = 0; x < 3; x++)
{
for (int y = 0; y < 3; y++)
{
o.uv[x * 3 + y] = o.muv + _MainTex_TexelSize.xy*float2((y - c)*_BloomSpread, (c - x)*_BloomSpread);
}
}
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//采样主纹理颜色
fixed4 col = tex2D(_MainTex, i.muv);
//滤波bloom颜色
fixed4 bcol = fixed4(0, 0, 0, 0);
for (int k = 0; k < 9; k++)
{
bcol += tex2D(_BloomTex, i.uv[k])*gaussMatrix[k];
}
//权重叠加
col += bcol * _BloomWeight;
return col;
}
ENDCG
}
bloom效果如下:
可以看出高斯模糊bloom效果吧。