GLSL Edge Detection
Yesterday, I read 834144373’s ShaderToy code which did GLSL Edge Detection in 97 chars, it was really simple and fast:
1 2 3 4 |
void mainImage(out vec4 o, vec2 u) { o -= o - length(fwidth(texture2D(iChannel0,u/iResolution.xy)))*3.; } |
Meanwhile, the rendering result is astonishingly awesome:
However, there are some noise from the GLSL edge detection.
Thus, I have made a little improvement on this algorithm and produced cleaner edges in this ShaderToy live demo:
Let’s expand the code a little bit:
1 2 3 4 5 |
void mainImage(out vec4 fragColor, vec2 fragCoord) { vec2 uv = fragCoord / iResolution.xy; fragColor = length(fwidth(texture2D(iChannel0, uv))) * vec4(3.); } |
According to OpenGL manual:
fwidth means
abs
(dFdx
(p)) +abs
(dFdy
(p))and
fwidthFine
is equivalent to.
abs
(dFdxFine
(p)) +abs
(dFdyFine
(p))
Sigmoid Function
Furthermore, one can use a Sigmoid function to increase the contrast. According to Wikipedia, a sigmoid function is a mathematical function having a characteristic “S”-shaped curve or sigmoid curve. Often, sigmoid function refers to the special case of the logistic function shown in the first figure and defined by the formula.
1 2 3 4 5 6 7 8 9 10 11 12 |
float sigmoid(float a, float f) { return 1.0/(1.0+exp(-f*a)); } void mainImage(out vec4 fragColor, vec2 fragCoord) { vec2 uv = fragCoord / iResolution.xy; float edgeStrength = length(fwidth(texture2D(iChannel0, uv))); edgeStrength = sigmoid(edgeStrength - 0.2, 32.0); fragColor = vec4(vec3(edgeStrength), 1.0); } |
Better, uh? Wait, there is still some noise… let’s add a bilateral filter!
A bilateral filter is an edge-preserving noise reduction filter:
After bilateral filter, the results did get improved:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
#ifdef GL_ES precision mediump float; #endif #define SIGMA 10.0 #define BSIGMA 0.1 #define MSIZE 15 float kernel[MSIZE]; float normpdf(in float x, in float sigma) { return 0.39894*exp(-0.5*x*x/(sigma*sigma))/sigma; } float normpdf3(in vec3 v, in float sigma) { return 0.39894*exp(-0.5*dot(v,v)/(sigma*sigma))/sigma; } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { if (iFrame < 1) { vec3 c = texture2D(iChannel0, (fragCoord.xy / iResolution.xy)).rgb; //declare stuff const int kSize = (MSIZE-1)/2; kernel[0] = 0.031225216; kernel[1] = 0.033322271; kernel[2] = 0.035206333; kernel[3] = 0.036826804; kernel[4] = 0.038138565; kernel[5] = 0.039104044; kernel[6] = 0.039695028; kernel[7] = 0.039894000; kernel[8] = 0.039695028; kernel[9] = 0.039104044; kernel[10] = 0.038138565; kernel[11] = 0.036826804; kernel[12] = 0.035206333; kernel[13] = 0.033322271; kernel[14] = 0.031225216; /* //create the 1-D kernel for (int j = 0; j <= kSize; ++j) { kernel[kSize+j] = kernel[kSize-j] = normpdf(float(j), SIGMA); } */ vec3 final_colour = vec3(0.0); float Z = 0.0; vec3 cc; float factor; float bZ = 1.0/normpdf(0.0, BSIGMA); //read out the texels for (int i = -kSize; i <= kSize; ++i) { for (int j = -kSize; j <= kSize; ++j) { cc = texture2D(iChannel0, (fragCoord.xy+vec2(float(i),float(j))) / iResolution.xy).rgb; factor = normpdf3(cc-c, BSIGMA) * bZ * kernel[kSize+j] * kernel[kSize+i]; Z += factor; final_colour += factor*cc; } } fragColor = vec4(final_colour/Z, 1.0); } else { fragColor = vec4(texture2D(iChannel1, (fragCoord.xy / iResolution.xy)).rgb, 1.0); } } |
…
But this is still not as good as my canny edge filter:
I’ll write my canny-based GLSL edge detection in ShaderToy in some day…
Low-poly Style Image Generation in GLSL
Edge detection can be further used for low-poly style image generation:
See live demo here:
Wireframes in 3D Mesh
Fwidth can be further used to render wireframes in 3D meshes.
- Label each vertices with pure Red, Green and Blue
- Use Fwidth to determine the fraction part of the derivatives:
1 2 3 |
float3 d = fwidth(input.col.xyz); float3 tdist = smoothstep(float3(0.0, 0.0, 0.0), d * 1.0, input.col.xyz); float4 resultColor = lerp(float4(0.0, 1.0, 0.0, 1.0), float4(0.0, 0.0, 0.0, 1.0), min(min(tdist.x, tdist.y), tdist.z)); |