Bloom效果,又或是高光效果

文章内使用Unity 2019 LTS

目标

  • Bloom效果

Bloom

以下这张图片也是一个常见的萤幕後制特效,Bloom,中文翻作高光。

from wiki

仔细观察这张图,可以在这其中看到一个先前介绍过的效果,那就是模糊(Blur),也就是说,我们可以结合之前的模糊效果,再加上一些「处理」就可以完成这项功能了。

那这些处理是甚麽呢?事实上Bloom的实作原理很简单,先给出一个值标示出影像中,属於高亮度的部分,随後将这些高亮度的部分储存在Render Texture中并进行模糊操作,最後再与原图进行混和。

开始撰写吧!

  1. Bloom Shader
Shader "Learning/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;
		};	
		
		// vertex shader 用於获取高亮处
		v2f vertExtractBright(appdata_img v) {
			v2f o;
			
			o.pos = UnityObjectToClipPos(v.vertex);
			
			o.uv = v.texcoord;
					 
			return o;
		}
		
		// 降至灰阶,分析明亮度
		// wiki: https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
		fixed luminance(fixed4 color) {
			return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; 
		}
		
		// fragment shader 用於获取高亮处
		fixed4 fragExtractBright(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);
			// 第一组纹理座标为原图,第二组为Bloom
			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
		// 各4个Pass处理这个效果
		Pass {  
			CGPROGRAM  
			#pragma vertex vertExtractBright  
			#pragma fragment fragExtractBright  
			
			ENDCG  
		}
		
        // 这两个Shader来自Day26的画面模糊效果
		UsePass "Learning/Blur/GAUSSIAN_BLUR_VERTICAL"
		
		UsePass "Learning/Blur/GAUSSIAN_BLUR_HORIZONTAL"
		
		Pass {  
			CGPROGRAM  
			#pragma vertex vertBloom  
			#pragma fragment fragBloom  
			
			ENDCG  
		}
	}
}

画面模糊效果

  1. Script
[RequireComponent(typeof(Camera))]
public class BloomCopy : MonoBehaviour
{
    public Shader BloomShader;

    private Material material;

	[Range(0, 4)]
	public int iterations = 3;
	
	[Range(0.2f, 3.0f)]
	public float blurSpread = 0.6f;

	[Range(0.0f, 4.0f)]
	public float luminanceThreshold = 0.6f;

    void Start()
    {
        if (!BloomShader.isSupported)
        {
            return;
        }
        else{
            material = new Material(BloomShader);
            material.hideFlags = HideFlags.DontSave;
        }
    }

    private void OnRenderImage(RenderTexture src, RenderTexture dest) {
        if (material != null) {
			material.SetFloat("_LuminanceThreshold", luminanceThreshold);
			
			RenderTexture buffer0 = RenderTexture.GetTemporary(src.width, src.height, 0);
			buffer0.filterMode = FilterMode.Bilinear;
			
			Graphics.Blit(src, buffer0, material, 0);
			
			for (int i = 0; i < iterations; i++) {
				material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
				
				RenderTexture buffer1 = RenderTexture.GetTemporary(src.width, src.height, 0);
				
				// Render the vertical pass
				Graphics.Blit(buffer0, buffer1, material, 1);
				
				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
				buffer1 = RenderTexture.GetTemporary(src.width, src.height, 0);
				
				// Render the horizontal pass
				Graphics.Blit(buffer0, buffer1, material, 2);
				
				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
			}

			material.SetTexture ("_Bloom", buffer0);  
			Graphics.Blit (src, dest, material, 3);  

			RenderTexture.ReleaseTemporary(buffer0);
		} else {
			Graphics.Blit(src, dest);
		}
    }
}

Reference


<<:  倒数第3天

>>:  Day 30|Divi 功能练习 23 Video Module 影片嵌入功能

Day11. UX/UI 设计流程之一: Functional Map (以 Axure RP 实作)

有了 User Story,已经能够了解产品会有哪些角色、他们的需求及功能价值。但缺少的是这些需求...

TailwindCSS 从零开始 - 让 Variants 成为伪类的强大神器

除了 Tailwind CSS - 设定自己想要的 TailwindCSS 样式 Variant ...

管理是什麽?

What is management? 如果有人问你,「一个主管的工作到底是什麽?」,你会怎麽说? ...

[Day24] Scrum失败经验谈 – 壁垒分明的职务配置

不足的丰富资源 未依团队性质配置的资源,会制造资源不足的假象 在IT团队最大的时候,有11人,分别是...

Day 03 : ML in Production 的挑战

在 Day2 提到什麽是用於生产的机械学习 ML in Production ,今天来谈用於生产的机...