在看各种效果的时候发现会有一些亮条的感觉,十分的漂亮,因此自己写点笔记记录一下思路。
卷积
在对图像处理的时候,我们经常会考虑到他周围的像素颜色对他的影响,我们在这称之为卷积以上言论均是我的个人理解。在《UnityShader入门精要》中是这样解释的
在图像处理中,卷积操作指的就是使用一个卷积核(kernel)对一张图像中的每个像素进行一系列操作。卷积核通常是一个四方形网格结构。(例如3X3的方形区域),该区域内每个方格都有一个权重值。当对图像中的某个像素进行卷积时,我们会把卷积核的中心放置于该图像上,依次计算核中每个元素和其覆盖的图像像素值的乘积并求和,得到的结果就是该位置的新像素值。
高斯模糊
高斯模糊用到卷积核也被称之为高斯核。利用高斯核给图像卷积既可以获得一个模糊的图像。高斯核大约长得像一个3x3的样子,其中的值便是他的权重,我们将这个核放在对应的位置,这样便可以根据权重来计算这九个像素的值,然后我们进行归一化,即将每一个像素除以九个点的权重和,才能得到最后的矩阵.然后中心点的颜色值便是这九个矩阵的值加起来.
某个像素点的颜色灰度值
乘以权重值
最终结果
加起来便是中心点的新值.从而达到了模糊图片的目的.以上全是个人理解,如有错误立马更改,而由于卷积核的性质,会导致图片小一圈,我们的解决方法是当图片在边缘时,会将对立边缘的值拿来用.相当于是一个圆柱一样).假设高斯核为NxN,图片长宽为W与H,那么最终纹理采样的次数为 N * N * W * H次,会很容易的变大.不过幸好我们可以讲一个二维的高斯核拆成两个一维的.这样就变成了2 * W * H * N次了.具体的代码我们可以去这里查看.
然后我们可以通过多次迭代来达到更模糊的感觉.
C#脚本如下
public class GaussianBlur : MonoBehaviour
{
[Range(0, 4)]
//迭代次数
public int iterations = 3;
[Range(0.2f, 3f)]
//模糊程度
public float blurSpread = 0.6f;
[Range(1, 8)]
//图片缩放
public int downSample = 2;
public Material material;
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if(material != null)
{
int rtx = source.width/downSample;
int rty = source.height/downSample;
RenderTexture buffer1 = RenderTexture.GetTemporary(rtx, rty);
buffer1.filterMode = FilterMode.Bilinear;
Graphics.Blit(source, buffer1);
for (int i = 0; i < iterations; i++)
{
material.SetFloat("_BlurSize",1.0f +i *blurSpread);
RenderTexture buffer2 = RenderTexture.GetTemporary(rtx, rty,0);
Graphics.Blit(buffer1, buffer2,material,0);
RenderTexture.ReleaseTemporary(buffer1);
buffer1 = buffer2;
buffer2 = RenderTexture.GetTemporary(rtx, rty,0);
Graphics.Blit (buffer1, buffer2,material,1);
RenderTexture.ReleaseTemporary(buffer1);
buffer1 = buffer2;
}
Graphics.Blit(buffer1,destination);
RenderTexture.ReleaseTemporary(buffer1);
}
}
}
最终效果图
Bloom效果
既然了解了高斯模糊的原理,那么Bloom的原理也就很清楚了,在图中选出想要的颜色,然后将其单独提到一个Pass来处理,将他高斯模糊了以后再与原来的图像混合,这样就会出现一种泛出光芒将周围都掩盖的光亮效果.
Bloom的效果也是十分的简单。
- 获得图中较亮区域的片元。
- 将这些片元用一个pass来进行高斯模糊,
- 将原本的图片与高斯模糊的图片相加
我们在第一个pass块中来每个片元的计算对应的强度值。并储存在一个缓冲区中,(这里的提取高亮区域的算法测试下来对白色的支持比较好,对其他的颜色支持较差,其他颜色的提取算法暂时不知道)
fixed luminance(fixed4 color){
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
fixed4 frag(v2f i):SV_Target{
fixed4 c = tex2D(_MainTex,i.uv);
fixed val =clamp(Luminance(c) - _LuminanceThreshold,0.0,1.0);
return c * val;
}
然后将这个缓冲区的图片进行高斯模糊,做法和前面讲的做法是一样的,因此这里不再过多的讲解,这里将一个小技巧:我们可以利用给Pass起名字的方法来达到pass复用的目的,前面高斯模糊中我们已经横竖两个pass块给命名了。那么我们在Bloom的shader中可以直接这样写。
pass{
CGPROGRAM
#pragma vertex vertExtractBright
#pragma fragment frag
ENDCG
}
UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"
UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"
pass{
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom
ENDCG
}
其中的UsePass便是代码的重用,可以跟正常的pass一样使用
最后一个pass便是将我们的两个贴图合并,代码如下
v2fBloom vertBloom(appdata_img v){
v2fBloom o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord;
o.uv.zw = v.texcoord;
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0.0){
o.uv.w = 1 - o.uv.w;
}
#endif
return o;
}
fixed4 fragBloom(v2fBloom i):SV_Target{
return tex2D(_MainTex,i.uv.xy) + tex2D(_Bloom,i.uv.zw);
}
其中的宏命令是为了处理平台差异。
C#端的代码如下,与高斯模糊下的差距不大
public class Bloom : MonoBehaviour
{
[Range(0, 4)]
public int iterations = 3;
[Range(0.2f, 3f)]
public float blurSpread = 0.6f;
[Range(1, 8)]
public int downSample = 2;
[Range(0.0f, 1.0f)]
public float luminanceThreshold = 0.6f;
public Material material;
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (material != null)
{
material.SetFloat(“_LuminanceThreshold”, luminanceThreshold);
int rtx = source.width / downSample;
int rty = source.height / downSample;
RenderTexture buffer1 = RenderTexture.GetTemporary(rtx, rty);
buffer1.filterMode = FilterMode.Bilinear;
Graphics.Blit(source, buffer1,material,0);
for (int i = 0; i < iterations; i++)
{
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
RenderTexture buffer2 = RenderTexture.GetTemporary(rtx, rty, 0);
Graphics.Blit(buffer1, buffer2, material, 1);
RenderTexture.ReleaseTemporary(buffer1);
buffer1 = buffer2;
buffer2 = RenderTexture.GetTemporary(rtx, rty, 0);
Graphics.Blit(buffer1, buffer2, material, 2);
RenderTexture.ReleaseTemporary(buffer1);
buffer1 = buffer2;
}
material.SetTexture("_Bloom", buffer1);
Graphics.Blit(source,destination,material,3);
RenderTexture.ReleaseTemporary(buffer1);
}
}
}
Shader代码如下
Shader "Unlit/Bloom"
{
Properties
{
_MainTex ("Texture", 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;
float4 _MainTex_TexelSize;
sampler2D _Bloom;
float _LuminanceThreshold;
float _BlurSize;
struct v2f{
float4 pos: SV_POSITION;
float2 uv :TEXCOORD0;
};
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;
}
fixed4 frag(v2f i):SV_Target{
fixed4 c = tex2D(_MainTex,i.uv);
fixed val =clamp(Luminance(c) - _LuminanceThreshold,0.0,1.0);
return c * val;
}
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;
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0.0){
o.uv.w = 1 - 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 Always
Cull off
ZWrite off
pass{
CGPROGRAM
#pragma vertex vertExtractBright
#pragma fragment frag
ENDCG
}
UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"
UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"
pass{
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom
ENDCG
}
}
}