Welcome, Guest.
Username: Password: Remember me

TOPIC: Eye Adaption

Eye Adaption 7 months 4 weeks ago #1

Update: EyeAdaption is now part of the offical ReShade shaders. For the latest updates visit my repository github.com/brussell1/Shaders
This post is the first one in a little series showing my attempts at shader creation. Generally I try for relatively simple effects with reasonable performance impact.
So here is an eye adaption effect. It brightens the screen when the average screen luminance is lower than a choosable value and darkens it when it's above another one.
Feedback and tips for improvement are welcome.

Example Video

Usage: just copy the following code and save it to EyeAdaption.fx.
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ReShade effect file
// Eye Adaption by brussell
//
// Credits:
// luluco250 - luminance get/store code from Magic Bloom
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

#include "ReShade.fxh"

/*==============================================================================*\
|                             EFFECT PARAMETERS                                 |
\*==============================================================================*/

uniform float fAdp_Speed <
    ui_label = "AdaptionSpeed";
    ui_tooltip = "Speed of adaption. The higher the faster";
    ui_type = "drag";
    ui_min = 0.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.1;

uniform bool bAdp_BrightenEnable <
    ui_label = "BrightenEnable";
	ui_tooltip = "Enable Brightening";
> = true;

uniform float fAdp_BrightenThreshold <
    ui_label = "BrightenThreshold";
    ui_tooltip = "A lower average screen luminance brightens the image";
    ui_type = "drag";
    ui_min = 0.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.2;

uniform float fAdp_BrightenMax <
    ui_label = "BrightenMax";
    ui_tooltip = "Brightens the image by maximum value";
    ui_type = "drag";
    ui_min = 0.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.1;

uniform float fAdp_BrightenCurve <
    ui_label = "BrightenCurve";
    ui_tooltip = "Brightening increase depending on average screen luminance. 1=linear growth, 0.5=quadratic, 2=sq. root";
    ui_type = "drag";
    ui_min = 0.2;
    ui_max = 5.0;
    ui_step = 0.001;
> = 1.0;

uniform float fAdp_BrightenDynamic <
    ui_label = "BrightenDynamic";
    ui_tooltip = "Amount of dynamic brightening (Dark pixels get brightened more)";
    ui_type = "drag";
    ui_min = 0.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.5;

uniform float fAdp_BrightenBlack <
    ui_label = "BrightenBlack";
    ui_tooltip = "Amount of lows preservation. 1=no black brightening";
    ui_type = "drag";
    ui_min = 0.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.5;

uniform float fAdp_BrightenSaturation <
    ui_label = "BrightenSaturation";
    ui_tooltip = "Amount of lows preservation. 1=no black brightening";
    ui_type = "drag";
    ui_min = -1.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.0;

uniform bool bAdp_DarkenEnable <
    ui_label = "DarkenEnable";
	ui_tooltip = "Enable Darkening";
> = true;

uniform float fAdp_DarkenThreshold <
    ui_label = "DarkenThreshold";
    ui_tooltip = "A higher average screen luminance darkebs the image";
    ui_type = "drag";
    ui_min = 0.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.3;

uniform float fAdp_DarkenMax <
    ui_label = "DarkenMax";
    ui_tooltip = "Darkens the image by maximum value";
    ui_type = "drag";
    ui_min = 0.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.4;

uniform float fAdp_DarkenCurve <
    ui_label = "DarkenCurve";
    ui_tooltip = "Darkening increase depending on average screen luminance. 1=linear growth, 0.5=quadratic, 2=sq. root";
    ui_type = "drag";
    ui_min = 0.2;
    ui_max = 5.0;
    ui_step = 0.001;
> = 0.5;

uniform float fAdp_DarkenDynamic <
    ui_label = "DarkenDynamic";
    ui_tooltip = "Amount of dynamic darkening (Bright pixels get darkened more)";
    ui_type = "drag";
    ui_min = 0.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.5;

uniform float fAdp_DarkenWhite <
    ui_label = "DarkenWhite";
    ui_tooltip = "Amount of highs preservation. 1=no white darkening";
    ui_type = "drag";
    ui_min = 0.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.5;

uniform float fAdp_DarkenSaturation <
    ui_label = "DarkenSaturation";
    ui_tooltip = "Amount of lows preservation. 1=no white darkening";
    ui_type = "drag";
    ui_min = -1.0;
    ui_max = 1.0;
    ui_step = 0.001;
