Unreal 5.6 - Kuwahara
Post-Process Technique

KUWAHARA 5.6

Music by Simon Henry - Sometime Ago

The Kuwahara Post Process technique is an imaging technique, originally developed by Michiyoshi Kuwahara.

A doctor working on the university of Kyoto, Japan, in the 1970’s.

In unreal engine, it’s used artistically to simulate a painted environment.

There are many versions of the Kuwahara filter for UE 5.6

however, in my version I add the ability to
add a texture to simulate a canvas, and it’s quite literally how I start the base of my paintings.

Like in this case — “The Gardener”

In order to save time, I scult as normal,
or model a mesh, create a depth map and apply the filter.


I now have a solid color-base to begin painting in photoshop as normal with my brushes and ComfyUI.

Saving me time, making the process efficient and above all, working smartly.

KUWAHARA CODE

Custom Node

Unreal 5.6

//Kuwahara UE5.6 Version by Joshua Balcaceres //www.balcaceres.art

float3 mean[4] = { {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0} };

float3 sigma[4] = { {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0} };

float2 offsets[4] = {{-RADIUS.x, -RADIUS.y}, {-RADIUS.x, 0}, {0, -RADIUS.y}, {0, 0}};

float2 pos; float3 col;

float gradientX = 0; float gradientY = 0;

float sobelX[9] = {-1, -2, -1, 0, 0, 0, 1, 2, 1}; float sobelY[9] = {-1, 0, 1, -2, 0, 2, -1, 0, 1};

int index = 0; float2 texelSize = 1.0/VIEWSIZE;

for(int x = -1; x <= data-preserve-html-node="true" 1; x++) { for(int y = -1; y <= data-preserve-html-node="true" 1; y++) { if(index == 4) { index++; continue; }

    float2 offset = float2(x, y) * texelSize;
    float3 pxCol = SceneTextureLookup(UV + offset, 14, false).xyz;
    float pxLum = dot(pxCol, float3(0.2126, 0.7152, 0.0722));

    gradientX += pxLum * sobelX[index];
    gradientY += pxLum * sobelY[index];

    index++;

}

}

float angle = 0;

if(abs(gradientX) > 0.001) { atan(gradientY / gradientX); }

float s = sin(angle); float c = cos(angle);

for(int i = 0; i < 4; i++) { for(int j = 0; j <= data-preserve-html-node="true" RADIUS.x; j++) { for(int k = 0; k <= data-preserve-html-node="true" RADIUS.y; k++) { pos = float2(j, k) + offsets[i]; float2 offs = pos * texelSize;

        offs = float2(offs.x * c - offs.y * s, offs.x * s+ offs.y *c);

        float2 uvpos = UV + offs;
        col = SceneTextureLookup(uvpos, 14, false);

        mean[i] += col;

        sigma[i] += col * col;

    }

}

}

float n = (RADIUS.x+1) * (RADIUS.y+1); float sigma_f; float min = 1;

for(int i = 0; i < 4; i++) { mean[i] /= n; sigma[i] = abs(sigma[i] / n - mean[i] * mean[i]); sigma_f = sigma[i].r + sigma[i].g + sigma[i].b;

if(sigma_f < min)
    {
        min = sigma_f;
        col = mean[i];
    }

}

return col;