Game Development Community

dev|Pro Game Development Curriculum

A Gaussian Blur Effect for T2D

by Kevin Yancey · 04/08/2008 (2:16 pm) · 6 comments

This scene object works by using a dynamic texture to capture the part of the scene that is behind the scene object and put it into a texture, and then redrawing that texture with various offsets and alpha values to effectively blend each pixel's color with that of its neighbors. The amount that each pixel is blended is determined using a gaussian distribution (i.e., normal distribution). The BlurFactor is used as the standard deviation for the distribution. The greater the blur factor, the greater the blur. Also, this implementation stops blending pixels that are further than 3 * BlurFactor away, because the blending weight for these pixels would be so low as to have no noticable effect. Also note that because only those pixels within the scene object's area are copied to the texture, those pixels towards the edges of the scene object will not be blended with its neighbors outside the scene object's boundaries.

This implementation performs its blur effect in the horizontal direction first, and then repeats its process in the y direction. The execution time is O(n) bounded, where n is the blur factor.

Also, note that this effect will only work in OpenGL mode (note DirectX), unless the glu2d3d library is modified to implement the glCopyTexSubImage2D function, which should not be hard to do.

Below is the code to implement this scene object. There are many things that could be done to improve this implementation, but its enough for some purposes and enough to build on for others. If you make any improvements or find bugs, please feel free to post them and link to them from this page using a comment.

//BlurEffect.h
#include "dgl/gDynamicTexture.h"

#ifndef _T2DSCENEOBJECT_H_
#include "T2D/t2dSceneObject.h"
#endif

class t2dBlurEffect : public t2dSceneObject {
private:
	typedef t2dSceneObject Parent;
	DynamicTexture * m_texture;
	F32 m_blurFactor;

	void drawBlur(F32 xOffset, F32 yOffset, F32 * destAlpha, F32 sourceAlpha);
public:
	t2dBlurEffect();
	~t2dBlurEffect();
	virtual void renderObject( const RectF& viewPort, const RectF& viewIntersection );

	static void initPersistFields();

	DECLARE_CONOBJECT(t2dBlurEffect);
};

//BlurEffect.cpp

#include "dgl/dgl.h"
#include "core/color.h"
#include "math/mMathFn.h"
#include "console/consoleTypes.h"
#include "core/bitStream.h"
#include "dgl/gDynamicTexture.h"
#include "T2D/t2dSceneGraph.h"
#include "T2D/t2dUtility.h"
#include "./t2dBlurEffect.h"

t2dBlurEffect::t2dBlurEffect() : t2dSceneObject() {
	m_texture = new DynamicTexture();
	m_blurFactor = 2.0;
}

t2dBlurEffect::~t2dBlurEffect() {
	delete m_texture;
}

void t2dBlurEffect::initPersistFields() {
	addField("BlurFactor", TypeF32, Offset(m_blurFactor, t2dBlurEffect));

	Parent::initPersistFields();
}

