一、Bloom
这是一种常见的屏幕效果,模拟真实摄像机的图像效果,使画面中较亮的区域扩散到周围的区域中,造成一种朦胧的效果。
实现原理:
先根据阙值提取图像中明亮区域,存储在一张纹理中,然后利用高斯模糊对这张纹理进行模糊处理,模拟光线扩散的效果,最后再与原图像进行混合,得到最终效果。
二、代码
摄像机脚本:
using UnityEngine;
using System.Collections;
public class Bloom : PostEffectsBase {
public Shader bloomShader;
private Material bloomMaterial = null;
public Material material {
get {
bloomMaterial = CheckShaderAndCreateMaterial(bloomShader, bloomMaterial);
return bloomMaterial;
}
}
// Blur iterations - larger number means more blur.
[Range(0, 4)]
public int iterations = 3; 高斯模糊迭代次数
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f; //模糊范围,每次迭代的模糊扩散-较大的值意味着更多的模糊,出于性能考虑
[Range(1, 8)]
public int downSample = 2; //缩放系数,越大需要处理的像素数越少,同时也能进一步提高模糊程度,过大会导致图像像素话
[Range(0.0f, 4.0f)]
public float luminanceThreshold = 0.6f; //控制提取较亮区域时使用的阙值
//大多数情况下,图像亮度值不会超过1,但是如果开启HDR那么可能会超过1,所以我们区域选择为4。
void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
material.SetFloat("_LuminanceThreshold", luminanceThreshold); //将luminanceThreshold传入Shader
int rtW = src.width/downSample;
int rtH = src.height/downSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
buffer0.filterMode = FilterMode.Bilinear;
Graphics.Blit(src, buffer0, material, 0); //使用第一个Pass进行提取明亮部分图像,存储在buffer0中。
for (int i = 0; i < iterations; i++) { //与上节相同
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
// Render the vertical pass
Graphics.Blit(buffer0, buffer1, material, 1);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
// Render the horizontal pass
Graphics.Blit(buffer0, buffer1, material, 2);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
material.SetTexture ("_Bloom", buffer0); //新得到的纹理赋值给Shader
Graphics.Blit (src, dest, material, 3); //使用最后一个Pass
RenderTexture.ReleaseTemporary(buffer0); //释放
} else {
Graphics.Blit(src, dest);
}
}
}
Shader脚本:
Shader "Unity Shaders Book/Chapter 12/Bloom" {
Properties {
_MainTex ("Base (RGB)", 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;
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;
}
fixed luminance(fixed4 color) {
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
fixed4 fragExtractBright(v2f i) : SV_Target {
fixed4 c = tex2D(_MainTex, i.uv);
fixed val = clamp(luminance(c) - _LuminanceThreshold, 0.0, 1.0); //把采样得到的亮度值减去阙值,再截取结果到(0,1)之间。
return c * val; //相乘就是提取的亮的部分了。
}
struct v2fBloom { //定义结构体用来接收新的顶点着色器中的uv(half4)。
float4 pos : SV_POSITION;
half4 uv : TEXCOORD0;
};
v2fBloom vertBloom(appdata_img v) {
v2fBloom o;
o.pos = UnityObjectToClipPos (v.vertex);
o.uv.xy = v.texcoord; //存储_MainTex
o.uv.zw = v.texcoord; //存储_Bloom
#if UNITY_UV_STARTS_AT_TOP //用来判断是否在DX平台,如果在dx平台开启了抗锯齿,y值会为负值,
if (_MainTex_TexelSize.y < 0.0)
o.uv.w = 1.0 - o.uv.w; //如果是,我们对Y坐标进行翻转
#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 { //第一个Pass,进行亮度比较提取
CGPROGRAM
#pragma vertex vertExtractBright //就是正常的顶点着色器
#pragma fragment fragExtractBright //片元着色器中,进行颜色对比后提取亮的区域
ENDCG
}
UsePass "Unity Shaders Book/Chapter 12/Gaussian Blur/GAUSSIAN_BLUR_VERTICAL"
UsePass "Unity Shaders Book/Chapter 12/Gaussian Blur/GAUSSIAN_BLUR_HORIZONTAL"
Pass { //进行混合 亮部图像和原图像,
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom //上面函数注释
ENDCG
}
}
FallBack Off
}
注意:使用之前高斯模糊的Pass时,名称都变成大写了。
完事儿。