/* Magic Bloom by luluco250 Attempts to simulate a natural-looking bloom. Features: --Wide bloom blurring, derived from the gaussian function defined here: https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics --Eye adaptation, decreases or increases the brightness of bloom according to the overall image luminance. --Lens dirt, as standard I suppose. Really not much here. It uses an image named "MagicBloom_Dirt.png" so make sure you have one in your textures directory. --Unwanted features can be disabled through preprocessor definitions, saving performance. Preprocessor definitions: --MAGICBLOOM_ADAPT_RESOLUTION: Determines the width/height of the texture used for adaptation. It is recommended to use 256, but you can use as far as 1024 without issues. Too low resolutions will make adaptation seem "unstable". Must be a power of two value: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 etc. --MAGICBLOOM_BLUR_PRECALCULATED: If set to 0 the gaussian blur will be calculated inside the shader. Otherwise, it uses a pre-calculated kernel (array). --MAGICBLOOM_NODIRT: If set to 1 all lens dirt related features are disabled. Beneficial for performance if you don't wish to use lens dirt. --MAGICBLOOM_NOADAPT: If set to 1 all adaptation related features are disabled. Beneficial for performance if you don't wish to use adaptation. MIT Licensed: Copyright (c) 2017 luluco250 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ReShade.fxh" //Statics #ifndef MAGICBLOOM_ADAPT_RESOLUTION #define MAGICBLOOM_ADAPT_RESOLUTION 256 #endif #ifndef MAGICBLOOM_BLUR_PRECALCULATED #define MAGICBLOOM_BLUR_PRECALCULATED 1 #endif #ifndef MAGICBLOOM_NODIRT #define MAGICBLOOM_NODIRT 0 #endif #ifndef MAGICBLOOM_NOADAPT #define MAGICBLOOM_NOADAPT 0 #endif static const int iBlurSamples = 4; static const int iAdaptResolution = MAGICBLOOM_ADAPT_RESOLUTION; #define CONST_LOG2(v) (((v) & 0xAAAAAAAA) != 0) | ((((v) & 0xFFFF0000) != 0) << 4) | ((((v) & 0xFF00FF00) != 0) << 3) | ((((v) & 0xF0F0F0F0) != 0) << 2) | ((((v) & 0xCCCCCCCC) != 0) << 1) static const float sigma = float(iBlurSamples) / 2.0; static const float double_pi = 6.283185307179586476925286766559; static const int lowest_mip = CONST_LOG2(iAdaptResolution) + 1; static const float3 luma_value = float3(0.2126, 0.7152, 0.0722); //Uniforms #include "ReShadeUI.fxh" uniform float fBloom_Intensity < __UNIFORM_SLIDER_FLOAT1 ui_label = "Bloom Intensity"; ui_tooltip = "Amount of bloom applied to the image."; ui_min = 0.0; ui_max = 10.0; ui_step = 0.001; > = 1.0; uniform float fBloom_Threshold < ui_label = "Bloom Threshold"; ui_tooltip = "Thresholds (limits) dark pixels from being accounted for bloom.\n" "Essentially, it increases the contrast in bloom and blackens darker pixels.\n" "At 1.0 all pixels are used in bloom.\n" "This value is not normalized, it is exponential, therefore changes in lower values are more noticeable than at higher values."; ui_type = "drag"; ui_min = 1.0; ui_max = 10.0; ui_step = 0.1; > = 2.0; #if !MAGICBLOOM_NODIRT uniform float fDirt_Intensity < __UNIFORM_SLIDER_FLOAT1 ui_label = "Dirt Intensity"; ui_tooltip = "Amount of lens dirt applied to bloom.\n" "Uses a texture called \"MagicBloom_Dirt.png\" from your textures directory(ies)."; ui_min = 0.0; ui_max = 1.0; ui_step = 0.001; > = 0.0; #endif #if !MAGICBLOOM_NOADAPT uniform float fExposure < __UNIFORM_SLIDER_FLOAT1 ui_label = "Exposure"; ui_tooltip = "The target exposure that bloom adapts to.\n" "It is recommended to just leave it at 0.5, unless you wish for a brighter (1.0) or darker (0.0) image."; ui_min = 0.0; ui_max = 1.0; ui_step = 0.001; > = 0.5; uniform float fAdapt_Speed < ui_label = "Adaptation Speed"; ui_tooltip = "How quick bloom adapts to changes in the image brightness.\n" "At 1.0, changes are instantaneous.\n" "It is recommended to use low values, between 0.01 and 0.1.\n" "0.1 will provide a quick but natural adaptation.\n" "0.01 will provide a slow form of adaptation."; ui_type = "drag"; ui_min = 0.001; ui_max = 1.0; ui_step = 0.001; > = 0.1; uniform float fAdapt_Sensitivity < __UNIFORM_SLIDER_FLOAT1 ui_label = "Adapt Sensitivity"; ui_tooltip = "How sensitive adaptation is towards brightness.\n" "At higher values bloom can get darkened at the slightest amount of brightness.\n" "At lower values bloom will require a lot of image brightness before it's fully darkened." "1.0 will not modify the amount of brightness that is accounted for adaptation.\n" "0.5 is a good value, but may miss certain bright spots."; ui_min = 0.0; ui_max = 3.0; ui_step = 0.001; > = 1.0; uniform float2 f2Adapt_Clip < __UNIFORM_SLIDER_FLOAT2 ui_label = "Adaptation Min/Max"; ui_tooltip = "Determines the minimum and maximum values that adaptation can determine to ajust bloom.\n" "Reducing the maximum would cause bloom to be brighter (as it is less adapted).\n" "Increasing the minimum would cause bloom to be darker (as it is more adapted).\n" "Keep the maximum above or equal to the minium and vice-versa."; ui_min = 0.0; ui_max = 1.0; ui_step = 0.001; > = float2(0.0, 1.0); uniform int iAdapt_Precision < __UNIFORM_SLIDER_INT1 ui_label = "Adaptation Precision"; ui_tooltip = "Determins how accurately bloom adapts to the center of image.\n" "At 0 the adaptation is calculated from the average of the whole image.\n" "At the highest value (which may vary) adaptation focuses solely on the center pixel(s) of the screen.\n" "Values closer to 0 are recommended."; ui_min = 0; ui_max = lowest_mip; ui_step = 0.1; > = lowest_mip * 0.3; #endif uniform uint iDebug < ui_label = "Debug Options"; ui_tooltip = "Contains debugging options like displaying the bloom texture."; ui_type = "combo"; ui_items = "None\0Display Bloom Texture\0"; > = 0; //Textures texture tMagicBloom_1 < pooled = true; > { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RGBA16F; }; texture tMagicBloom_2 < pooled = true; > { Width = BUFFER_WIDTH / 4; Height = BUFFER_HEIGHT / 4; Format = RGBA16F; }; texture tMagicBloom_3 < pooled = true; > { Width = BUFFER_WIDTH / 8; Height = BUFFER_HEIGHT / 8; Format = RGBA16F; }; texture tMagicBloom_4 < pooled = true; > { Width = BUFFER_WIDTH / 16; Height = BUFFER_HEIGHT / 16; Format = RGBA16F; }; texture tMagicBloom_5 < pooled = true; > { Width = BUFFER_WIDTH / 32; Height = BUFFER_HEIGHT / 32; Format = RGBA16F; }; texture tMagicBloom_6 < pooled = true; > { Width = BUFFER_WIDTH / 64; Height = BUFFER_HEIGHT / 64; Format = RGBA16F; }; texture tMagicBloom_7 < pooled = true; > { Width = BUFFER_WIDTH / 128; Height = BUFFER_HEIGHT / 128; Format = RGBA16F; }; texture tMagicBloom_8 < pooled = true; > { Width = BUFFER_WIDTH / 256; Height = BUFFER_HEIGHT / 256; Format = RGBA16F; }; #if !MAGICBLOOM_NOADAPT texture tMagicBloom_Small { Width = iAdaptResolution; Height = iAdaptResolution; Format = R32F; MipLevels = lowest_mip; }; texture tMagicBloom_Adapt { Format = R32F; }; texture tMagicBloom_LastAdapt { Format = R32F; }; #endif #if !MAGICBLOOM_NODIRT texture tMagicBloom_Dirt { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; }; #endif //Samplers sampler sMagicBloom_1 { Texture = tMagicBloom_1; }; sampler sMagicBloom_2 { Texture = tMagicBloom_2; }; sampler sMagicBloom_3 { Texture = tMagicBloom_3; }; sampler sMagicBloom_4 { Texture = tMagicBloom_4; }; sampler sMagicBloom_5 { Texture = tMagicBloom_5; }; sampler sMagicBloom_6 { Texture = tMagicBloom_6; }; sampler sMagicBloom_7 { Texture = tMagicBloom_7; }; sampler sMagicBloom_8 { Texture = tMagicBloom_8; }; #if !MAGICBLOOM_NOADAPT sampler sMagicBloom_Small { Texture = tMagicBloom_Small; }; sampler sMagicBloom_Adapt { Texture = tMagicBloom_Adapt; MinFilter = POINT; MagFilter = POINT; }; sampler sMagicBloom_LastAdapt { Texture = tMagicBloom_LastAdapt; MinFilter = POINT; MagFilter = POINT; }; #endif #if !MAGICBLOOM_NODIRT sampler sMagicBloom_Dirt { Texture = tMagicBloom_Dirt; }; #endif //Functions #if !MAGICBLOOM_BLUR_PRECALCULATED float gaussian_function(float2 i) { static const float first_part = 1.0 / (double_pi * pow(sigma, 2.0)); static const float second_part_a = 1.0 / (2.0 * pow(sigma, 2.0)); float second_part_b = (pow(i.x, 2.0) + pow(i.y, 2.0)) * second_part_a; return first_part * exp(-second_part_b); } #endif //Why use a single-pass blur? To reduce the amount of textures used in half. //Scale should be the original resolution divided by target resolution. float3 blur(sampler sp, float2 uv, float scale) { float2 ps = BUFFER_PIXEL_SIZE * scale; #if MAGICBLOOM_BLUR_PRECALCULATED static const float kernel[9] = { 0.0269955, 0.0647588, 0.120985, 0.176033, 0.199471, 0.176033, 0.120985, 0.0647588, 0.0269955 }; static const float accum = 1.02352; #else float accum = 0.0; #endif float gaussian_weight = 0.0; float3 col = 0.0; [loop] for (int x = -iBlurSamples; x <= iBlurSamples; ++x) { for (int y = -iBlurSamples; y <= iBlurSamples; ++y) { #if MAGICBLOOM_BLUR_PRECALCULATED gaussian_weight = kernel[x + iBlurSamples] * kernel[y + iBlurSamples]; #else gaussian_weight = gaussian_function(float2(x, y)); accum += gaussian_weight; #endif col += tex2D(sp, uv + ps * float2(x, y)).rgb * gaussian_weight; } } #if MAGICBLOOM_BLUR_PRECALCULATED return col * accum; #else return col / accum; #endif } /* Uncharted 2 Tonemapper Thanks John Hable and Naughty Dog. */ float3 tonemap(float3 col, float exposure) { static const float A = 0.15; //shoulder strength static const float B = 0.50; //linear strength static const float C = 0.10; //linear angle static const float D = 0.20; //toe strength static const float E = 0.02; //toe numerator static const float F = 0.30; //toe denominator static const float W = 11.2; //linear white point value col *= exposure; col = ((col * (A * col + C * B) + D * E) / (col * (A * col + B) + D * F)) - E / F; static const float white = 1.0 / (((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F); col *= white; return col; } float3 blend_screen(float3 a, float3 b) { return 1.0 - (1.0 - a) * (1.0 - b); } /* The function below is a leftover from debugging. It just draws a line on the screen, it's horizontal position being the value you specify (from 0.0-1.0, becoming left-right). No use now but might be useful later on so I just left it here. */ /*void debug_value(inout float3 col, float2 uv, float value, float3 needle_color) { static const float2 ps = BUFFER_PIXEL_SIZE; col = (uv.x + ps.x > value && uv.x - ps.x < value) ? needle_color : col; }*/ //Shaders /* Thresholding is performed on the first blur for two reasons: --Save an entire texture from being used to threshold. --Being the smallest blur it also results in the least amount of artifacts. */ float4 PS_Blur1(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { float3 col = blur(ReShade::BackBuffer, uv, 2.0); col = pow(abs(col), fBloom_Threshold); col *= fBloom_Intensity; return float4(col, 1.0); } float4 PS_Blur2(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(blur(sMagicBloom_1, uv, 4.0), 1.0); } float4 PS_Blur3(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(blur(sMagicBloom_2, uv, 8.0), 1.0); } float4 PS_Blur4(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(blur(sMagicBloom_3, uv, 8.0), 1.0); } float4 PS_Blur5(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(blur(sMagicBloom_4, uv, 16.0), 1.0); } float4 PS_Blur6(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(blur(sMagicBloom_5, uv, 32.0), 1.0); } float4 PS_Blur7(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(blur(sMagicBloom_6, uv, 64.0), 1.0); } float4 PS_Blur8(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(blur(sMagicBloom_7, uv, 128.0), 1.0); } //Final blend shader float4 PS_Blend(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { float3 col = tex2D(ReShade::BackBuffer, uv).rgb; float3 bloom = tex2D(sMagicBloom_1, uv).rgb + tex2D(sMagicBloom_2, uv).rgb + tex2D(sMagicBloom_3, uv).rgb + tex2D(sMagicBloom_4, uv).rgb + tex2D(sMagicBloom_5, uv).rgb + tex2D(sMagicBloom_6, uv).rgb + tex2D(sMagicBloom_7, uv).rgb + tex2D(sMagicBloom_8, uv).rgb; //Dunno if making the division by 8 a static multiplication helps, but whatever. static const float bloom_accum = 1.0 / 8.0; bloom *= bloom_accum; #if !MAGICBLOOM_NOADAPT float exposure = fExposure / max(tex2D(sMagicBloom_Adapt, 0.0).x, 0.00001); bloom = tonemap(bloom, exposure); #else //Without adaptation it seems 100.0 exposure is needed for bloom to look bright enough. bloom = tonemap(bloom, 100.0); #endif #if !MAGICBLOOM_NODIRT float3 dirt = tex2D(sMagicBloom_Dirt, uv).rgb; dirt *= fDirt_Intensity; bloom = blend_screen(bloom, dirt * bloom); #endif col = blend_screen(col, bloom); //If we're to display the bloom texture, we replace col with it. col = iDebug == 1 ? bloom : col; return float4(col, 1.0); } #if !MAGICBLOOM_NOADAPT /* How adaptation works: --Calculate image luminance. --Save it to a smaller, mipmapped texture. --Mipmaps require a power of 2 texture. --Fetch a mipmap level according to a specfied amount of precision. --The lowest mipmap is simply an average of the entire image. */ float PS_GetSmall(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return dot(tex2D(ReShade::BackBuffer, uv).rgb, luma_value); } float PS_GetAdapt(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { float curr = tex2Dlod(sMagicBloom_Small, float4(0.5, 0.5, 0, lowest_mip - iAdapt_Precision)).x; curr *= fAdapt_Sensitivity; curr = clamp(curr, f2Adapt_Clip.x, f2Adapt_Clip.y); float last = tex2D(sMagicBloom_LastAdapt, 0.0).x; //Using the frametime/delta here would actually scale adaptation with the framerate. //We don't want that, so we don't even bother with it. return lerp(last, curr, fAdapt_Speed); } float PS_SaveAdapt(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return tex2D(sMagicBloom_Adapt, 0.0).x; } #endif technique MagicBloom { pass Blur1 { VertexShader = PostProcessVS; PixelShader = PS_Blur1; RenderTarget = tMagicBloom_1; } pass Blur2 { VertexShader = PostProcessVS; PixelShader = PS_Blur2; RenderTarget = tMagicBloom_2; } pass Blur3 { VertexShader = PostProcessVS; PixelShader = PS_Blur3; RenderTarget = tMagicBloom_3; } pass Blur4 { VertexShader = PostProcessVS; PixelShader = PS_Blur4; RenderTarget = tMagicBloom_4; } pass Blur5 { VertexShader = PostProcessVS; PixelShader = PS_Blur5; RenderTarget = tMagicBloom_5; } pass Blur6 { VertexShader = PostProcessVS; PixelShader = PS_Blur6; RenderTarget = tMagicBloom_6; } pass Blur7 { VertexShader = PostProcessVS; PixelShader = PS_Blur7; RenderTarget = tMagicBloom_7; } pass Blur8 { VertexShader = PostProcessVS; PixelShader = PS_Blur8; RenderTarget = tMagicBloom_8; } pass Blend { VertexShader = PostProcessVS; PixelShader = PS_Blend; } #if !MAGICBLOOM_NOADAPT pass GetSmall { VertexShader = PostProcessVS; PixelShader = PS_GetSmall; RenderTarget = tMagicBloom_Small; } pass GetAdapt { VertexShader = PostProcessVS; PixelShader = PS_GetAdapt; RenderTarget = tMagicBloom_Adapt; } pass SaveAdapt { VertexShader = PostProcessVS; PixelShader = PS_SaveAdapt; RenderTarget = tMagicBloom_LastAdapt; } #endif }