屏幕后处理——Bloom
来自于《Unity Shader 入门精要》书本的学习
先上图
代码分3部分
1.PostEffectsBase.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; //在编辑器状态下可执行该脚本来查看效果 [ExecuteInEditMode] //屏幕后处理特效一般需要绑定在摄像机上 [RequireComponent(typeof(Camera))] public class PostEffectsBase : MonoBehaviour { void Start () { CheckResources(); } protected void CheckResources() { bool isSupported = CheckSupport(); //如果显卡检测 返回false if (isSupported == false) { //NotSupported()方法,即不显示 NotSupported(); } } //检查显卡是否支持 protected bool CheckSupport() { //如果显卡不支持图像后期处理 if (SystemInfo.supportsImageEffects == false) { //返回false return false; } //如果支持图像后处理,返回true return true; } //图像不显示 protected void NotSupported() { enabled = false; } //CheckShaderAndCreateMaterial函数接受两个参数,第一个参数指定了改特效需要使用的Shader //第二个参数则是用于后期处理的材质。该函数首先检查Shader的可用性,检查通过后就返回一个使 //用了该shader的材质,否则返回null. protected Material CheckShaderAndCreateMaterial(Shader shader, Material material) { //如果shader为空 if (shader == null) { return null; } //shader.isSupported:能在终端用户的图形卡上运行这个着色器&& 并且存在material 他的shader是我们输入的shader if (shader.isSupported && material && material.shader == shader) { return material; } if (!shader.isSupported) { return null; } //上面都不满足的话,重新创建新的材质球 else { material = new Material(shader); //hideFlags:控制对象销毁的位掩码 //HideFlags.DontSave对象不会保存到场景中。加载新场景时不会被破坏。 material.hideFlags = HideFlags.DontSave; return material; } } }
2.bloom.using System.Collections;
using System.Collections.Generic; using UnityEngine; public class bloom : PostEffectsBase { public Shader bloomShader; private Material bloomMaterial; public Material material { get { //根据PostEffectsBase中的方法检测,第一个参数指定了该特效需要使用的Shader,第二个参数则是用于后期处理的材质; //该函数首先检查Shader的可用性,检查通过后返回一个使用了该shader的材质,否则返回Null. bloomMaterial = CheckShaderAndCreateMaterial(bloomShader, bloomMaterial); return bloomMaterial; } } //高斯模糊的叠带次数 [Range(0, 4)] public int iterations = 3; //高斯模糊的叠带范围 [Range(0.2f, 4.0f)] public float blurSpread = 0.6f; //降采样的数值 [Range(1, 8)] public int downSample = 2; //luminanceThreshold,大多数情况下图像亮度不会超过1.但如果我们开启了HDR,硬件会允许我们把颜色值储存在一个更高精度范围的缓冲中, //此时像素的亮度就会超过1. [Range(0.0f, 4.0f)] public float luminanceThreshold = 0.6f; //OnRenderImage函数 void OnRenderImage (RenderTexture src , RenderTexture dest) { if (material != null) { material.SetFloat("_luminanceThreshold", luminanceThreshold); //将图像进行降采样不仅可以减少需要处理的像素,提高性能,而且适当的降采样旺旺还可以得到更好的模糊效果 int rtW = src.width / downSample; int rtH = src.height / downSample; //定义第一个缓存buffer0,并吧src中的图像缩放后储存到buffer0中。 RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0); buffer0.filterMode = FilterMode.Bilinear; //调用shader中的第一个Pass提取图像中较亮的区域,提到的较亮区域将储存在buffer0 中。 Graphics.Blit(src, buffer0, material, 0); for (int i = 0; i < iterations; i++) { material.SetFloat("_BlurSize", 1.0f + i * blurSpread); //定义buffer1 RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0); //调用第二个pass,输入buffer0,输出buffer1. Graphics.Blit(buffer0, buffer1, material, 1); RenderTexture.ReleaseTemporary(buffer0); //将输出的buffer1重新赋值给buffer0 buffer0 = buffer1; buffer1 = RenderTexture.GetTemporary(rtW, rtH,0); //调用第三个pass,输入buffer0(上面输出的buffer1),输出buffer1(新的buffer1) Graphics.Blit(buffer0, buffer1, material, 2); //将新的buffer1再次给buffer0赋值 buffer0 = buffer1; } //第四个Pass,将buffer0赋值给贴图_Bloom material.SetTexture("_Bloom", buffer0); Graphics.Blit(src, dest, material, 3); RenderTexture.ReleaseTemporary(buffer0); } else { Graphics.Blit(src, dest); } } }
3.bloom.shader
Shader "Unlit/Bloom" { Properties { _MainTex ("Texture", 2D) = "white" {} //高斯模糊较亮的区域 _Bloom ("Bloom(RGB)",2D) = "Black"{} //阔值,提取大于这个亮度的区域 后面将在 大于这个值 的区域里进行高斯模糊 _luminanceThreshold("luminanceThreshold",Float) = 0.5 //控制不同迭代之间高斯模糊的模糊区域范围 也就是uv偏移的范围 _BlurSize ("Blur Size",Float) = 1.0 } SubShader { CGINCLUDE #include "UnityCG.cginc" sampler2D _MainTex; half4 _MainTex_TexelSize; sampler2D _Bloom; Float _luminanceThreshold; float _BlurSize; ///提取交亮区域的 顶点.片元 着色器 struct v2f { float4 pos :SV_POSITION; half2 uv :TEXCOORD0; }; v2f vertExtractBright (appdata_img v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; return o; } //通过主贴图得到一个灰度值 fixed4 luminance (fixed4 color){ return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; } fixed4 fragExtractBright (v2f i ):SV_Target { ///我们降采样得到的亮度值减去阔值_luminanceThreshold,并把结果截取到0到1的范围内,然后 ///我们把该值和原像素相乘,得到提取后的两部区域 ///这样就是低于_luminanceThreshold显示为黑色 fixed c = tex2D(_MainTex , i.uv); //clamp(x,a,b),如果x<a,返回a,x>b,返回b,否则返回为x; fixed val = clamp (luminance(c) - _luminanceThreshold, 0.0, 1.0); return c * val; } ///使用Unity提供的_MainTex_Texel_TexelSize变量,计算相邻文理坐标的的偏移(也是高斯模糊的写法) //竖方向跟横方向的两个顶点着色器公用的v2f输出定义 struct v2fBlur { float4 pos : SV_POSITION; half2 uv[5]:TEXCOORD0; }; //竖直方向的 v2fBlur vertBlurV (appdata_img v){ v2fBlur 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; } //水平方向的 v2fBlur vertBlurH (appdata_img v){ v2fBlur 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 (v2fBlur i) : SV_Target{ float weight [3] = {0.4026, 0.2442, 0.0545}; fixed3 sum = tex2D (_MainTex,i.uv[0]).rgb * weight[0]; for (int j = 1; j<3; j++ ){ sum += tex2D(_MainTex,i.uv[j*2-1]).rgb * weight[j]; sum += tex2D(_MainTex,i.uv[j*2]).rgb * weight[j]; } return fixed4 (sum,1.0); } ///混合亮部图像和原图像时使用的 顶点.片元 着色器 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.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 Always Cull Off ZWrite Off pass { CGPROGRAM #pragma vertex vertExtractBright #pragma fragment fragExtractBright ENDCG } pass { CGPROGRAM #pragma vertex vertBlurV #pragma fragment fragBlur ENDCG } pass { CGPROGRAM #pragma vertex vertBlurH #pragma fragment fragBlur ENDCG } pass { CGPROGRAM #pragma vertex vertBloom #pragma fragment fragBloom ENDCG } } FallBack Off }