Welcome, Guest.
Username: Password: Remember me

TOPIC: Kuwahara Shader as DoF

Kuwahara Shader as DoF 4 months 3 weeks ago #1

When I first saw Eideren's Kuwahara shader, I toyed with the idea of making a DoF shader that utilizes the effect, for games that have very stylized looks.

My rationale is that there's no point in going for photorealism for those heavily stylized games, because they were never designed for photorealism to begin with.

So, here's a demo of my shader running in Guild Wars 2, a game that has a "painterly" aesthetic. Kuwahara effect often results in a painting effect, with goes well with GW2's overall look:



Here's the source code:
/*
A slightly altered version of the basic Kuwahara filter

source:
Anisotropic Kuwahara Filtering on the GPU
www.kyprianidis.com
Paper on the related shader : http://www.kyprianidis.com/p/pg2009/jkyprian-pg2009.pdf
Preview App' : https://code.google.com/archive/p/gpuakf/downloads
Code : https://code.google.com/archive/p/gpuakf/source/default/source
Use this code in accordance to the license holder's license !

Ported over to Reshade by Eideren
DoF Effects by Moriz
*/


uniform int Iterations <
	ui_type = "drag";
	ui_min = 0.0;
	ui_max = 16.0;
	ui_label = "Iterations";
	ui_tooltip = "VERY HEAVY performance cost. Best to leave this at 4, unless SampleDistance is also high.";
> = 4;

uniform float SampleDistance <
	ui_type = "drag";
	ui_min = 0.0;
	ui_max = 10.0;
	ui_label = "SampleDistance";
	ui_tooltip = "Bigger this is, the stronger the effect.\nWarning: reduces accuracy if set too high, without also adjusting Iterations.";
> = 6.4;

uniform float DepthNearLimit <
	ui_type = "drag";
	ui_min = 0.0;
	ui_max = 0.1;
	ui_label = "Depth Near Limit";
	ui_tooltip = "Blurs object close to the screen.";
> = 0.01;

uniform float DepthFarLimit <
	ui_type = "drag";
	ui_min = 0.0;
	ui_max = 1.0;
	ui_label = "Depth Far Limit";
	ui_tooltip = "Blurs object in the distance.";
> = 0.27;

uniform float FocusDOFWidth <
	ui_type = "drag";
	ui_min = 0.0;
	ui_max = 0.4;
	ui_label = "Focus DOF Width";
	ui_tooltip = "Adjusts how far further DoF extends past focussed object.\nIf set to 0.0, Autofocus will only adjust to the focussed object.";
> = 0.06;

uniform float FocusSpeed <
	ui_type = "drag";
	ui_min = 0.0;
	ui_max = 1.0;
	ui_label = "Focus Speed";
> = 0.2;

uniform float RightClickFocusOffsetX <
	ui_type = "drag";
	ui_min = 0.0;
	ui_max = 1.0;
	ui_label = "Right Click Focus Offset X Axis";
	ui_tooltip = "Also affects Screen Center focus.\n0.0 is top of the screen.";
> = 0.5;

uniform float RightClickFocusOffsetY <
	ui_type = "drag";
	ui_min = 0.0;
	ui_max = 1.0;
	ui_label = "Right Click Focus Offset Y Axis";
	ui_tooltip = "Also affects Screen Center focus.\n0.0 is left side of the screen.";
> = 0.5;

uniform bool AlwaysUseScreenCenter <
    ui_type = "toggle";
    ui_label = "ALways Use Screen Center";
	ui_tooltip = "Disables mouse focussing. Always focuses onto a predetermined point on the screen.";
> = false;

uniform bool ShowDebug <
    ui_type = "toggle";
    ui_label = "Show Debug";
> = false;

uniform bool IgnoreDepthOne <
    ui_type = "toggle";
    ui_label = "Ignore Depth = 1";
	ui_tooltip = "Ignores the skybox.\nAlso affects character select screen and world map screen.";
> = true;

uniform bool UseAutoFocus <
    ui_type = "toggle";
    ui_label = "Use Auto Focus";
	ui_tooltip = "Turn off to disable all autofocus.";
> = true;

uniform bool AllowNearLimitAdjustments <
	ui_type = "toggle";
    ui_label = "Allow Near Limit Adjustments";
	ui_tooltip = "Turn on to allow near DoF boundary to move forward with autofocus.\nNot recommended for 3rd person perspectives.";
> = false;