void t2dBlurEffect::renderObject(const RectF& viewPort, const RectF& viewIntersection) {
	const RectI & windowBounds = this->getSceneGraph()->getCurrentRenderWindow()->getBounds();
	const RectF & camera = this->getSceneGraph()->getCurrentRenderWindow()->getCurrentCameraArea();
	t2dVector position = this->getPosition();
	t2dVector size = this->getSize();
	position.mX -= size.mX / 2;
	position.mY -= size.mY / 2;
	RectI updateRect;
	updateRect.point.set(
		windowBounds.point.x + windowBounds.extent.x * ((position.mX - camera.point.x) / camera.extent.x),
		windowBounds.point.y + windowBounds.extent.y * ((position.mY - camera.point.y) / camera.extent.y));
	updateRect.extent.set(
		windowBounds.extent.x * size.mX / camera.extent.x,
		windowBounds.extent.y * size.mY / camera.extent.y);
	m_texture->setUpdateRect(updateRect);
	m_texture->update();

	glEnable(GL_TEXTURE_2D);

    glBindTexture(GL_TEXTURE_2D, m_texture->getTextureHandle().getGLName());
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    // Set Blend Options.
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

	glEnable(GL_SCISSOR_TEST);
	glScissor(updateRect.point.x, Platform::getWindowSize().y - updateRect.point.y - updateRect.extent.y, updateRect.extent.x, updateRect.extent.y);

   // Fetch Positions.
	F32 xOffset = camera.extent.x / (F32)windowBounds.extent.x;
	F32 yOffset = camera.extent.y / (F32)windowBounds.extent.y;

	U32 alphaSize = m_blurFactor * 3;
	F32 * alpha = new F32[alphaSize];

	F32 sdSqd = pow(m_blurFactor, 2.0f);
	for(int i = 0; i < alphaSize; ++i) {
		alpha[i] = 1.0f / (2.0f * 3.14159f * sdSqd) * pow(2.718281f, -1.0f * pow(i, 2.0f) / (2.0f * sdSqd));
	}

	F32 accumulatedAlpha = alpha[0];
	// Draw Object.
	glBegin(GL_QUADS);
	for(int i = 1; i < alphaSize; ++i) {
		drawBlur(xOffset * i, 0.0f, &accumulatedAlpha, alpha[i]);
		drawBlur(-1.0f * xOffset * i, 0.0f, &accumulatedAlpha, alpha[i]);
	}
	glEnd();

	F32 windowLeft = this->getPosition().mX - this->getSize().mX / 2;
	F32 windowTop = this->getPosition().mY - this->getSize().mY / 2;
	F32 windowRight = windowLeft + this->getSize().mX;
	F32 windowBottom = windowTop + this->getSize().mY;

	glDisable(GL_TEXTURE_2D);

	glBlendFunc(GL_ONE,GL_ONE);

	//This is required to clean up the alpha channel of the render buffer.
	glBegin(GL_QUADS);
	glColor4f(0.0f, 0.0f, 0.0f, 1.0);
	glVertex2f(windowLeft, windowTop);
	glVertex2f(windowRight, windowTop);
	glVertex2f(windowRight, windowBottom);
	glVertex2f(windowLeft, windowBottom);
	glEnd();

	glDisable(GL_SCISSOR_TEST);

	// Disable Texturing.
	glDisable(GL_BLEND);

	m_texture->setUpdateRect(updateRect);
	m_texture->update();

	glEnable(GL_TEXTURE_2D);

    glBindTexture(GL_TEXTURE_2D, m_texture->getTextureHandle().getGLName());
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

	glEnable(GL_SCISSOR_TEST);
	glScissor(updateRect.point.x, Platform::getWindowSize().y - updateRect.point.y - updateRect.extent.y, updateRect.extent.x, updateRect.extent.y);

	// Fetch Positions.
	accumulatedAlpha = alpha[0];

	glBegin(GL_QUADS);
	for(int i = 1; i < alphaSize; ++i) {
		drawBlur(0.0f, yOffset * i, &accumulatedAlpha, alpha[i]);
		drawBlur(0.0f, -1.0f * yOffset * i, &accumulatedAlpha, alpha[i]);
	}
	glEnd();

	delete [] alpha;

	glDisable(GL_SCISSOR_TEST);

	glDisable(GL_BLEND);
	glDisable(GL_TEXTURE_2D);

    // Call Parent.
    Parent::renderObject( viewPort, viewIntersection ); // Always use for Debug Support!
}

void t2dBlurEffect::drawBlur(F32 xOffset, F32 yOffset, F32 * destAlpha, F32 sourceAlpha) {
	F32 minX = m_texture->texCoordInfo()->point.x;
	F32 minY = m_texture->texCoordInfo()->point.y;
    F32 maxX = m_texture->texCoordInfo()->extent.x;
	F32 maxY = m_texture->texCoordInfo()->extent.y;

	F32 windowLeft = this->getPosition().mX - this->getSize().mX / 2;
	F32 windowTop = this->getPosition().mY - this->getSize().mY / 2;
	F32 windowRight = windowLeft + this->getSize().mX;
	F32 windowBottom = windowTop + this->getSize().mY;

	glColor4f(1.0f, 1.0f, 1.0f, sourceAlpha / (*destAlpha + sourceAlpha));
	glTexCoord2f( minX, minY );
	glVertex2f(windowLeft + xOffset, windowTop + yOffset);
	glTexCoord2f( maxX, minY );
	glVertex2f(windowRight + xOffset, windowTop + yOffset);
	glTexCoord2f( maxX, maxY );
	glVertex2f(windowRight + xOffset, windowBottom + yOffset);
	glTexCoord2f( minX, maxY );
	glVertex2f(windowLeft + xOffset, windowBottom + yOffset);
	*destAlpha += sourceAlpha;
}

IMPLEMENT_CONOBJECT(t2dBlurEffect);

This code is a patch that needs to be made to the gDynamicTexture.cc file in order to fix its texture coordinate calculations. Just copy it over the corresponding method.
void DynamicTexture::setUpdateRect( const RectI &newRect )
{
   mHasUpdateRect = true;

   // Check to see if this is being called as part of the constructor
   if( mTextureHandle == NULL )
   {
      GBitmap *bmp = new GBitmap( newRect.extent.x, newRect.extent.y, false, GBitmap::RGBA );
      mTextureHandle = new TextureHandle( NULL, bmp, BitmapKeepTexture, true );
      mSize = newRect.extent;
   }

   if( mTempDynamicTexture != NULL )
      mTempDynamicTexture->setUpdateRect( newRect );

   // Flip from upper-left point to lower-left point (for GL)
   RectI updateRect = newRect;
   updateRect.point.y = Platform::getWindowSize().y - updateRect.point.y - updateRect.extent.y;

   if( updateRect.extent == mUpdateRect.extent )
   {
      if( updateRect.point != mUpdateRect.point )
         mUpdateRect.point = updateRect.point;
   }
   else
   {
      TextureObject *to = mTextureHandle->object;
      mUpdateRect = updateRect;

      // If the new texture size can fit in the existing allocated size,
      // then we are golden, otherwise we have to re-create or we'll get
      // hosed.
      if( mTextureHandle->getDownloadedWidth() >= mUpdateRect.extent.x  &&
          mTextureHandle->getDownloadedHeight() >= mUpdateRect.extent.y )
      {

         // We don't have to resize the actual memory (yay)
         to->bitmapWidth = mUpdateRect.extent.x;
         to->bitmapHeight = mUpdateRect.extent.y;
      }
      else
      {
         // Damn, we gotta reallocate
         delete mTextureHandle;
         mTextureHandle = NULL;

         GBitmap *bmp = new GBitmap( updateRect.extent.x, updateRect.extent.y, false, GBitmap::RGBA );
         mTextureHandle = new TextureHandle( NULL, bmp, BitmapKeepTexture, true );
         mSize = updateRect.extent;
      }
   }

   // DON'T USE mUpdateRect here
   // Note that this looks a bit funny, this is because grabbing pixels results in
   // the rows getting flipped, to adjust for this, the texture coordinates must be flipped
   TextureObject *obj = (TextureObject *)mTextureHandle->object;
   F32 maxX  = F32(newRect.extent.x) / F32(obj->texWidth);
   F32 maxY = F32(newRect.extent.y) / F32(obj->texHeight);
   mTextureCoords = RectF( 0.0f, maxY, maxX, 0.0f );
}

You wont be able to create this scene object the level designer, but you'll be able to do it in torque script as follows:
new t2dBlurEffect() {
   Position = "37.500 -0.000";
   size = "25.000 75.000";
   BlurFactor = "2.0";
};

About the author

Recent Blogs


#1
04/10/2008 (9:28 am)
pretty sweet. do you have a larger screenshot ?
#2
04/11/2008 (3:36 pm)
The screenshot is getting resized by the website. I'll try posting it to my own website and link to it from here when I get a chance.
#3
04/18/2008 (1:23 pm)
Slick resource! I integrated the resource and created a blur area. I even gave it a "Follow Mouse" behavior, so I can move the blur area around the screen:

www.zombieshortbus.com/ggtutorials/blur.jpg
#4
04/21/2008 (7:29 pm)
Micheal,

I'm glad that you like it, and thanks for posting the screenshot.
#5
07/02/2009 (6:33 pm)
hello.

i know this resource is old and all that, but i'd like to point something out.

i've just tried it, and noticed someting:
- if you use the effect on the default zoom factor, or greater, you can notice the blur perfectly, BUT if you use a lower zoom factor (Say, 1.3), then you'll see how the screen is actually "replicated" a couple times, and in this replication is where you see the lbur, in the rest of the playable area, nothing.

now, if you still use a lower zoom factor, and try to cover the entire screen with it, you will get a NASTY White area in your game, and a reduced zone with the actual blur effect aactivated.

i really dont know why it happens, but i just wanted to point it out.

regardless of all i said above, great resource.
#6
09/02/2011 (11:24 am)
After compiling the project with visual studio, everything's fine, but when initiating my game prototype, the console.log says "unable to instantiate non-conobject", and the error is related to this part of the game.cs code:
new t2dBlurEffect() {  
   Position = "37.500 -0.000";  
   size = "25.000 75.000";  
   BlurFactor = "2.0";  
};

Maybe I'm not storing the t2dBlurEffect.h and t2dBlurEffect.cpp within the correct directory? (In the Visual Studio project, I included them in TGBGame-Source-T2D, and the Windows directory is C:Program Files (x86)-Torque-Torque Game Builder 1.7.5 Pro-engine-source-T2D)