Last active
January 17, 2023 17:38
-
-
Save samloeschen/3b03e55fb4fb226d977e746b799677f8 to your computer and use it in GitHub Desktop.
A fast and simple bloom filter for Unity using the double-kawase method
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class BloomFilter : MonoBehaviour { | |
[Range(1, 16)] | |
public int iterations = 1; | |
[Range(0f, 1f)] | |
public float threshold = 0.8f; | |
[Range(0f, 1f)] | |
public float softThreshold = 0.25f; | |
[Range(0f, 5f)] | |
public float strength = 1f; | |
public float downsampleDistance = 1f; | |
public float upsampleDistance = 0.5f; | |
public Shader shader; | |
[System.NonSerialized] | |
Material material; | |
RenderTexture[] scratchTextures = new RenderTexture[16]; | |
void Start () { | |
material = new Material(shader); | |
material.hideFlags = HideFlags.HideAndDontSave; | |
} | |
const string thresholdName = "_Threshold"; | |
const string softnessName = "_Softness"; | |
const string strengthName = "_Strength"; | |
const string blendTargetName = "_BlendTarget"; | |
const string downsampleName = "_DownsampleDistance"; | |
const string upsampleName = "_UpsampleDistance"; | |
const string filterName = "_Filter"; | |
const int prefilterPass = 0; | |
const int downsamplePass = 1; | |
const int upsamplePass = 2; | |
const int blendPass = 3; | |
void OnRenderImage(RenderTexture src, RenderTexture dst) { | |
// set material uniforms | |
material.SetFloat(thresholdName, threshold); | |
material.SetFloat(softnessName, softThreshold); | |
material.SetFloat(strengthName, Mathf.GammaToLinearSpace(strength)); | |
material.SetFloat(downsampleName, downsampleDistance); | |
material.SetFloat(upsampleName, upsampleDistance); | |
material.SetTexture(blendTargetName, src); | |
// compute brightness response curve as a vector and set it on the material | |
float knee = threshold * softThreshold; | |
Vector4 filter = Vector4.zero; | |
filter.x = threshold; | |
filter.y = filter.x - knee; | |
filter.z = 2f * knee; | |
filter.w = 0.25f / (knee + 0.00001f); | |
material.SetVector(filterName, filter); | |
int width = src.width; | |
int height = src.height; | |
// perform luminance filter pass | |
RenderTexture renderTarget = scratchTextures[0] = RenderTexture.GetTemporary(width, height, 0, src.format); | |
Graphics.Blit(src, renderTarget, material, prefilterPass); | |
RenderTexture renderSource = renderTarget; | |
int i = 1; | |
// perform downsample | |
for (i = 1; i < iterations; i++) { | |
width /= 2; | |
height /= 2; | |
// early exit if we have gone beneath the minimum size | |
if (width < 2 || height < 2) { | |
Debug.LogWarning("Cannot further downsample texture!"); | |
break; | |
} | |
renderTarget = RenderTexture.GetTemporary(width, height, 0, src.format); | |
scratchTextures[i] = renderTarget; // store this texture for upsampling | |
Graphics.Blit(renderSource, renderTarget, material, downsamplePass); | |
renderSource = renderTarget; | |
} | |
// perform upsample | |
for (i -= 2; i >= 0; i--) { | |
renderTarget = scratchTextures[i]; | |
scratchTextures[i] = null; | |
Graphics.Blit(renderSource, renderTarget, material, upsamplePass); | |
RenderTexture.ReleaseTemporary(renderSource); | |
renderSource = renderTarget; | |
} | |
// perform blend pass | |
Graphics.Blit(renderSource, dst, material, blendPass); | |
RenderTexture.ReleaseTemporary(renderSource); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Shader "Image/BloomFilter" | |
{ | |
Properties | |
{ | |
_MainTex ("Texture", 2D) = "white" {} | |
} | |
CGINCLUDE | |
#include "UnityCG.cginc" | |
uniform sampler2D _MainTex; | |
uniform float2 _MainTex_TexelSize; | |
uniform sampler2D _BlendTarget; | |
uniform half _Threshold; | |
uniform half _Softness; | |
uniform half _Strength; | |
uniform half _DownsampleDistance; | |
uniform half _UpsampleDistance; | |
uniform half4 _Filter; | |
struct vertex_input | |
{ | |
float4 vertex : POSITION; | |
float2 uv : TEXCOORD0; | |
}; | |
struct vertex_output | |
{ | |
float2 uv : TEXCOORD0; | |
float4 vertex : SV_POSITION; | |
}; | |
vertex_output vert (vertex_input v) | |
{ | |
vertex_output o; | |
o.vertex = UnityObjectToClipPos(v.vertex); | |
o.uv = v.uv; | |
return o; | |
} | |
half3 kawase_sample (sampler2D tex, float2 uv, float4 offset) { | |
half3 c = tex2D(tex, uv + offset.xy).rgb + | |
tex2D(tex, uv + offset.zy).rgb + | |
tex2D(tex, uv + offset.xw).rgb + | |
tex2D(tex, uv + offset.zw).rgb; | |
return c * 0.25; | |
} | |
half4 prefilter (vertex_output i) : SV_Target | |
{ | |
half4 color = tex2D(_MainTex, i.uv); | |
half k = dot(float3(0.299, 0.587, 0.114), color); | |
half soft_k = k - _Filter.y; | |
soft_k = clamp(soft_k, 0, _Filter.z); | |
soft_k = soft_k * soft_k * _Filter.w; | |
half filter_k = max(soft_k, k - _Filter.x); | |
filter_k /= max(k, 0.00001); | |
return color * filter_k; | |
} | |
half4 downsample (vertex_output i) : SV_Target | |
{ | |
half dist = _DownsampleDistance; | |
half4 offset = _MainTex_TexelSize.xyxy * float2(-dist, dist).xxyy; | |
half3 sample = kawase_sample(_MainTex, i.uv, offset); | |
return half4(sample, 1); | |
} | |
half4 upsample (vertex_output i) : SV_Target | |
{ | |
half dist = _UpsampleDistance; | |
half4 offset = _MainTex_TexelSize.xyxy * float2(-dist, dist).xxyy; | |
half3 sample = kawase_sample(_MainTex, i.uv, offset); | |
return half4(sample, 1); | |
} | |
half4 blendpass (vertex_output i) : SV_Target | |
{ | |
half dist = _UpsampleDistance; | |
half4 c = tex2D(_BlendTarget, i.uv); | |
half4 offset = _MainTex_TexelSize.xyxy * float2(-dist, dist).xxyy; | |
c.rgb += _Strength * kawase_sample(_MainTex, i.uv, offset); | |
return c; | |
} | |
ENDCG | |
SubShader | |
{ | |
Cull Off ZWrite Off ZTest Always | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment prefilter | |
ENDCG | |
} | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment downsample | |
ENDCG | |
} | |
Pass | |
{ | |
Blend One One | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment upsample | |
ENDCG | |
} | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment blendpass | |
ENDCG | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment