Tuesday, 26 March 2013

Bloom in Soaring Steele

One of the shader effects that we have implemented in our game Soaring Steele is a bloom effect, this effect is great because it helps being our game to life because for the most part you are flying through the bright sky with the sun beating down.

Definition:

Bloom (sometimes referred to as light bloom or glow) is a computer graphics effect used in video gamesdemos and high dynamic range rendering (HDR) to reproduce an imaging artifact of real-world cameras. The effect produces fringes (or feathers) of light extending from the borders of bright areas in an image, contributing to the illusion of an extremely bright light overwhelming the camera or eye capturing the scene. (Wikipedia)


Before and After Comparison:


C++ Code Sample:
Here is an example of how I integrate bloom into my draw loop.
Steps: 
  1. Draw the Scene to an Frame Buffer Object
  2. Draw the Frame Buffer Object with a Bright Pass Shader
  3. Blur the Bright Pass using a two pass Gaussian blur
  4. Combine the Bright/Blur pass and the Scene pass together and draw as the final image.
1:  particleBase->bind();  
2:    
3:            Scene::draw3D();  
4:    
5:            particleBase->unbind();  
6:    
7:            particleBrightPass->bind();  
8:    
9:                 BrightPass->begin();  
10:    
11:                      BrightPass->setUniform("luminance",luminance);  
12:                      BrightPass->setUniform("middleGrey",middleGrey);  
13:                      BrightPass->setUniform("threshold",threshold);  
14:    
15:                      glActiveTexture(GL_TEXTURE0);  
16:                      glBindTexture(GL_TEXTURE_2D,particleBase->getColourTexture(0));  
17:                      BrightPass->setUniform("particlebase",0);  
18:    
19:                      particleBase->draw();  
20:                   
21:                 BrightPass->end();  
22:    
23:            particleBrightPass->unbind();  
24:    
25:            guassian1DBlurX();  
26:            guassian1DBlurY();  
27:    
28:            combinePass->bind();  
29:    
30:                 CombinePass->begin();  
31:    
32:                      glActiveTexture(GL_TEXTURE0);  
33:                      glBindTexture(GL_TEXTURE_2D,particleBase->getColourTexture(0));  
34:                      CombinePass->setUniform("baseInput",0);  
35:    
36:                      glActiveTexture(GL_TEXTURE1);  
37:                      glBindTexture(GL_TEXTURE_2D,particleGuassianTwoPassYBlur->getColourTexture(0));  
38:                      CombinePass->setUniform("adjustmentInput",1);  
39:    
40:                      particleGuassianTwoPassYBlur->draw();       
41:    
42:                 CombinePass->end();  
43:    
44:            combinePass->unbind();  
45:    
46:            combinePass->draw(1);  

Shaders:

Bright Pass:

Here is where I apply the bright pass.
 #version 330  
 in vec2 uv0;  
 out vec4 colour;  
 uniform sampler2D particlebase;  
 uniform float luminance = 1;  
 uniform float middleGrey = 1;  
 uniform float threshold = 1;  
 void main()  
 {  
      vec3 color = texture2D(particlebase,uv0).rgb;  
      colour *= (middleGrey / luminance);  
      colour *= (1.0 + (colour / (threshold * threshold) ));  
      colour -= 0.5;  
      colour /= (1.0 + (colour * colour));  
      colour.rgb = color;  
 }  

X Pass Guassian Blur:

In this pass I take the result of the bright pass and blur it along the x axis.

 #version 330  
 vec2 uv0[5];  
 float weights[5];  
 in vec2 uv1;  
 out vec4 colour;  
 uniform sampler2D brightpassInput;  
 uniform vec2 pixelSize;  
 vec3 GuassianBlur()  
 {  
      vec3 result = vec3(0.0);  
      for(int i = 0; i < 5; i++)  
      {  
           result += texture2D(brightpassInput,uv0[i]).rgb * weights[i];  
      }  
      return result;  
 }  
 void CalculateUvs_Weights()  
 {  
      uv0[0] = uv1 + vec2(-pixelSize.x*2,0);  
      uv0[1] = uv1 + vec2(-pixelSize.x,0);  
      uv0[2] = uv1 + vec2(0,0);  
      uv0[3] = uv1 + vec2(pixelSize.x,0);  
      uv0[4] = uv1 + vec2(pixelSize.x*2,0);  
      weights[0] = 0.01330373 / 0.47365426;  
      weights[1] = 0.11098164 / 0.47365426;  
      weights[2] = 0.22508352 / 0.47365426;  
      weights[3] = 0.11098164 / 0.47365426;  
      weights[4] = 0.01330373 / 0.47365426;  
 }  
 void main()  
 {  
      CalculateUvs_Weights();  
      colour.rgb = GuassianBlur();  
 }  

Y Pass Gaussian Blur:

In this pass I blur the x pass, along the y axis.
 #version 330  
 vec2 uv0[5];  
 float weights[5];  
 in vec2 uv1;  
 out vec4 colour;  
 uniform sampler2D brightpassInput;  
 uniform vec2 pixelSize;  
 vec3 GuassianBlur()  
 {  
      vec3 result = vec3(0.0);  
      for(int i = 0; i < 5; i++)  
      {  
           result += texture2D(brightpassInput,uv0[i]).rgb * weights[i];  
      }  
      return result;  
 }  
 void CalculateUvs_Weights()  
 {  
      uv0[0] = uv1 + vec2(0,pixelSize.y*2);  
      uv0[1] = uv1 + vec2(0,pixelSize.y);  
      uv0[2] = uv1 + vec2(0,0);  
      uv0[3] = uv1 + vec2(0,-pixelSize.y);  
      uv0[4] = uv1 + vec2(0,-pixelSize.y*2);  
      weights[0] = 0.01330373 / 0.47365426;  
      weights[1] = 0.11098164 / 0.47365426;  
      weights[2] = 0.22508352 / 0.47365426;  
      weights[3] = 0.11098164 / 0.47365426;  
      weights[4] = 0.01330373 / 0.47365426;  
 }  
 void main()  
 {  
      CalculateUvs_Weights();  
      colour.rgb = GuassianBlur();  
 }  

Combine Pass:
Finally I combine the two passed using a screen filter.
 #version 330  
 in vec2 uv0;  
 uniform sampler2D baseInput;  
 uniform sampler2D adjustmentInput;  
 out vec4 colour;  
 vec3 screen(vec3 color1, vec3 color2)  
 {  
      return (1.0 - ((1.0 - color1) * (1.0 - color2)) );  
 }  
 void main()  
 {  
      vec3 colorA = texture2D(baseInput,uv0).rgb;  
      vec3 colorB = texture2D(adjustmentInput,uv0).rgb;  
      colour.rgb = screen(colorA,colorB);  
 }