> = 0.0;


//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// END OF TWEAKING VARIABLES                                                                                                 //
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//global vars
#define LumCoeff float3(0.212656, 0.715158, 0.072186)
uniform float Frametime < source = "frametime";>;

//textures and samplers
texture2D texLuminance { Width = 256; Height = 256; Format = R8; MipLevels = 7; };
texture2D texAvgLuminance { Format = R16F; };
texture2D texAvgLuminanceLast { Format = R16F; };

sampler SamplerLuminance { Texture = texLuminance; };
sampler SamplerAvgLuminance { Texture = texAvgLuminance; };
sampler SamplerAvgLuminanceLast { Texture = texAvgLuminanceLast; };


//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Eye Adaption                                                                                                                  //
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

float PS_Luminance(float4 pos : SV_Position, float2 texcoord : TEXCOORD) : SV_Target
{
   return dot(tex2D(ReShade::BackBuffer, texcoord.xy).xyz, LumCoeff);
}

float PS_AvgLuminance(float4 pos : SV_Position, float2 texcoord : TEXCOORD) : SV_Target
{
   float lum = tex2Dlod(SamplerLuminance, float4(0.5.xx, 0, 7)).x;
   float lumlast = tex2D(SamplerAvgLuminanceLast, 0.0).x;
   return lerp(lumlast, lum, fAdp_Speed * 10.0/Frametime);
}

float PS_StoreAvgLuminance(float4 pos : SV_Position, float2 texcoord : TEXCOORD) : SV_Target
{
   return tex2D(SamplerAvgLuminance, 0.0).x;
}

float4 PS_Adaption(float4 pos : SV_Position, float2 texcoord : TEXCOORD) : SV_Target
{
    float4 color = tex2Dlod(ReShade::BackBuffer, float4 (texcoord.xy, 0, 0));
    static const float avglum = saturate(tex2D(SamplerAvgLuminance, 0.0).x);
    float adpcurve, adpdelta;
    float colorluma = dot(color.xyz, LumCoeff);
    float3 colorchroma = color.xyz - colorluma;

    [branch]
    if(bAdp_BrightenEnable == true && avglum < fAdp_BrightenThreshold) {
        adpcurve = (-fAdp_BrightenMax / pow(fAdp_BrightenThreshold, fAdp_BrightenCurve)) * pow(avglum, fAdp_BrightenCurve) + fAdp_BrightenMax;
        adpdelta = lerp(adpcurve, adpcurve - adpcurve * colorluma, fAdp_BrightenDynamic);
        adpdelta = lerp(adpdelta, min(colorluma, adpdelta), fAdp_BrightenBlack);
        colorluma += adpdelta;
        colorchroma = colorchroma * saturate(1.0 + (adpcurve / fAdp_BrightenMax) * fAdp_BrightenSaturation);
        color.xyz = colorluma + colorchroma;
    }
    [branch]
    if(bAdp_DarkenEnable == true && avglum > fAdp_DarkenThreshold) {
        adpcurve = (fAdp_DarkenMax / pow(1.0 - fAdp_DarkenThreshold, 1.0 / fAdp_DarkenCurve)) * pow(avglum - fAdp_DarkenThreshold, 1.0 / fAdp_DarkenCurve);
        adpdelta = lerp(adpcurve, adpcurve * colorluma, fAdp_DarkenDynamic);
        adpdelta = lerp(adpdelta, min(1.0 - colorluma, adpdelta), fAdp_DarkenWhite);
        colorluma -= adpdelta;
        colorchroma = colorchroma * saturate(1.0 + (adpcurve / fAdp_DarkenMax) * fAdp_DarkenSaturation);
        color.xyz = colorluma + colorchroma;
    }

    return color;
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Techniques                                                                                                                //
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

technique EyeAdaption {

    pass Luminance
    {
        VertexShader = PostProcessVS;
        PixelShader = PS_Luminance;
        RenderTarget = texLuminance;
    }

    pass AvgLuminance
    {
        VertexShader = PostProcessVS;
        PixelShader = PS_AvgLuminance;
        RenderTarget = texAvgLuminance;
    }

    pass Adaption
    {
        VertexShader = PostProcessVS;
        PixelShader = PS_Adaption;
    }

    pass StoreAvgLuminance
    {
        VertexShader = PostProcessVS;
        PixelShader = PS_StoreAvgLuminance;
        RenderTarget = texAvgLuminanceLast;
    }
}

Version for legacy ReShade:
Warning: Spoiler! [ Click to expand ]


edit:
Changelog 30.01.2018:
-added parameters for black and white protection
-fixed dynamic adaption calculations
-adaption speed now (almost) fps independent
-some code optimisation

edit:
Changelog 08.02.2018:
-simplified adaption calculation
-added saturation parameters

edit:
Changelog 14.02.2018:
-simpler, more efficient code, less parameters
-better saturation calculation
-toggable brightening and darkening

edit:
-added GUI version for ReShade 3
Last Edit: 4 months 1 week ago by brussell.
The administrator has disabled public write access.
The following user(s) said Thank You: Marty McFly, Insomnia, Martigen, DeMondo, luluco250, Qsimil, andrew, MakeNX, Ryukou36, GP-Unity and this user have 5 others thankyou

brussells humble shaders: Adaption 7 months 4 weeks ago #2

Interesting. To simulate adaptation before tonemapping (instead of untonemapping and retonemapping, try to protect white tones by reducing the darkening for those pixels. That'll retain most of the image fidelity.
The administrator has disabled public write access.
The following user(s) said Thank You: brussell

brussells humble shaders: Adaption 7 months 4 weeks ago #3

wow thats somthing..perfect
The administrator has disabled public write access.

brussells humble shaders: Adaption 7 months 3 weeks ago #4

Changelog 30.01.2018:
-added parameters for black and white protection
-fixed dynamic adaption calculations
-adaption speed now (almost) fps independent
-some code optimisations
The administrator has disabled public write access.
The following user(s) said Thank You: Qsimil

brussells humble shaders: Adaption 7 months 3 weeks ago #5

I have to say I really, really like this shader. :) Thanks for sharing!
The administrator has disabled public write access.

brussells humble shaders: Adaption 7 months 2 weeks ago #6

Changelog 08.02.2018:
-simplified adaption calculation
-added saturation parameters
The administrator has disabled public write access.

brussells humble shaders: Adaption 7 months 2 weeks ago #7

brussell wrote:
Changelog 08.02.2018:
-simplified adaption calculation
-added saturation parameters
Thank you brussel. Just one suggestion: please stop adding a default key and setting it enabled, this screws up profiles, and perhaps call it something more informative :) So for eg:
technique LightAdaptionRussel

