Generalized Kuwahara

Postprocessing Kuwahara shader

Ported from Unity to Godot 4, orignal by Acerola

– Video by Acerola: This is the Kuwahara Filter

– Source code: https://github.com/GarrettGunnell/Post-Processing/tree/main/Assets/Kuwahara%20Filter

Shader code
shader_type canvas_item;

const int _N = 8;

uniform sampler2D SCREEN_TEXTURE: hint_screen_texture, filter_linear_mipmap, repeat_disable;

uniform int _KernelSize: hint_range(2, 20, 1) = 2;

uniform float _Hardness: hint_range(1.0f, 100.0f) = 8.0f;
uniform float _Sharpness: hint_range(1.0f, 18.0f) = 8.0f;
uniform float _ZeroCrossing: hint_range(0.01f, 2.0f) = 0.58f;
uniform float _Zeta: hint_range(0.01f, 3.0f) = 1.0f;

varying vec2 texelSize;


float gaussian(float sigma, float pos) {
	return (1.0f / sqrt(2.0f * PI * sigma * sigma)) * exp(-(pos * pos) / (2.0f * sigma * sigma));
}


void vertex() {
	texelSize = vec2(1.0 / vec2(textureSize(SCREEN_TEXTURE, 0)));
}


void fragment() {
	vec4 m[8];
	vec3 s[8];

	int kernelRadius = _KernelSize / 2;

	//float zeta = 2.0f / (kernelRadius);
	float zeta = _Zeta;

	float zeroCross = _ZeroCrossing;
	float sinZeroCross = sin(zeroCross);
	float eta = (zeta + cos(zeroCross)) / (sinZeroCross * sinZeroCross);

	for (int k = 0; k < _N; ++k) {
		m[k] = vec4(0.0f);
		s[k] = vec3(0.0f);
	}

	for (int y = -kernelRadius; y <= kernelRadius; ++y) {
		for (int x = -kernelRadius; x <= kernelRadius; ++x) {
			vec2 v = vec2(float(x), float(y)) / float(kernelRadius);
			vec3 c = texture(SCREEN_TEXTURE, UV + vec2(float(x), float(y)) * texelSize.xy).rgb;
			c = clamp(c, 0.0f, 1.0f);
			float sum = 0.0f;
			float w[8];
			float z, vxx, vyy;

			/* Calculate Polynomial Weights */
			vxx = zeta - eta * v.x * v.x;
			vyy = zeta - eta * v.y * v.y;
			z = max(0, v.y + vxx);
			w[0] = z * z;
			sum += w[0];
			z = max(0, -v.x + vyy);
			w[2] = z * z;
			sum += w[2];
			z = max(0, -v.y + vxx);
			w[4] = z * z;
			sum += w[4];
			z = max(0, v.x + vyy);
			w[6] = z * z;
			sum += w[6];
			v = sqrt(2.0f) / 2.0f * vec2(v.x - v.y, v.x + v.y);
			vxx = zeta - eta * v.x * v.x;
			vyy = zeta - eta * v.y * v.y;
			z = max(0, v.y + vxx);
			w[1] = z * z;
			sum += w[1];
			z = max(0, -v.x + vyy);
			w[3] = z * z;
			sum += w[3];
			z = max(0, -v.y + vxx);
			w[5] = z * z;
			sum += w[5];
			z = max(0, v.x + vyy);
			w[7] = z * z;
			sum += w[7];

			float g = exp(-3.125f * dot(v,v)) / sum;

			for (int k = 0; k < 8; ++k) {
				float wk = w[k] * g;
				m[k] += vec4(c * wk, wk);
				s[k] += c * c * wk;
			}
		}
	}

	vec4 output = vec4(0.0f);
	for (int k = 0; k < _N; ++k) {
		m[k].rgb /= m[k].w;
		s[k] = abs(s[k] / m[k].w - m[k].rgb * m[k].rgb);

		float sigma2 = s[k].r + s[k].g + s[k].b;
		float w = 1.0f / (1.0f + pow(_Hardness * 1000.0f * sigma2, 0.5f * _Sharpness));

		output += vec4(m[k].rgb * w, w);
	}

	COLOR.rgb = clamp(output / output.w, 0.0f, 1.0f).rgb;
}
Live Preview
Tags
acerola, Anime, generalized kuwahara, kuwahara, npr, Post processing, postprocessing, stylized
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from Firerabbit

Related shaders

Comments 4

  1. whats the setup in engine? I tried making my main camera a subviewport, rendering that subviewport to a texturerect, and then applying this shader to that texture rect but that doesnt seem to work :(

    2
      • Just add a ColorRect and set its "Anchor Preset" to "Full Rect" in the editor

      (you can find this option if you select the ColorRect and got to the 2DView, its the one left to the anchor)

      • Add this shader in the Material as the shader and adjust the settings.
      • The Default settings are very suttle. If you want to increase the effect change the "Kernel Size".
      1