高斯模糊 【Unity Shader入门精要12.4】
Blit(src,dest,mat,pass)函数的作用,按照Unity官方API的说法是将src这个RT用mat这个材质中的某个pass渲染,然后复制到dest中。如果要给渲染加一些后处理效果(SSAO,HDR,bloom之类的),几乎可以肯定会用到这个函数。根据Unity自带文档中的例子,在OnRenderImage中调用Blit,然后用指定的mat渲染出来。
OnRenderImage(src,dest)是Camera的一个回调(message),他会在camera执行渲染时候被调用,官方给的大部分Image Effect的实现都是用了这个回调。
(记得分配新的RT,用完后手动 Release 掉)
https://blog.****.net/yaokang522/article/details/46789913/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//12.4 高斯模糊
public class GaussianBlur : PostEffectsBase {
//声明该效果需要的shader,并据此创建相应的材质
public Shader gaussianBlurShader;
private Material gaussianBlurMaterial = null;
public Material material
{
get
{//gaussianBlurShader是指定的shader,对应了本节所用的shader
gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
return gaussianBlurMaterial;
}
}
//在脚本中提供调整高斯模糊的参数
//【高斯模糊迭代次数】Bulr iterations - Larger number means more blur(越大越模糊)
[Range(0, 4)]
public int iterations = 3;
//【模糊范围】Blur spread for each iteration - larger value means more blur(越大越模糊)
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f;
//【缩放系数】
[Range(1, 8)]
public int downSpread = 2;
/*blurSpread,downSpread都是出于性能的考虑,在高斯核维数不变的情况下,_BlurSize越大,模糊程度越高,但采样数却不受到影响。
但过大的_BlurSize值会造成虚影。而downSpread越大,需要处理的像素数越少,同时也能进一步提高模糊程度,但过大的downSpread可能会使图像像素化*/
//one:最简单的OnRenderImage
/*
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material !=null)
{
int rtW = src.width;
int rtH = src.height;
//利用RenderTexture.GetTemporary函数分配一块与屏幕图像大小相同的缓冲区【buffer】
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
//高斯迷糊需要调用两个Pass,需要使用一块中间缓存来储存第一个Pass执行完毕之后得到的模糊结果
//Render the vertical pass(渲染垂直通道)
//使用shader中的第一个pass【0】,对【src】进行处理,并将结果储存在【buffer】中
Graphics.Blit(src, buffer, material, 0);
//Render the horizontal pass(渲染水平通道)
//使用shader中的第二个pass【1】,对【buffer】进行处理,并将结果储存在【dest】中
Graphics.Blit(buffer, dest, material, 1);
//释放缓存【buffer】
RenderTexture.ReleaseTemporary(buffer);
}else
{
Graphics.Blit(src, dest);
}
}*/
//two
//利用缩放对图像进行采样,从而减少需要处理的像素个数,提高性能
/* private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material !=null ) {
//与one不同的是在声明缓冲区大小时,用了小雨屏幕分辨率的尺寸
int rtW = src.width / downSpread;
int rtH = src.height / downSpread;
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
//将该临时渲染纹理的滤波模式设置为双线性
buffer.filterMode = FilterMode.Bilinear;
//
Graphics.Blit(src, buffer, material, 0);
//
Graphics.Blit(buffer, dest, material, 1);
RenderTexture.ReleaseTemporary(buffer);
}else
{
Graphics.Blit(src, dest);
}
}*/
//three,考虑了高斯模糊的迭代次数
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material !=null)
{
int rtW = src.width / downSpread;
int rtH = src.height / downSpread;
//定义第一个缓存【buffer0】
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
//将该临时渲染纹理的滤波模式设置为双线性
buffer0.filterMode = FilterMode.Bilinear;
//吧【src】中的图像缩放后储存到【buffer0】
Graphics.Blit(src, buffer0);
for (int i = 0; i < iterations; i++)
{
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
//定义第二个缓存【buffer1】
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
//使用shader中的第一个pass【0】,对【buffer0】进行处理,并将结果储存在【buffer1】中
Graphics.Blit(buffer0, buffer1, material, 0);
//释放【buffer0】
RenderTexture.ReleaseTemporary(buffer0);
//把【buffer1】储存到【buffer0】中
buffer0 = buffer1;
//重新分配【buffer1】
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
//调用第二个pass【1】重复。。。对【buffer0】进行处理,并将结果储存在【buffer1】中
Graphics.Blit(buffer0, buffer1, material, 1);
//释放【buffer0】
RenderTexture.ReleaseTemporary(buffer0);
//把【buffer1】储存到【buffer0】中
buffer0 = buffer1;
//【buffer0】将存储最终的图像
}
//把结果显示到屏幕上
Graphics.Blit(buffer0, dest);
//释放【buffer0】
RenderTexture.ReleaseTemporary(buffer0);
}
else
{
Graphics.Blit(src, dest);
}
}
}
//12.4 高斯模糊
Shader "Unlit/Chapter12-GaussianBlur"
{
Properties
{
_MainTex ("Base(RGB)", 2D) = "white" {}
_BlurSize("Blur Size",Float)=1.0
}
SubShader
{
/* CGINCLUDE
。。。。。。。。。
ENDCG
包含的代码不需要包含在任何Pass语义块中,使用的时候只需要在Pass中直接指定需要使用的顶点着色器和片元着色器函数名即可
CGINCLUDE类似C++头文件的功能.
由于高斯模糊需要定义两个Pass,但他们使用的片元着色器代码是完全相同的,使用CGINCIUDE可以避免编写两个完全一样的frag.
*/
CGINCLUDE
#include "UnityCG.cginc"
//定义属性对应变量
sampler2D _MainTex;
//由于要用到相邻像素的纹理坐标,使用unity提供的_MainTex_TexelSize变量,以计算相邻像素的纹理坐标偏移量
half4 _MainTex_TexelSize;
float _BlurSize;
//分别定义两个Pass将要使用的顶点着色器
struct v2f
{
float4 pos : SV_POSITION;
half2 uv[5]:TEXCOORD0;
};
//竖直方向顶点着色器代码
v2f vertBlurVertical(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
//数组的第一个坐标储存了当前的采样,而剩余的四个坐标则是高斯模糊中对邻域采样使用的纹理坐标,属性_BlurSize控制采样距离
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;
}
//水平方向顶点着色器代码
v2f vertBlurHorizontal(appdata_img v) {
v2f 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;
}
//定义李定义两个Pass共用的片元着色器
fixed4 fragBlur(v2f i) : SV_Target{
//一个5*5的二维高斯核可以拆分成两个大小为5的一维高斯核,并且由于对称性,只需要记录3个高斯权重,也就是代码的Weight变量
float weight[3] = { 0.4026, 0.2442, 0.0545 };
//将sum初始化为当前的像素值乘以它的权重值
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
//根据对称性,我们进行两次迭代,每次迭代包含两次纹理采样,并把像素值和权重相乘后的结果叠加到sum中
for (int it = 1; it < 3; it++) {
sum += tex2D(_MainTex, i.uv[it ]).rgb * weight[it];
sum += tex2D(_MainTex, i.uv[it +1]).rgb * weight[it];
/*
//书上原来是这个,做了一点改动,
sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
改动后的对应关系如下:
|权重|weight[2] | weight[1] |weight[0] |weight[1] |weight[2]
| uv | uv[3] | uv[1] | uv[0] | uv[2] | uv[4]
| Y |uv+(0,2) | uv+(0,1) | uv+(0,0) |uv+(0,-1) |uv+(0,-2)
| X |uv+(2,0) | uv+(1,0) | uv+(0,0) |uv+(-1,0) |uv+(-2,0)
2【uv 3】
1【uv 1】
-2【uv 4】 -1【uv 2】 0【uv 0】 1【uv 1】 2【uv 3】
-1【uv 2】
-2【uv 4】
*/
}
//最后函数返回滤波结果sum
return fixed4(sum, 1.0);
}
ENDCG
ZTest Always Cull Off Zwrite Off
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
}
}
FallBack "Diffuse"
}