Importance of blur with image gradients
While working on generative paintings with brush strokes, aligning the brush strokes with the gradients of the colors in the source images can make a huge difference on the results of the rendering. It's really important to first blur your source image before you calculate the gradients.
In the images below you can see the results of calculating the gradients with and withouth blurring the source/input image.
Brush strokes without blur:

Brush strokes with blur:

I created a simple blur shader which reads from the current
GL_READ_FRAMEBUFFER and then applies a horizontal + vertical blur and
stores the result in a texture that you can read back by
calling setAsReadBuffer(). (I'm using a couple of custom classes
for logging which you can simple remove or create your own macro
to replace them; see RX_*()).
Blur.h
/* # Blur Simple blur shader which performs 2 passes; a vertical and horizontal blur. This class assumes you set the read framebuffer and then call blur(). We read the current GL_READ_FRAMEBUFFER into our intermediate scene texture and then perfrom 2 passes. This means we might have one extra blit which might be considered a performance issue; thouh on modern gpus you won't notice anything. To use the blurred result, call setAsReadBuffer() and either blit it to the default framebuffer or do something else with it */ #ifndef ROXLU_BLUR_H #define ROXLU_BLUR_H #include <roxlu/core/Constants.h> #include <roxlu/opengl/GL.h> static const char* B_VS = "" "#version 150\n" "const float w = 1.0;" "const float h = 1.0;" "const vec2 vertices[4] = vec2[](" " vec2(-w, h), " " vec2(-w, -h), " " vec2( w, h), " " vec2( w, -h) " ");" "const vec2 texcoords[4] = vec2[] (" " vec2(0.0, 0.0), " " vec2(0.0, 1.0), " " vec2(1.0, 0.0), " " vec2(1.0, 1.0) " ");" "out vec2 v_tex;" "void main() {" " gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);" " v_tex = texcoords[gl_VertexID]; " "}" ""; class Blur { public: Blur(); ~Blur(); bool setup(int winW, int winH, float blurAmount = 8.0f, int texFetches = 5); void blur(); /* applies the blur */ void setAsReadBuffer(); /* sets the result to the current read buffer */ private: bool setupFBO(); bool setupShader(); float gauss(const float x, const float sigma2); void shutdown(); /* resets this class; destroys all allocated objects */ public: GLuint fbo; /* the framebuffer for rtt */ GLuint depth; /* depth buffer; not 100% we actually need one */ GLuint vao; /* we use attribute-less rendering; but we need a vao as GL core 3 does not allow drawing with the default VAO */ GLuint prog0; /* program for the vertical blur */ GLuint prog1; /* program for the horizontal blur */ GLuint vert; /* the above vertex shader */ GLuint frag0; /* vertical fragment shader */ GLuint frag1; /* horizontal fragment shader */ GLuint tex0; /* first pass (scene capture) + combined blur */ GLuint tex1; /* intermedia texture */ int win_w; /* width of the window/fbo */ int win_h; /* height of the window/fbo */ float blur_amount; /* the blur amount, 5-8 normal, 8+ heave */ int num_fetches; /* how many texel fetches (half), the more the heavier for the gpu but more blur */ }; #endif
Blur.cpp
#include <assert.h> #include <math.h> #include <roxlu/opengl/GL.h> #include <roxlu/opengl/Error.h> #include <roxlu/core/Log.h> #include <sstream> #include <string.h> #include "Blur.h" Blur::Blur() :fbo(0) ,depth(0) ,vao(0) ,prog0(0) ,prog1(0) ,vert(0) ,frag0(0) ,frag1(0) ,tex0(0) ,tex1(0) ,win_w(0) ,win_h(0) ,blur_amount(0) ,num_fetches(0) { } Blur::~Blur() { shutdown(); } bool Blur::setup(int winW, int winH, float blurAmount, int texFetches) { assert(winW); assert(winH); assert(blurAmount); assert(texFetches); win_w = winW; win_h = winH; blur_amount = blurAmount; num_fetches = texFetches; glGenVertexArrays(1, &vao); if(!setupFBO()) { shutdown(); return false; } if(!setupShader()) { shutdown(); return false; } return true; } bool Blur::setupFBO() { glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glGenRenderbuffers(1, &depth); glBindRenderbuffer(GL_RENDERBUFFER, depth); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, win_w, win_h); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth); glGenTextures(1, &tex0); glBindTexture(GL_TEXTURE_2D, tex0); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, win_w, win_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0); glGenTextures(1, &tex1); glBindTexture(GL_TEXTURE_2D, tex1); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, win_w, win_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex1, 0); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if(status != GL_FRAMEBUFFER_COMPLETE) { RX_ERROR("Framebuffer not complete"); shutdown(); return false; } return true; } bool Blur::setupShader() { // CREATE SHADER SOURCES // ----------------------------------------------------------------------- int num_els = num_fetches; /* kernel size */ float* weights = new float[num_els]; float sum = 0.0; float sigma2 = blur_amount; weights[0] = gauss(0,sigma2); sum = weights[0]; for(int i = 1; i < num_els; i++) { weights[i] = gauss(i, sigma2); sum += 2 * weights[i]; } for(int i = 0; i < num_els; ++i) { weights[i] = weights[i] / sum; } float dy = 1.0 / win_h; float dx = 1.0 / win_w; std::stringstream xblur; std::stringstream yblur; std::stringstream base_frag; base_frag << "#version 150\n" << "uniform sampler2D u_scene_tex;\n" << "in vec2 v_tex;\n" << "out vec4 fragcolor;\n"; xblur << base_frag.str() << "void main() {\n" << " fragcolor = texture(u_scene_tex, v_tex) * " << weights[0] << ";\n"; yblur << base_frag.str() << "void main() {\n" << " fragcolor = texture(u_scene_tex, v_tex) * " << weights[0] << ";\n"; for(int i = 1 ; i < num_els; ++i) { yblur << " fragcolor += texture(u_scene_tex, v_tex + vec2(0.0, " << float(i) * dy << ")) * " << weights[i] << ";\n" ; yblur << " fragcolor += texture(u_scene_tex, v_tex - vec2(0.0, " << float(i) * dy << ")) * " << weights[i] << ";\n" ; xblur << " fragcolor += texture(u_scene_tex, v_tex + vec2(" << float(i) * dx << ", 0.0)) * " << weights[i] << ";\n" ; xblur << " fragcolor += texture(u_scene_tex, v_tex - vec2(" << float(i) * dx << ", 0.0)) * " << weights[i] << ";\n" ; } yblur << "}"; xblur << "}"; delete weights; weights = NULL; // ----------------------------------------------------------------------- std::string yblur_s = yblur.str(); std::string xblur_s = xblur.str(); const char* yblur_vss = yblur_s.c_str(); const char* xblur_vss = xblur_s.c_str(); vert = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vert, 1, &B_VS, NULL); glCompileShader(vert); eglGetShaderInfoLog(vert); frag0 = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(frag0, 1,&yblur_vss, NULL); glCompileShader(frag0); eglGetShaderInfoLog(frag0); prog0 = glCreateProgram(); glAttachShader(prog0, vert); glAttachShader(prog0, frag0); glLinkProgram(prog0); eglGetShaderLinkLog(prog0); frag1 = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(frag1, 1, &xblur_vss, NULL); glCompileShader(frag1); eglGetShaderInfoLog(frag1); prog1 = glCreateProgram(); glAttachShader(prog1, vert); glAttachShader(prog1, frag1); glLinkProgram(prog1); eglGetShaderLinkLog(prog1); GLint u_scene_tex = 0; // set scene tex unit for 1st pass glUseProgram(prog0); u_scene_tex = glGetUniformLocation(prog0, "u_scene_tex"); if(u_scene_tex < 0) { RX_ERROR("Cannot find u_scene_tex (1)"); shutdown(); return false; } glUniform1i(u_scene_tex, 0); // set scene tex unit for 2nd pass glUseProgram(prog1); u_scene_tex = glGetUniformLocation(prog1, "u_scene_tex"); if(u_scene_tex < 0) { RX_ERROR("Cannot find u_scene_tex (2)"); shutdown(); return false; } glUniform1i(u_scene_tex, 0); return true; } void Blur::blur() { assert(fbo); // Blur the current read framebuffer into our first attachment (the source) GLenum draw_bufs0[] = { GL_COLOR_ATTACHMENT0 } ; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDrawBuffers(1, draw_bufs0); glViewport(0,0,win_w, win_h); glBlitFramebuffer(0, 0, win_w, win_h, 0, 0, win_w, win_h, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBindVertexArray(vao); { // vertical blur GLenum draw_bufs1[] = { GL_COLOR_ATTACHMENT1 } ; glDrawBuffers(1, draw_bufs1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex0); glUseProgram(prog0); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } { // horizontal blur GLenum draw_bufs1[] = { GL_COLOR_ATTACHMENT0 } ; glDrawBuffers(1, draw_bufs1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex1); glUseProgram(prog1); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } void Blur::setAsReadBuffer() { assert(fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glReadBuffer(GL_COLOR_ATTACHMENT0); } float Blur::gauss(const float x, const float sigma2) { double coeff = 1.0 / (2.0 * PI * sigma2); double expon = -(x*x) / (2.0 * sigma2); return (float) (coeff*exp(expon)); } void Blur::shutdown() { if(fbo) { glDeleteFramebuffers(1, &fbo); } fbo = 0; if(depth) { glDeleteRenderbuffers(1, &depth); } depth = 0; if(vao) { glDeleteVertexArrays(1, &vao); } vao = 0; if(prog0) { glDeleteProgram(prog0); } prog0 = 0; if(prog1) { glDeleteProgram(prog1); } prog1 = 0; if(vert) { glDeleteShader(vert); } vert = 0; if(frag0) { glDeleteShader(frag0); } frag0 = 0; if(frag1) { glDeleteShader(frag1); } frag1 = 0; if(tex0) { glDeleteTextures(1, &tex0); } tex0 = 0; if(tex1) { glDeleteTextures(1, &tex1); } tex1 = 0; win_w = 0; win_h = 0; blur_amount = 0.0f; num_fetches = 0; }
NAT Types
Building Cabinets
Compiling GStreamer from source on Windows
Debugging CMake Issues
Dual Boot Arch Linux and Windows 10
Mindset Updated Edition, Carol S. Dweck (Book Notes)
How to setup a self-hosted Unifi NVR with Arch Linux
Blender 2.8 How to use Transparent Textures
Compiling FFmpeg with X264 on Windows 10 using MSVC
Blender 2.8 OpenGL Buffer Exporter
Blender 2.8 Baking lightmaps
Blender 2.8 Tips and Tricks
Setting up a Bluetooth Headset on Arch Linux
Compiling x264 on Windows with MSVC
C/C++ Snippets
Reading Chunks from a Buffer
Handy Bash Commands
Building a zero copy parser
Kalman Filter
Saving pixel data using libpng
Compile Apache, PHP and MySQL on Mac 10.10
Fast Pixel Transfers with Pixel Buffer Objects
High Resolution Timer function in C/C++
Rendering text with Pango, Cairo and Freetype
Fast OpenGL blur shader
Spherical Environment Mapping with OpenGL
Using OpenSSL with memory BIOs
Attributeless Vertex Shader with OpenGL
Circular Image Selector
Decoding H264 and YUV420P playback
Fast Fourier Transform
OpenGL Rim Shader
Rendering The Depth Buffer
Delaunay Triangulation
RapidXML
Git Snippets
Basic Shading With OpenGL
Open Source Libraries For Creative Coding
Bouncing particle effect
OpenGL Instanced Rendering
Mapping a texture on a disc
Download HTML page using CURL
Height Field Simulation on GPU
OpenCV
Some notes on OpenGL
Math
Gists to remember
Reverse SSH
Working Set
Consumer + Producer model with libuv
Parsing binary data
C++ file operation snippets
Importance of blur with image gradients
Real-time oil painting with openGL
x264 encoder
Generative helix with openGL
Mini test with vector field
Protractor gesture recognizer
Hair simulation
Some glitch screenshots
Working on video installation
Generative meshes
Converting video/audio using avconv
Auto start terminal app on mac
Export blender object to simple file format