Thanks :)
Last Edit: 7 months 2 weeks ago by Martigen.
The administrator has disabled public write access.

brussells humble shaders: Adaption 7 months 1 week ago #8

Thanks a lot. It's a really useful shader. One question, any chances for version with UI ?
The administrator has disabled public write access.

brussells humble shaders: Adaption 7 months 1 week ago #9

Rudy102 wrote:
Thanks a lot. It's a really useful shader. One question, any chances for version with UI ?
Yes. Soon. :)

btw:
Changelog 14.02.2018:
-simpler, more efficient code, less parameters
-better saturation calculation
-toggable brightening and darkening
The administrator has disabled public write access.
The following user(s) said Thank You: Rudy102

brussells humble shaders: Adaption 7 months 1 week ago #10

What a nice update, thank you kindly :)
The administrator has disabled public write access.

brussells humble shaders: Adaption 6 months 3 weeks ago #11

I've added the GUI-version to the initial post. I will also try to add it to the official shader repository soon.
Last Edit: 6 months 3 weeks ago by brussell.
The administrator has disabled public write access.
The following user(s) said Thank You: GP-Unity

brussells humble shaders: Adaption 6 months 3 weeks ago #12

one quick thing:

your luma averaging function isn't actually giving you an average - it is merely reporting the last luma value that the shader happens to sample. every time you reload the shader, it will give you a different luma value, even if the scene does not change in any way.
The administrator has disabled public write access.

brussells humble shaders: Adaption 6 months 3 weeks ago #13

Nope. It's correct. It works by using ReShade's mipmap generation to do the averaging and then simply samples the result from the lowest mipmap.
Cheers, crosire =)
The administrator has disabled public write access.
The following user(s) said Thank You: brussell

brussells humble shaders: Adaption 6 months 3 weeks ago #14

