高斯模糊作为图形引擎后处理部分的典型代表,已经是初级图形人员的基础课程了,通过高斯模糊,我们可以让渲染效果变得有朦胧感,其实shader的编写并不是特别复杂,但是涉及到渲染管线的后处理流程,实现起来还是有相当的难度,由于内容量较多,作者将分数讲进行讲解。
渲染管线的流程部分需要掌握熟练OpenGL/DirectX,Ogre把渲染API分别封装到了GLRenderSystem和D3DRenderSystem。
高斯模糊前效果图:
高斯模糊后:
可以看到Sinbad变得朦胧了很多,下面跟踪一下Ogre的设计思路以及实现流程,Ogre工程中对应此部分的是Compositor_Sample那个工程。
第一步:注册合成器(Compositor)逻辑(Logic)
CompositorManager::GetSingleton().RegisterCompositorLogic("GaussianBlur",new GaussianBlurLogic); RegisterCompositors();其中所用的GaussianBlurLogic内容如下:
class GaussianBlurLogic:public ListenerFactoryLogic { protected: virtual Tomo::CompositorInstance::Listener* CreateListener(Tomo::CompositorInstance* newInstance) ; };
Tomo::CompositorInstance::Listener* GaussianBlurLogic::CreateListener(Tomo::CompositorInstance* newInstance) { GaussianListener* gaussianListener = new GaussianListener; Tomo::Viewport* vp = newInstance->GetChain()->GetViewport(); gaussianListener->NotifyViewportSize(vp->GetActualWidth(),vp->GetActuralHeight()); return gaussianListener; }我们看到GaussianBlurListener创建了一个Listener,GuassianListener负责高斯模糊过程中的流程控制,生成以及传递必要的参数给渲染管线。
class GaussianListener:public Tomo::CompositorInstance::Listener{ protected: int m_VpWidth,m_VpHeight; float m_BloomTexWeights[15][4]; float m_BloomTexOffsetsHorz[15][4]; float m_BloomTexOffsetsVert[15][4]; public: GaussianListener(); virtual ~GaussianListener(); void NotifyViewportSize(int width,int height); virtual void NotifyMaterialSetup(unsigned int passId,Tomo::MaterialPtr& mat); };高斯模糊实际是根据高斯分布的数学原理进行的,我们需要混合当前像素前后左右的像素,高斯分布就是负责给相邻像素分配权重的。
void GaussianListener::NotifyViewportSize(int width,int height) { m_VpWidth = width; m_VpHeight = height; //central sample,no offsets m_BloomTexOffsetsHorz[0][0] = 0.f; m_BloomTexOffsetsHorz[0][1] = 0.f; m_BloomTexOffsetsVert[0][0] = 0.f; m_BloomTexOffsetsVert[0][1] = 0.f; // float scale = 3.f; float texelSize = 1.f/(float)((m_VpWidth>m_VpHeight)?m_VpHeight:m_VpWidth); m_BloomTexWeights[0][0]=m_BloomTexWeights[0][1] = m_BloomTexWeights[0][2] = Tomo::Math::GaussianDistribution(0,0,scale); m_BloomTexWeights[0][3] = 1.f; for(int i=1;i<8;++i){ m_BloomTexWeights[i][0]=m_BloomTexWeights[i][1] = m_BloomTexWeights[i][2] = Tomo::Math::GaussianDistribution((float)i,0.f,scale); m_BloomTexWeights[i][3] = 1.f; // m_BloomTexOffsetsHorz[i][0] = i*texelSize; m_BloomTexOffsetsHorz[i][1] = 0.f; m_BloomTexOffsetsVert[i][0] = 0.f; m_BloomTexOffsetsVert[i][1] = i*texelSize; } for(int i=8;i<15;++i){ m_BloomTexWeights[i][0]=m_BloomTexWeights[i][1] = m_BloomTexWeights[i][2] = m_BloomTexWeights[i-7][0]; m_BloomTexWeights[i][3] = 1.f; // m_BloomTexOffsetsHorz[i][0] = -m_BloomTexOffsetsHorz[i-7][0]; m_BloomTexOffsetsHorz[i][1] = 0.f; m_BloomTexOffsetsVert[i][0] = 0.f; m_BloomTexOffsetsVert[i][1] = -m_BloomTexOffsetsVert[i-7][1]; } }参数的计算根据当前的Viewport的尺寸进行计算,Viewport大小改变,参数也会随之改变,接下来注册合成器(Compositor)。
void CharacterApplication::RegisterCompositors() { const ResourceManager::ResourceMap& compositorMap = CompositorManager::GetSingleton().GetResourceMap(); for(ResourceManager::ResourceMap::const_iterator it = compositorMap.begin(); it != compositorMap.end(); ++it){ //CompositorPtr comp = *it; // CompositorManager::GetSingleton().AddCompositor(m_pActiveViewport,it->second->GetName()); if("Gaussian Blur" == it->second->GetName()) CompositorManager::GetSingleton().SetCompositorEnabled(m_pActiveViewport,it->second->GetName(),true); } }所有的Compositor将添加到CompositorManager进行统一管理,但是使用时需要CompositorManager::SetCompositorEnabled。
void CompositorManager::SetCompositorEnabled(Viewport* vp,const string& compositor,bool enabled) { CompositorChain* chain = GetCompositorChain(vp); for(size_t pos = 0;pos < chain->GetNumCompositors();++pos){ CompositorInstance* inst = chain->GetCompositor(pos); if(inst->GetCompositor()->GetName() == compositor){ chain->SetCompositorEnabled(pos,true); } } }到了这一步,我们已经使GaussianBlur对应的Compositor产生了作用。
class _TomoExport CompositorManager:public ResourceManager,public Singleton<CompositorManager> { public: CompositorManager(); virtual ~CompositorManager(); CompositorChain* GetCompositorChain(Viewport* vp); CompositorInstance* AddCompositor(Viewport* vp,const string& compositor,size_t addPosition = CompositorChain::LAST); void SetCompositorEnabled(Viewport* vp,const string& compositor,bool enabled); void RegisterCompositorLogic(const string& name,CompositorLogic* logic); CompositorLogic* GetCompositorLogic(const string& name); Renderable* GetTexturedRectangle2D(); protected: virtual Resource* CreateImpl(const string& name,const string& group,bool isManual = false, ManualResourceLoader* loader = 0,NameValueMap* params = 0); typedef map<string,CompositorLogic*> CompositorLogicMap; CompositorLogicMap m_mCompositorLogics; typedef map<Viewport*,CompositorChain*> CompositorChainMap; CompositorChainMap m_mChains; Rectangle2D* m_pRectangle2D; };如上面的CompositorManager类,我们可以看到CompositorLogic,CompositorChain的存储,Compositor其实是封装进了CompositorChain里面通过CompositorInstance的形式存储。
class _TomoExport CompositorChain:public RenderTargetListener,public Viewport::Listener { public: CompositorChain(Viewport* vp); virtual ~CompositorChain(); protected: Viewport* m_pViewport; typedef vector<CompositorInstance*> Instances; Instances m_vInstances; };CompositorInstance的结构如下,
class _TomoExport CompositorInstance{ public: CompositorInstance(CompositionTechnique* technique,CompositorChain* chain); virtual ~CompositorInstance(); protected: Compositor* m_pCompositor; CompositionTechnique* m_pTechnique; CompositorChain* m_pChain; };由于内容量庞大,CompositorManager的具体调度过程将在下一讲进行讲解。