uniform float NearLimitAdjustmentsMulti <
	ui_type = "drag";
	ui_min = 0.0001;
	ui_max = 1.0;
	ui_label = "Near Limit Adjustments Multiplier";
	ui_tooltip = "Controls the magnitude of Near Limit Adjustments";
> = 0.01;


uniform float2 MouseCoord < source = "mousepoint"; >;
uniform float frametime < source = "frametime"; >;
uniform bool RightClick < source = "mousebutton"; keycode = 1; toggle = false; >;

#include "ReShade.fxh"
#include "DrawText.fxh"

#define TexChannel0 (ReShade::BackBuffer)
#define TexChannel0_size (ReShade::ScreenSize.xy)

// Force lod for lower Shader Models
#define SAMPLE(s,uv) tex2Dlod(s, float4(uv, 0, 0))

texture FocusPointTex { Width = 1; Height = 1; Format = R16F; };
sampler FocusPointSampler { Texture = FocusPointTex; };

texture FocusPointTexPrev { Width = 1; Height = 1; Format = R16F; };
sampler FocusPointPrevSampler { Texture = FocusPointTexPrev; };

float4 KuwaharaModEffect(float effectPower, float2 tex) {
    float2 uv = tex.xy;
	float n = float((Iterations + 1) * (Iterations + 1));
    float3 m[4];
	float3 s[4];
	for (int k = 0; k < 4; ++k) {
		m[k] = (0.0);
		s[k] = (0.0);
	}

    for (int j = -Iterations; j <= 0; ++j)  {
		for (int i = -Iterations; i <= 0; ++i)  {
			float3 c = SAMPLE(TexChannel0, uv + float2(i,j) / TexChannel0_size * effectPower).rgb;
			m[0] += c;
			s[0] += c * c;
		}
	}
	
	for (int j = -Iterations; j <= 0; ++j)  {
		for (int i = 0; i <= Iterations; ++i)  {
			float3 c = SAMPLE(TexChannel0, uv + float2(i,j) / TexChannel0_size * effectPower).rgb;
			m[1] += c;
			s[1] += c * c;
		}
	}
	
	for (int j = 0; j <= Iterations; ++j)  {
		for (int i = 0; i <= Iterations; ++i)  {
			float3 c = SAMPLE(TexChannel0, uv + float2(i,j) / TexChannel0_size * effectPower).rgb;
			m[2] += c;
			s[2] += c * c;
		}
	}
	
	for (int j = 0; j <= Iterations; ++j)  {
		for (int i = -Iterations; i <= 0; ++i)  {
			float3 c = SAMPLE(TexChannel0, uv + float2(i,j) / TexChannel0_size * effectPower).rgb;
			m[3] += c;
			s[3] += c * c;
		}
	}
	
	float4 fragColor = 0;
	float min_sigma2 = 1e+2;
	float3 median = 0;
	for (int k = 0; k < 4; ++k) {
		m[k] /= n;
		s[k] = abs(s[k] / n - m[k] * m[k]);
		
		float sigma2 = s[k].r + s[k].g + s[k].b;
		median += m[k] / 4;
		if (sigma2 < min_sigma2) {
			min_sigma2 = sigma2;
			fragColor = float4(m[k], 1.0);
		}
	}
    
	return fragColor;
}

float GetFocusPass(float4 vpos : SV_POSITION, float2 texcoord : TexCoord) : SV_Target {
	float focusPoint = (AlwaysUseScreenCenter || RightClick) ? ReShade::GetLinearizedDepth(float2(0.5, 0.5)) : ReShade::GetLinearizedDepth(MouseCoord * ReShade::PixelSize);

	float focusPointPrev = tex2D(FocusPointPrevSampler, float2(RightClickFocusOffsetX, RightClickFocusOffsetY)).x;

	float focusSpeedMulti = (RightClick) ? 2.0 : 1.0;
	focusPoint = (focusPoint >= 1.0 && IgnoreDepthOne) ? focusPointPrev : focusPoint;

	return lerp(focusPointPrev, focusPoint, FocusSpeed*focusSpeedMulti*10.0/frametime);
}

