Skip to content

Instantly share code, notes, and snippets.

@WamWooWam
Last active May 1, 2022 20:43
Show Gist options
  • Save WamWooWam/ec6109e240aef0e5313ab27db9a35b61 to your computer and use it in GitHub Desktop.
Save WamWooWam/ec6109e240aef0e5313ab27db9a35b61 to your computer and use it in GitHub Desktop.
Sonic Colours-style bloom implemented as a Unity PostProcessEffect
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.PostProcessing;
[Serializable]
[PostProcess(typeof(RainbowBloomRenderer), PostProcessEvent.AfterStack, "Rainbow/Bloom")]
public sealed class RainbowBloom : PostProcessEffectSettings
{
public FloatParameter threshold = new FloatParameter() { value = 0.9f };
}
public sealed class RainbowBloomRenderer : PostProcessEffectRenderer<RainbowBloom>
{
private const int COLORS_BLOOM_WIDTH = 160;
private const int COLORS_BLOOM_HEIGHT = 120;
private const int COLORS_BLOOM_BUFFERS = 4;
private RenderTexture brightPassSrcTex;
private RenderTexture[] bloomTex;
private RenderTexture[] bloomTexTemp;
private static class Pass
{
public static int Downsample = 0,
Bloom = 1,
AlphaBlend = 2,
AdditiveBlend = 3,
Copy = 4;
}
private static int _Param = Shader.PropertyToID(nameof(_Param));
private static int _BloomStar_Param1 = Shader.PropertyToID(nameof(_BloomStar_Param1));
private static int _BlendParams = Shader.PropertyToID(nameof(_BlendParams));
public override void Render(PostProcessRenderContext context)
{
context.command.BeginSample("RainbowBloom");
// get our shader
var sheet = context.propertySheets.Get(Shader.Find("Hidden/PostProcessing/RainbowBloom"));
// aquire our render targets
brightPassSrcTex = context.GetScreenSpaceTemporaryRT(widthOverride: COLORS_BLOOM_WIDTH, heightOverride: COLORS_BLOOM_HEIGHT);
bloomTex = new RenderTexture[COLORS_BLOOM_BUFFERS];
bloomTexTemp = new RenderTexture[COLORS_BLOOM_BUFFERS];
for (int i = 0; i < COLORS_BLOOM_BUFFERS; i++)
{
bloomTex[i] = context.GetScreenSpaceTemporaryRT(widthOverride: COLORS_BLOOM_WIDTH >> i, heightOverride: COLORS_BLOOM_HEIGHT >> i);
bloomTexTemp[i] = context.GetScreenSpaceTemporaryRT(widthOverride: COLORS_BLOOM_WIDTH >> i, heightOverride: COLORS_BLOOM_HEIGHT >> i);
}
// calculate width/height for downsampling the display
float rcpWidth = 1.0f / context.screenWidth;
float rcpHeight = 1.0f / context.screenHeight;
float blockWidth = Math.Max(1.0f, context.screenWidth / (float)COLORS_BLOOM_WIDTH);
float blockHeight = Math.Max(1.0f, context.screenHeight / (float)COLORS_BLOOM_HEIGHT);
var downSampleParam = new Vector4(
((blockWidth / 2.0f + 0.5f) * rcpWidth),
rcpWidth,
((blockHeight / 2.0f + 0.5f) * rcpHeight),
rcpHeight
);
// downsample the display into brightPassSrcTex
sheet.properties.SetVector(_Param, downSampleParam);
context.command.BlitFullscreenTriangle(context.source, brightPassSrcTex, sheet, Pass.Downsample);
// copy the bright pass into the first bloom texture
sheet.properties.SetFloat(_BloomStar_Param1, this.settings.threshold.value);
context.command.BlitFullscreenTriangle(brightPassSrcTex, bloomTex[0], sheet, Pass.Bloom);
// downscale the previous bloom texture into the next 3 textures
for (int i = 1; i < COLORS_BLOOM_BUFFERS; i++)
context.command.BlitFullscreenTriangle(bloomTex[i - 1], bloomTex[i]);
// for each buffer, shift it up, down, left and right a number of times, this blurs the texture
// it's rudimentary, but it works
for (int i = 0; i < COLORS_BLOOM_BUFFERS; i++)
{
for (int j = 0; j < 2; j++)
{
var srcSurface = j == 0 ? bloomTex[i] : bloomTexTemp[i];
var destSurface = j == 0 ? bloomTexTemp[i] : bloomTex[i];
for (int k = 0; k < 1 + (1 << (i + 1)); k++)
{
float x = 0.0f;
float y = 0.0f;
if (j == 0)
x = (float)((k + 1) / 2);
else
y = (float)((k + 1) / 2);
if ((k % 2) == 0)
{
x *= -1;
y *= -1;
}
// pass our parameters into the shader
float xCoord = x / (float)srcSurface.width;
float yCoord = y / (float)srcSurface.height;
float srcAlphaDstAlpha = 1.0f / (k + 1.0f);
sheet.properties.SetVector(_BlendParams, new Vector4(xCoord, yCoord, srcAlphaDstAlpha, 0));
// will it blend?
context.command.BlitFullscreenTriangle(srcSurface, destSurface, sheet, Pass.AlphaBlend);
}
}
}
// copy the display
context.command.BlitFullscreenTriangle(context.source, context.destination);
// additively blend the bloom textures
for (int i = 3; i >= 0; i--)
context.command.BlitFullscreenTriangle(bloomTex[i], context.destination, sheet, Pass.AdditiveBlend);
context.command.EndSample("RainbowBloom");
}
}
Shader "Hidden/PostProcessing/RainbowBloom"
{
HLSLINCLUDE
#include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
#include "Packages/com.unity.postprocessing/PostProcessing/Shaders/Colors.hlsl"
#include "Packages/com.unity.postprocessing/PostProcessing/Shaders/Sampling.hlsl"
TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
float4 _Param;
float _BloomStar_Param1;
float4 _BlendParams;
float4 BloomFrag(VaryingsDefault i) : SV_Target
{
return float4(saturate(saturate(SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord).xyz * 0.5) / (_BloomStar_Param1.x * 0.5) - _BloomStar_Param1.x), 1);
}
float4 DownsampleFrag(VaryingsDefault i) : SV_Target
{
float4 sum = 0;
int count = 0;
for (float x = -_Param.x; x <= _Param.x; x += _Param.y)
{
for (float y = -_Param.z; y <= _Param.z; y += _Param.w)
{
sum += SAMPLE_TEXTURE2D_LOD(_MainTex, sampler_MainTex, i.texcoord + float2(x, y), 0);
++count;
}
}
return count > 0 ? sum / count : 0;
}
VaryingsDefault AlphaBlendVert(AttributesDefault v)
{
VaryingsDefault o;
o.vertex = float4(v.vertex.xy, 0.0, 1.0);
o.texcoord = TransformTriangleVertexToUV(v.vertex.xy);
#if UNITY_UV_STARTS_AT_TOP
o.texcoord = o.texcoord * float2(1.0, -1.0) + float2(0.0, 1.0);
#endif
o.texcoordStereo = TransformStereoScreenSpaceTex(o.texcoord, 1.0);
return o;
}
float4 AlphaBlendFrag(VaryingsDefault i) : SV_Target
{
return float4(SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord + _BlendParams.xy).xyz, _BlendParams.z);
}
float4 PassFinal(VaryingsDefault i) : SV_Target
{
return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
}
ENDHLSL
SubShader
{
Cull Off ZWrite Off ZTest Always
// 0: Prefilter 13 taps
Pass
{
HLSLPROGRAM
#pragma vertex VertDefault
#pragma fragment DownsampleFrag
ENDHLSL
}
Pass
{
HLSLPROGRAM
#pragma vertex VertDefault
#pragma fragment BloomFrag
ENDHLSL
}
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
#pragma vertex AlphaBlendVert
#pragma fragment AlphaBlendFrag
ENDHLSL
}
Pass
{
Blend One One
HLSLPROGRAM
#pragma vertex VertDefault
#pragma fragment PassFinal
ENDHLSL
}
Pass
{
HLSLPROGRAM
#pragma vertex VertDefault
#pragma fragment PassFinal
ENDHLSL
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment