Basically what I've created is a post-processing shader that will intentionally pixelate and reduce the color palette of the screen. The reason you might want to do this is if you're interested in giving your game a retro look or some kind of transitioning effect (you can change how pixilated the screen is at will). Whether you're interested or not, it's still an interesting effect to see in action.
Here are some screenshots with the shader off and then on.
You can download the pre-built binary (for windows) and source code here: http://www.henrywrites.com/downloads/IrrPixelate.zip
Directions
* Use the arrow keys and mouse to move/look around.
* Press the spacebar to toggle the effect (it's on by default).
* Press the escape key to quit.
In case the link goes down (in the future or even temporarily) I'll post the code below.
pixelate.frag
Code: Select all
uniform sampler2D texture;
uniform float new_w, new_h, palette;
varying vec2 texCoord;
vec4 pixelate(sampler2D tex, vec2 uv)
{
vec2 coord = vec2( ceil(uv.x * new_w) / new_w,
ceil(uv.y * new_h) / new_h );
return texture2D(tex, coord);
}
vec4 reduce_palette(vec4 color, float max_colors_per_channel)
{
if(max_colors_per_channel < 0) {
return color;
}
return ceil(color * max_colors_per_channel) / max_colors_per_channel;
}
void main()
{
vec4 color = pixelate(texture, texCoord);
gl_FragColor = reduce_palette(color, palette);
}
Code: Select all
varying vec2 texCoord;
void main()
{
vec2 Position;
Position.xy = sign(gl_Vertex.xy);
gl_Position = vec4(Position.xy, 0.0, 1.0);
texCoord = Position.xy *.5 + .5;
}
Code: Select all
#ifndef __POST_PROCESS_EFFECT_PIXELATE__
#define __POST_PROCESS_EFFECT_PIXELATE__
#include <irrlicht.h>
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
class IPixelate_Callback : public video::IShaderConstantSetCallBack
{
public:
float height;
float width;
float palette;
virtual void OnSetConstants(video::IMaterialRendererServices* services, s32 userData)
{
int texture = 0;
services->setPixelShaderConstant("texture", (float*)(&texture), 1);
services->setPixelShaderConstant("new_h", reinterpret_cast<f32*>(&height), 1);
services->setPixelShaderConstant("new_w", reinterpret_cast<f32*>(&width), 1);
services->setPixelShaderConstant("palette", reinterpret_cast<f32*>(&palette), 1);
}
};
class IPostProcessPixelate : public scene::ISceneNode
{
public:
core::aabbox3d<f32> Box;
video::S3DVertex Vertices[6];//the vertices for the onscreenquad
video::SMaterial Material;
video::ITexture* rt0; //the rendertarget
int mat;
IPixelate_Callback* callback;
IPostProcessPixelate(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id): scene::ISceneNode(parent, mgr, id)
{
Vertices[0] = video::S3DVertex(-1.0f, -1.0f, 0.0f,1,1,0, video::SColor(255,0,255,255), 0.0f, 1.0f);
Vertices[1] = video::S3DVertex(-1.0f, 1.0f, 0.0f,1,1,0, video::SColor(255,0,255,255), 0.0f, 0.0f);
Vertices[2] = video::S3DVertex( 1.0f, 1.0f, 0.0f,1,1,0, video::SColor(255,0,255,255), 1.0f, 0.0f);
Vertices[3] = video::S3DVertex( 1.0f, -1.0f, 0.0f,1,1,0, video::SColor(255,0,255,255), 1.0f, 1.0f);
Vertices[4] = video::S3DVertex(-1.0f, -1.0f, 0.0f,1,1,0, video::SColor(255,0,255,255), 0.0f, 1.0f);
Vertices[5] = video::S3DVertex( 1.0f, 1.0f, 0.0f,1,1,0, video::SColor(255,0,255,255), 1.0f, 0.0f);
}
~IPostProcessPixelate()
{
// Material.MaterialType = video::EMT_SOLID;
// Material = video::SMaterial();
callback->drop();
}
void setPixelatedResolution(float width, float height)
{
if(callback)
{
callback->width = width;
callback->height = height;
}
}
void setReducedColorPalette(float palette)
{
if(callback)
{
callback->palette = palette;
}
}
void initiate(int sizeW, int sizeH, scene::ISceneManager* smgr)
{
video::IVideoDriver* driver = smgr->getVideoDriver();
video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
callback = new IPixelate_Callback();
callback->width = (f32)sizeW;
callback->height = (f32)sizeH;
Material.MaterialType = (E_MATERIAL_TYPE)gpu->addHighLevelShaderMaterialFromFiles
(
"media/pixelate.vert", "main", video::EVST_VS_1_1,
"media/pixelate.frag", "main", video::EPST_PS_1_1,
callback, (video::EMT_SOLID)
);
rt0 = driver->addRenderTargetTexture(dimension2d<u32>(sizeW,sizeH), "RTT1");
Material.Wireframe = false;
Material.Lighting = false;
Material.TextureLayer[0].Texture = rt0;
}
virtual void OnPreRender(){}
virtual void render()
{
u16 indices[] = {0,1,2,3,4,5};
video::IVideoDriver* driver = SceneManager->getVideoDriver();
SMaterial m = driver->getMaterial2D();
driver->setMaterial(Material);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
driver->drawIndexedTriangleList(&Vertices[0], 6, &indices[0], 2);
driver->setMaterial(m);
}
virtual u32 getMaterialCount(){return 1;}
virtual video::SMaterial& getMaterial(s32 i){return (Material);}
virtual const core::aabbox3d<f32>& getBoundingBox() const{return Box;}
};
#endif
Code: Select all
#include <irrlicht.h>
#include "IPostProcessPixelate.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
const int WINDOW_WIDTH = 1024;
const int WINDOW_HEIGHT = 768;
class MyEventReceiver : public IEventReceiver
{
public:
// This is the one method that we have to implement
virtual bool OnEvent(const SEvent& event)
{
// Remember whether each key is down or up
if (event.EventType == irr::EET_KEY_INPUT_EVENT)
KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
return false;
}
// This is used to check whether a key is being held down
virtual bool IsKeyDown(EKEY_CODE keyCode) const
{
return KeyIsDown[keyCode];
}
MyEventReceiver()
{
for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
KeyIsDown[i] = false;
}
private:
// We use this array to store the current state of each key
bool KeyIsDown[KEY_KEY_CODES_COUNT];
};
int main()
{
// create device.
MyEventReceiver receiver;
IrrlichtDevice *device = createDevice(video::EDT_OPENGL, core::dimension2d<u32>(WINDOW_WIDTH, WINDOW_HEIGHT), 32, false, false, false, &receiver);
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
device->getFileSystem()->addZipFileArchive("media/map-20kdm2.pk3");
scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp");
scene::IMeshSceneNode* q3node = 0;
if (q3levelmesh)
q3node = smgr->addOctreeSceneNode(q3levelmesh->getMesh(0), 0);
scene::ITriangleSelector* selector = 0;
if (q3node)
{
q3node->setPosition(core::vector3df(-1350,-130,-1400));
selector = smgr->createOctreeTriangleSelector(
q3node->getMesh(), q3node, 128);
q3node->setTriangleSelector(selector);
}
// Set a jump speed of 3 units per second, which gives a fairly realistic jump
// when used with the gravity of (0, -10, 0) in the collision response animator.
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS(0, 100.0f, .3f, 0, 0, 0, true, 3.f);
camera->setPosition(core::vector3df(50,50,-60));
camera->setTarget(core::vector3df(-70,30,-60));
if (selector)
{
scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
selector, camera, core::vector3df(30,50,30),
core::vector3df(0,-10,0), core::vector3df(0,30,0));
selector->drop(); // As soon as we're done with the selector, drop it.
camera->addAnimator(anim);
anim->drop(); // And likewise, drop the animator when we're done referring to it.
}
// First, let's get rid of the mouse cursor.
device->getCursorControl()->setVisible(false);
/* Add 3 animated hominids. */
scene::IAnimatedMeshSceneNode* node = 0;
// This X files uses skeletal animation, but without skinning.
node = smgr->addAnimatedMeshSceneNode(smgr->getMesh("media/dwarf.x"), 0);
node->setScale(core::vector3df(1.5, 1.5, 1.5));
node->setPosition(core::vector3df(-70,-66,-60));
node->setRotation(core::vector3df(0,-90,0));
node->setAnimationSpeed(20.f);
// Create our pixelation post processing effect.
IPostProcessPixelate* pixelate = new IPostProcessPixelate(0, smgr, 672);
pixelate->initiate(WINDOW_WIDTH, WINDOW_HEIGHT, smgr); // create a render target of size 1024x768.
pixelate->setPixelatedResolution(160, 120); // render the scene as if it were 160x120 pixels.
pixelate->setReducedColorPalette(16); // 16 colors per channel (16 red colors, 16 blue colors, 16 green colors) and any combonation of these.
bool usePixelate = true;
// FPS stuff.
int lastFPS = -1;
while(device->run())
{
if (device->isWindowActive())
{
driver->beginScene(true, true, 0);
// Check if we want to toggle our pixelation effect.
// We have to do a little hacking here, because irrlicht doesn't currently let you detect
// a single keypress (that is, we don't want to keep getting 'key presses' when the key
// is held down, but only on initial contact).
static bool isDown = false;
if(!receiver.IsKeyDown(irr::KEY_SPACE)) isDown = false;
if(receiver.IsKeyDown(irr::KEY_SPACE) && !isDown)
{
isDown = true;
usePixelate = !usePixelate;
if(usePixelate)
{
pixelate->setPixelatedResolution(160, 120);
pixelate->setReducedColorPalette(16);
}
else
{
pixelate->setPixelatedResolution(1024, 768);
pixelate->setReducedColorPalette(-1);
}
}
// Escape to quit.
if(receiver.IsKeyDown(irr::KEY_ESCAPE))
{
break;
}
// Capture the scene to our render target.
driver->setRenderTarget(pixelate->rt0, true, true, SColor(255,255,255,255));
smgr->drawAll();
// Render our capture and process scene.
driver->setRenderTarget(0);
pixelate->render();
// We're all done drawing, so end the scene.
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"IrrPixelate Demo - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;
str += " (press ESC to quit)";
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
}
device->drop();
return 0;
}