After hardcoding several Post Processing Effects for various things I started to find the common requirements between post process effects and set out to create an easy to use framework. I decided that I wanted the effects completely defined in the .fx files and settled on using annotations.
Brief Sample Effect
The below example defines a post processing effect that takes two input textures one from file and another from a previous render target. It outputs to two render targets and if the render targets don't exist it creates them as a 32bit ARGB texture.
technique main { pass P0 < int RenderTargetCount = 2; float2 RenderTargetSize = float2(1,1); string RenderTarget0 = "diffuse"; string RenderTarget1 = "normal"; int RenderTargetFMT0 = RT_U8ARGB; int RenderTargetFMT1 = RT_U8ARGB; int InputTextureCount = 2; int TextureType0 = TT_TEXTURE; int TextureType1 = TT_RENDERTARGET; string TextureName0 = "neon_metal.dds"; string TextureName1 = "position"; > { vertexShader = compile vs_3_0 PostProcVS(); pixelShader = compile ps_3_0 PostProcPS(); AlphaBlendEnable = false; AlphaTestEnable = false; ZEnable = false; ZWriteEnable = false; ColorWriteEnable = red | green | blue | alpha; } } Header File
For defining a Post Processing Effect in C++ I create a few layers. The first layer is an individual pass which stores the name of the needed render targets and a reference to any necessary textures. The second layer is the set of passes that get iterated over. When selecting textures the effect has three options: From File, Render Targets or Noise.
/* All content Copyright 2010 DigiPen (USA) Corporation, all rights reserved. ** ** File: Graphics/PostProcessEffect.h ** Author: Matthew Sorenson ** Email: msorenso@digipen.edu ** Brief: ** *******************************************************************************/ #ifndef GRAPHICS_POSTPROCESSINGEFFECT_H #define GRAPHICS_POSTPROCESSINGEFFECT_H #include <string> #include <vector> #include "../GUT/Device.h" #include "../GUT/Shader.h" #include "../GUT/Texture.h" #include "View.h" namespace Graphics{ class ComponentFactory; // Everything in the PostProcHelpers namespace are used internally by the // PostProcessEffect class namespace PostProcHelpers{ struct TargetDescripter { std::string name; GUT::RT_Format format; }; enum TextureType { TT_TEXTURE = 0, TT_RENDERTARGET = 1, TT_NOISE = 2 }; struct TextureDescriptor { std::string name; GUT::Texture::Ptr texture; TextureType type; }; typedef std::vector< TargetDescripter > TargetDescripterList; typedef std::vector< TextureDescriptor > TextureDescriptorList; struct Pass { float2 render_target_size; TargetDescripterList target_list; TextureDescriptorList texture_list; }; } class PostProcessEffect { public: PostProcessEffect(GUT::Device* device, const std::string& filename); ~PostProcessEffect(); void Render(View& view); private: typedef PostProcHelpers::Pass Pass; typedef std::vector< Pass > PassList; // These functions handle loading and parsing the annotations void LoadFile(const std::string& filename); GHandle GetAnnotation(GHandle handle, const std::string& name); void InitializeTargets( GHandle passh, Pass& pass); void InitializeTextures(GHandle passh, Pass& pass); // These functions are helpers for settings up individual passes for // rendering void BeginPass(Pass& pass, View& view, int pass_num); void EndPass(); void SetPassTargets( Pass& pass, int targetID, View& view); void SetPassTextures(Pass& pass, int textureID, View& view); GUT::Device* m_device; GUT::Shader::Ptr m_shader; GUT::Mesh* m_screen_quad; GUT::RenderTarget* m_current_targets[4]; GHandle m_tech; GHandle m_screen_size; PassList m_passes; }; } #endif //GRAPHICS_POSTPROCESSINGEFFECT_H Source File Most of the grunt work for running the effects is in the SetPassTargets and SetPassTextures functions which ensure that each pass has the resources it needs. Most of the loading takes place in the InitializeTextures and InitializeTargets which determine what each pass requires.
/* All content Copyright 2010 DigiPen (USA) Corporation, all rights reserved. ** ** File: Graphics/PostProcessEffect.cpp ** Author: Matthew Sorenson ** Email: msorenso@digipen.edu ** Brief: ** *******************************************************************************/ #include "PostProcessEffect.h" #include "../GUT/RenderTarget.h" #include "ComponentFactory.h" namespace Graphics{ PostProcessEffect::PostProcessEffect( GUT::Device* device, const std::string& filename) : m_device(device) { LOG_ASSERT_NOT_NULL(device); LoadFile(filename); m_screen_quad = GUT::CreateFullScreenQuad(m_device); for(int i = 0; i < 4; ++i) m_current_targets[i] = NULL; } PostProcessEffect::~PostProcessEffect() { SAFE_DELETE(m_screen_quad); } void PostProcessEffect::Render(View& view) { m_device->SetIndexBuffer(m_screen_quad->GetIndexBuffer()); m_device->SetVertexBuffer(m_screen_quad->GetVertexBuffer()); float2 resolution = float2(m_device->GetSettings().resolution); m_shader->SetFloat2(m_screen_size, resolution); m_shader->SetTechnique(m_shader->GetTechniqueByName("main")); m_shader->Begin(); for(int i = 0; i < (int)m_passes.size(); ++i){ BeginPass(m_passes[i], view, i); m_device->DrawIndexed(); EndPass(); } m_shader->End(); m_device->SetVertexBuffer(NULL); m_device->SetIndexBuffer(NULL); } void PostProcessEffect::BeginPass(Pass& pass, View& view, int pass_num) { for(int i = 0; i < (int)pass.target_list.size() && i < 4; ++i){ SetPassTargets(pass, i, view); } m_device->Clear(GUT::CLEAR_COLOR); for(int i = 0; i < (int)pass.texture_list.size(); ++i){ SetPassTextures(pass, i, view); } m_shader->BeginPass(pass_num); } void PostProcessEffect::EndPass() { m_shader->EndPass(); for(int i = 0; i < 4; ++i){ if(m_current_targets[i]){ m_current_targets[i]->End(); m_current_targets[i] = NULL; } } } void PostProcessEffect::SetPassTargets(Pass& pass,int targetID, View& view) { const std::string& name = pass.target_list[targetID].name; GUT::RT_Format format = pass.target_list[targetID].format; GUT::RenderTarget* rt = view.GetRenderTarget(name); if(NULL == rt){ rt = m_device->GetRenderTarget(pass.render_target_size, format); LogErrorIf(NULL == rt, "Failed to create Render Target %s", name.c_str()); view.AddRenderTarget(name, rt); } LogErrorIf(NULL == rt, "Render Target %s not fount", name.c_str()); m_current_targets[targetID] = rt; m_current_targets[targetID]->Begin(targetID); } void PostProcessEffect::SetPassTextures(Pass& pass, int targetID, View& view) { PostProcHelpers::TextureDescriptor texture = pass.texture_list[targetID]; char name[MAX_PATH]; sprintf_s(name, MAX_PATH, "tex%i", targetID); GHandle texHandle = m_shader->GetParameterByName(name); switch(texture.type){ case PostProcHelpers::TT_TEXTURE: case PostProcHelpers::TT_NOISE: m_shader->SetTexture(texHandle, texture.texture); break; case PostProcHelpers::TT_RENDERTARGET:{ GUT::RenderTarget* rt = view.GetRenderTarget(texture.name); LogErrorIf(!rt, "Could not find RenderTarget %s", texture.name.c_str()); m_shader->SetTexture(texHandle, rt); break; } default: LogError("Undefined Texture Type for Post Processing Effect %s", m_shader->GetFileName().c_str()); break; } } void PostProcessEffect::LoadFile(const std::string& filename) { m_shader = m_device->GetShader(filename); m_tech = m_shader->GetTechniqueByName("main"); m_screen_size = m_shader->GetParameterByName("screenSize"); LOG_ASSERT_NOT_NULL(m_tech); LOG_ASSERT_NOT_NULL(m_screen_size); int passCount = m_shader->GetPassCount(m_tech); for(int currPass = 0; currPass < passCount; ++currPass){ Pass pass; GHandle passh = m_shader->GetPass(m_tech, currPass); InitializeTargets(passh, pass); InitializeTextures(passh, pass); m_passes.push_back(pass); } } GHandle PostProcessEffect::GetAnnotation( GHandle handle, const std::string& name) { GHandle tmp = m_shader->GetAnnotationByName(handle, name.c_str()); LOG_ASSERT_NOT_NULL(tmp); return tmp; } void PostProcessEffect::InitializeTargets(GHandle passh, Pass& pass) { char tmp[MAX_PATH]; GHandle handle = m_shader->GetAnnotationByName(passh, "RenderTargetSize"); if(handle) m_shader->GetFloat2(handle, pass.render_target_size); else pass.render_target_size = float2(1, 1); int rtCount; m_shader->GetInt(GetAnnotation(passh, "RenderTargetCount"), rtCount); for(int i = 0; i < rtCount; ++i){ PostProcHelpers::TargetDescripter desc; sprintf(tmp, "RenderTarget%i", i); m_shader->GetString(GetAnnotation(passh, tmp), desc.name); int fmt; sprintf(tmp, "RenderTargetFMT%i", i); m_shader->GetInt(GetAnnotation(passh, tmp), fmt); desc.format = GUT::RT_Format(fmt); pass.target_list.push_back(desc); } } void PostProcessEffect::InitializeTextures(GHandle passh, Pass& pass) { char tmp[MAX_PATH]; int tCount; m_shader->GetInt(GetAnnotation(passh,"InputTextureCount"), tCount); for(int i = 0; i < tCount; ++i){ PostProcHelpers::TextureDescriptor desc; sprintf(tmp, "TextureName%i", i); m_shader->GetString(GetAnnotation(passh, tmp), desc.name); sprintf(tmp, "TextureType%i", i); int type; m_shader->GetInt(GetAnnotation(passh, tmp), type); desc.type = PostProcHelpers::TextureType(type); switch(desc.type){ case PostProcHelpers::TT_NOISE: desc.name = "noise.dds"; case PostProcHelpers::TT_TEXTURE: desc.texture = m_device->GetTexture(desc.name); break; default: break; } pass.texture_list.push_back(desc); } } } |