if that's the case, shouldn't the calculated luma value be the same every time you hit reload? with my test scene (which is a completely static scene), this luma value fluctuates every time i reload.
Last Edit: 6 months 3 weeks ago by moriz1.
The administrator has disabled public write access.

brussells humble shaders: Adaption 6 months 3 weeks ago #15

I can't reproduce this behavior. And even if the first frame after a reload somehow has a luma of 0 or 1, it should settle after a few seconds (depending on AdaptionSpeed).
The administrator has disabled public write access.

brussells humble shaders: Adaption 6 months 3 weeks ago #16

my mistake. apparently i didn't set the mipmap level correctly (i was trying out the sampling algorithm in a different shader), so it must be sampling that isn't actually a 1x1 texture.

i did however, notice that the luma value changes if i alt-tab in and out of the game window. doing this will cause the calculated luma to rise by about 0.05 in my test scene. not sure what that was about.

btw, this is the code i'm using to display the luma on screen:
float res = -0.1;

int line0[14] = { __L, __u, __m, __a, __Space, __C, __u, __r, __r, __e, __n, __t, __Colon, __Space };	//Luma Current: %d

DrawText_String(float2(100.0, 132.0), 32, 1, texcoord, line0, 14, res);
DrawText_Digit(DrawText_Shift(float2(100.0, 132.0), int2(14, 0), 32, 1), 32, 1, texcoord, 3, avglum, res);
if ( res != -0.1)
	return res;

you need to include "DrawText.fxh" for this to work. this code snippet will print "Luma Current: ####" on the top left corner of the screen.

EDIT: is it a good idea to use gamma correction? i've been using:
float CalcLuma(float3 color) {
	if (GammaCorrectionEnable) {
    	return pow((color.r*2 + color.b + color.g*3) / 6, 1/2.2);
	}
	
	return (color.r*2 + color.b + color.g*3) / 6;
}
Last Edit: 6 months 3 weeks ago by moriz1.
The administrator has disabled public write access.

brussells humble shaders: Adaption 6 months 2 weeks ago #17

moriz1 wrote:
i did however, notice that the luma value changes if i alt-tab in and out of the game window. doing this will cause the calculated luma to rise by about 0.05 in my test scene. not sure what that was about.

btw, this is the code i'm using to display the luma on screen:
float res = -0.1;

int line0[14] = { __L, __u, __m, __a, __Space, __C, __u, __r, __r, __e, __n, __t, __Colon, __Space };	//Luma Current: %d

DrawText_String(float2(100.0, 132.0), 32, 1, texcoord, line0, 14, res);
DrawText_Digit(DrawText_Shift(float2(100.0, 132.0), int2(14, 0), 32, 1), 32, 1, texcoord, 3, avglum, res);
if ( res != -0.1)
	return res;

you need to include "DrawText.fxh" for this to work. this code snippet will print "Luma Current: ####" on the top left corner of the screen.

There seems to be a bug(?) in the DrawTextDigit function. Even if you choose a fixed value like this
DrawText_Digit(DrawText_Shift(float2(100.0, 132.0), int2(14, 0), 32, 1), 32, 1, texcoord, 3, 1, res);
,something like 1.09 gets displayed.

EDIT: is it a good idea to use gamma correction? i've been using:
float CalcLuma(float3 color) {
	if (GammaCorrectionEnable) {
    	return pow((color.r*2 + color.b + color.g*3) / 6, 1/2.2);
	}
	
	return (color.r*2 + color.b + color.g*3) / 6;
}

No, not necessary.
Last Edit: 6 months 2 weeks ago by brussell.
The administrator has disabled public write access.

Eye Adaption 3 months 3 weeks ago #18

can this effect combined with vignette ? so if you face bright light you simulate human eyes that half closed to reduce light.
The administrator has disabled public write access.

Eye Adaption 3 months 3 weeks ago #19

PureEvilWindom wrote:
can this effect combined with vignette ? so if you face bright light you simulate human eyes that half closed to reduce light.

Sure. How about that: i.imgur.com/fJsqNji.gifv
I've also tried to mimic some glare-through-eye lids-effect recently: i.imgur.com/ROF4BEW.gifv
If there's more resonance I will add it.
The administrator has disabled public write access.
The following user(s) said Thank You: PureEvilWindom, Meddy

Eye Adaption 3 months 3 weeks ago #20

OMG, it is vey nice =)

post this fast
The administrator has disabled public write access.
  • Page:
  • 1
  • 2