效果
通过相机Render出一张RT,再对这张RT做高斯模糊,有2个好处:
避免事实运算,优化性能(实时高斯模糊只有15-20帧左右,静态几乎没性能影响)不适用Screen.Capture截屏的原因是,截屏有可能会截取到未渲染完成的画面,或者画面里有不希望出现的元素等。
C#层
using System
.Collections
.Generic
;
using UnityEngine
;
using UnityEngine
.Serialization
;
using UnityEngine
.UI
;
public class GaussianBlurUI : MonoBehaviour
{
public Camera
[] blurTargetCameras
;
private RenderTexture grabRenderTexture
;
private Material material
;
private Vector4
[] offsetAndWeights
= new Vector4[29];
private RenderTextureFormat rtFormat
;
private static List
<float> weights
= new List<float>();
[Range(1,4)]
[Header("RT的缩放因子,值越大尺寸越小")]
public int scaler
= 2;
[Range(1,4)]
[Header("模糊程度,值越高模糊越厉害")]
public int blurLevel
= 3;
public RawImage outputRawImage
;
private Dictionary
<int, int> blurLevelDict
= new Dictionary<int, int>()
{
{0, 7},{1,11},{2,19},{3,29}
};
public void Awake()
{
rtFormat
= SystemInfo
.SupportsRenderTextureFormat(RenderTextureFormat
.ARGBHalf
)
? RenderTextureFormat
.ARGBHalf
: RenderTextureFormat
.Default
;
Shader shader
= Shader
.Find("Custom/GaussianBlur");
material
= new Material(shader
);
material
.renderQueue
= 3000;
grabRenderTexture
= new RenderTexture(Screen
.width
/scaler
, Screen
.height
/scaler
,0, rtFormat
, RenderTextureReadWrite
.Linear
);
grabRenderTexture
.autoGenerateMips
= false;
}
private int GetPass()
{
Debug
.Assert(blurLevel
>=1&&blurLevel
<=4, "Invalid blur level!");
return blurLevel
- 1;
}
public void SetBlurTargetCamera(params Camera
[] cams
)
{
blurTargetCameras
= cams
;
}
public bool CaptureWithCamera()
{
if (outputRawImage
== null)
{
Debug
.LogError("must set outputRawImage!");
return false;
}
if (blurTargetCameras
== null || blurTargetCameras
.Length
== 0)
{
Debug
.LogError("blur error, need one camera at least!");
return false;
}
var pass
= GetPass();
var rt
= RenderTexture
.GetTemporary(grabRenderTexture
.width
, grabRenderTexture
.height
, 24, rtFormat
);
for (int i
= 0; i
< blurTargetCameras
.Length
; i
++)
{
Camera cam
= blurTargetCameras
[i
];
if (cam
== null || cam
.enabled
== false || cam
.gameObject
.activeInHierarchy
== false)
{
continue;
}
RenderTexture temp
= cam
.targetTexture
;
cam
.targetTexture
= rt
;
cam
.Render();
cam
.targetTexture
= temp
;
}
grabRenderTexture
.DiscardContents();
Graphics
.Blit(rt
, grabRenderTexture
);
RenderTexture
.ReleaseTemporary(rt
);
ProcessRenderTexture(pass
);
outputRawImage
.texture
= grabRenderTexture
;
return true;
}
private void ProcessRenderTexture(int pass
, RenderTexture rt
= null)
{
int nsamples2
= blurLevelDict
[pass
];
int nsamples
= nsamples2
* 2 - 1;
int width
= (nsamples
- 1) / 2;
if (rt
== null)
{
rt
= grabRenderTexture
;
}
GenerateGaussianWeights(width
, ref weights
);
var temp
= RenderTexture
.GetTemporary(rt
.width
, rt
.height
, 0, rtFormat
, RenderTextureReadWrite
.Linear
);
GenerateConvolutionFilter(ref weights
, width
, true, rt
.width
, rt
.height
, ref offsetAndWeights
);
material
.SetVectorArray("_OffsetAndWeights", offsetAndWeights
);
Graphics
.Blit(rt
, temp
, material
, pass
);
GenerateConvolutionFilter(ref weights
, width
, false, rt
.width
, rt
.height
, ref offsetAndWeights
);
material
.SetVectorArray("_OffsetAndWeights", offsetAndWeights
);
temp
.filterMode
= FilterMode
.Bilinear
;
grabRenderTexture
.DiscardContents();
Graphics
.Blit(temp
, rt
, material
, pass
);
RenderTexture
.ReleaseTemporary(temp
);
}
static float Gaussian(float x
, float s
)
{
return Mathf
.Exp(-(s
* x
) * (s
* x
));
}
static void GenerateGaussianWeights(int width
, ref List
<float> weights
)
{
weights
.Clear();
float s
= 3.0F / width
;
int size
= width
* 2 + 1;
float sum
= 0f;
for (int x
= 0; x
< size
; x
++)
{
weights
.Add(Gaussian(x
- width
, s
));
sum
+= weights
[x
];
}
for (int x
= 0; x
< size
; x
++)
{
weights
[x
] /= sum
;
}
}
void GenerateConvolutionFilter(ref List
<float> weights
, int width
, bool vertical
, int img_width
, int img_height
, ref Vector4
[] output
)
{
int nsamples
= 2 * width
+ 1;
int nsamples2
= (int) Mathf
.Ceil(nsamples
/ 2f);
for (int i
= 0; i
< nsamples2
; i
++)
{
float a
= weights
[i
* 2];
float b
;
if (i
*2+1>nsamples
- 1)
{
b
= 0;
}
else
{
b
= weights
[i
* 2 + 1];
}
float weight
= a
+ b
;
float offset
= b
*(a
+ b
);
float x_offset
= 0, y_offset
= 0;
if (vertical
)
{
y_offset
= i
* 2 - width
+ offset
;
}
else
{
x_offset
= i
* 2 - width
+ offset
;
}
x_offset
= x_offset
/ img_width
;
y_offset
= y_offset
/ img_height
;
output
[i
] = new Vector4(x_offset
, y_offset
, weight
, 0);
}
}
}
shader部分
half4 fragBlur7(v2f_img i): SV_Target{
half3 color = 0.0;
for(int s = 0; s < 7; s++){
half3 offsetAndWeight = _OffsetAndWeights[s].xyz;
half3 sample = tex2D(_MainTex, i.uv + offsetAndWeight.xy).xyz;
color += sample * offsetAndWeight.z;
}
return half4(color, 1.0);
}
half4 fragBlur11(v2f_img i): SV_Target{
half3 color = 0.0;
for(int s = 0; s < 11; s++){
half3 offsetAndWeight = _OffsetAndWeights[s].xyz;
half3 sample = tex2D(_MainTex, i.uv + offsetAndWeight.xy).xyz;
color += sample * offsetAndWeight.z;
}
return half4(color, 1.0);
}
half4 fragBlur19(v2f_img i): SV_Target{
half3 color = 0.0;
for(int s = 0; s < 19; s++){
half3 offsetAndWeight = _OffsetAndWeights[s].xyz;
half3 sample = tex2D(_MainTex, i.uv + offsetAndWeight.xy).xyz;
color += sample * offsetAndWeight.z;
}
return half4(color, 1.0);
}
half4 fragBlur29(v2f_img i): SV_Target{
half3 color = 0.0;
for(int s = 0; s < 29; s++){
half3 offsetAndWeight = _OffsetAndWeights[s].xyz;
half3 sample = tex2D(_MainTex, i.uv + offsetAndWeight.xy).xyz;
color += sample * offsetAndWeight.z;
}
return half4(color, 1.0);
}
RT上材质
Shader "Custom/GaussianImageEffect"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Cull Off ZWrite Off ZTest Always
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;
};
v2f vert(appdata v){
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
fixed4 frag(v2f i) : SV_TARGET{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb = pow(col.rgb, 1.25);
return col;
}
ENDCG
}
}
FallBack "Diffuse"
}