float4 DofPass(float4 vpos : SV_Position, float2 tex : TexCoord) : SV_Target
{
	float depth = ReShade::GetLinearizedDepth(tex);

	float focusPoint = tex2D(FocusPointSampler, float2(0.5, 0.5)).x;
	
    if (ShowDebug) {
        float res = -0.1;

		int line0[13] = { __M, __o, __u, __s, __e, __Space, __C, __o, __o, __r, __d, __Colon, __Space };

		DrawText_String(float2(100.0, 132.0), 32, 1, tex, line0, 13, res);
		DrawText_Digit(DrawText_Shift(float2(200.0, 132.0), int2(13, 0), 32, 1), 32, 1, tex, 0, MouseCoord.x, res);
        DrawText_Digit(DrawText_Shift(float2(400.0, 132.0), int2(13, 0), 32, 1), 32, 1, tex, 0, MouseCoord.y, res);
        DrawText_Digit(DrawText_Shift(float2(500.0, 132.0), int2(13, 0), 32, 1), 32, 1, tex, 3, focusPoint, res);

		if ( res != -0.1)
			return res;
    }

    if (depth >= 1.0 && IgnoreDepthOne) {
        return tex2D(ReShade::BackBuffer, tex).rgba;
    }

	if (depth >= DepthNearLimit && depth <= DepthFarLimit && !AllowNearLimitAdjustments) {
		return tex2D(ReShade::BackBuffer, tex).rgba;
	}

    float focusNear = (focusPoint < DepthNearLimit && UseAutoFocus) ? focusPoint : DepthNearLimit;
    float focusFar = (focusPoint > DepthFarLimit && UseAutoFocus) ? saturate(focusPoint + FocusDOFWidth) : DepthFarLimit;

	if (AllowNearLimitAdjustments && UseAutoFocus) {
		focusNear = saturate(NearLimitAdjustmentsMulti * (focusPoint - FocusDOFWidth));
	}

    float calcDepthGradient = (depth < focusNear && depth < focusFar) ? sqrt(saturate(1 - (depth / focusNear))) : sqrt(saturate((depth - focusFar) / (1 - focusFar)));

	float scaledSampleDistance = SampleDistance / (Iterations+1) * calcDepthGradient;

    return KuwaharaModEffect(scaledSampleDistance, tex);
}

float StorePrevFocusPass(float4 vpos : SV_POSITION, float2 texcoord : TexCoord) : SV_Target {
	return tex2D(FocusPointSampler, float2(0.5, 0.5)).x;
}

technique KuwaharaModDOF
{
	pass
	{
		VertexShader = PostProcessVS;
		PixelShader = GetFocusPass;
		RenderTarget = FocusPointTex;
	}
	pass
	{
		VertexShader = PostProcessVS;
		PixelShader = DofPass;
	}
	pass
	{
		VertexShader = PostProcessVS;
		PixelShader = StorePrevFocusPass;
		RenderTarget = FocusPointTexPrev;
	}
}

I've implemented mouse and look autofocus, which autofocusses onto whatever your cursor is pointing at, or whatever is in the center of your screen.
The administrator has disabled public write access.
The following user(s) said Thank You: Eideren

Kuwahara Shader as DoF 4 months 3 weeks ago #2

I like the general result very much, might use it on WoW! Some tips, if you want: make sure to put a saturate() or a curve converging to 1.0 in the lerp for the focus interpolation, lerp factor as is might exceed 1.0 and lead to oscillation. Also try linking iterations to sample radius to improve performance a lot. Computing it in lower resolution might also help, not sure how quality degradation will be though.
Last Edit: 4 months 3 weeks ago by Marty McFly.
The administrator has disabled public write access.

Kuwahara Shader as DoF 4 months 2 weeks ago #3

I'm glad you like it!

Marty McFly wrote:
Some tips, if you want: make sure to put a saturate() or a curve converging to 1.0 in the lerp for the focus interpolation, lerp factor as is might exceed 1.0 and lead to oscillation.

good point. if FocusSpeed is set to 1, any frametime shorter than 20 ms will cause the resulting value to be greater than 1 when using right click. but the real issue here, is that my framerate independent damping algorithm is simply wrong. it really should be multiplier * frametime / 1000.0, since frametime is given in miliseconds. this way, unless multiplier is set to some astronomically high value, or if the game is running at around 0.1 fps, the lerp value would still remain in bounds.

Marty McFly wrote:
Also try linking iterations to sample radius to improve performance a lot.

is there a specific ratio where these two variables would result in best performance? the way it is set up right now, they merely act as a multiplier on the result of the depth calculations. i could simply replace the two with a single variable that can be adjusted between 0.5 and 1.5, and it will still work exactly the same.

EDIT: nevermind, Iterations does show up again in the Kuwahara effect calculations. derp. maybe i should have it be linked to depth calculations?
Last Edit: 4 months 2 weeks ago by moriz1.
The administrator has disabled public write access.