Wednesday, October 8, 2014

Bloom effect with Gaussian blur

In continuation from previous post about Render To Texture,

After rendering scene to texture, we can apply post-processing filters such as blur effect.

Here is the screenshot of what we are trying to achieve by combining FBO and Gaussian Blur

device-2014-07-29-085350

To achieve this,

1) Render scene to texture

2) Apply Gaussian Blur on texture in above step

3) Combine above 2 textures to get Bloom Effect

Original Texture Horizontal blur applied Vertical blur applied Final result
blur_original blur_horiz blur_both blur_bloom

As usual code is available in Google Code.

  public void RenderGaussianBlur()
  {
    GLES20.glViewport(0, 0, fboWidth, fboHeight);
    float ratio = (float)fboWidth/(float)fboHeight;
    float a = 5f;
    Matrix.orthoM(m_fProjMatrix, 0, -a*ratio, a*ratio, -a*ratio, a*ratio, 1, 10);
    Matrix.multiplyMM(m_fVPMatrix, 0, m_fProjMatrix, 0, m_fViewMatrix, 0);
    //render scene to texture fboTex
    RenderToTexture();
    //apply blur filter
    Blur();
  }

 


I’ll discuss here about Gaussian Blur filter alone, since FBO is covered in previous post.


we use Gaussian Blur with linear approximation in 2 steps.


1) Apply vertical blur filter on texture and render it to another texture.


2) Apply horizontal blur on above rendered texture.


below is code both steps.

public void BlurStep(int step)
  {
   //apply horizontal blur
    if (step == 1)
      GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboIdStep1); 
    else if (step == 2)//apply vertical blur
      GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboIdStep2);
    
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
    
    vertexBuffer.position(0);
    GLES20.glVertexAttribPointer(iPositionRTT, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer2);
    GLES20.glEnableVertexAttribArray(iPositionRTT);
    
    texBuffer.position(0);
    GLES20.glVertexAttribPointer(iTexCoordsRTT, 2, GLES20.GL_FLOAT, false, 0, texBuffer1);
    GLES20.glEnableVertexAttribArray(iTexCoordsRTT);
    
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    if (step == 1)
      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fboTex);
    else if (step == 2)
      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fboTexStep1);
    
    GLES20.glUniform1i(iTexLocRTT, 0);
    
    Matrix.setIdentityM(m_fModel, 0);    
    
    Matrix.multiplyMM(m_fMVPMatrix, 0, m_fVPMatrix, 0, m_fModel, 0);
    
    GLES20.glUniformMatrix4fv(iVPMatrix, 1, false, m_fMVPMatrix, 0);
    
    if (step == 1)
      GLES20.glUniform1i(iDirection, 0);
    else if (step == 2)
      GLES20.glUniform1i(iDirection, 1);
    
    GLES20.glUniform1f(iBlurScale,  1.0f);
    
    GLES20.glUniform1f(iBlurAmount, 20f);    
    GLES20.glUniform1f(iBlurStrength, 0.5f);
      
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
  }

Most important part of blur filter is blur shader. There are many approaches for achieving blur effect without compromising render speed.


To learn more about linear sampling, please follow this link.

precision mediump float;
varying vec2 v_texCoords;
uniform sampler2D u_texId;
uniform int direction;
uniform float blurScale;
uniform float blurAmount;
uniform float blurStrength;
 
float GaussianFunction(float x, float dev)
{
  return ((1.0/sqrt(2.0*3.142857*dev))*exp(-(x*x)/(2.0*dev)));
} 
 
void main() 
{  
  float dev = blurAmount*0.5*0.5;
  dev *= dev;
  vec4 color = vec4(0.0,0.0,0.0,0.0);
  vec4 temp =  vec4(0.0,0.0,0.0,0.0);
  float strength = 1.0 - blurStrength;
  float half1 = float(blurAmount)*0.5;
  float texel = 1.0/128.0;
  int count = int(blurAmount);
  if (direction == 0) 
  {
    for (int i=0;i<count;i++) {
      float offset = float(i) - half1;
      temp = texture2D(u_texId, v_texCoords+vec2(offset*texel*blurScale,0.0))*GaussianFunction(offset*strength, dev);
      color += temp;
    }
  } 
  else 
  {
    for (int i=0;i<count;i++) {
      float offset = float(i) - half1;
      temp = texture2D(u_texId, v_texCoords+vec2(0.0,offset*texel*blurScale))*GaussianFunction(offset*strength, dev);
      color += temp;
    }
  }
  
  gl_FragColor = clamp(color, 0.0, 1.0);
  gl_FragColor.w = 1.0;
}