dx postprocess
come from:http://www.cnblogs.com/mavaL/archive/2010/12/02/1894151.html
這個PostProcess例子還真是復雜,包含了很多我沒接觸過的盲點,只能一點一點的消化。不過令人興奮的是,這個例子包含很多有用的知識點。我覺得PostProcess----后期效果處理就是對RTT(Render to Texture)技術的進階(本例在各個源RT,目的RT之間反復處理)。對于本例來說,RTT和圖像處理PipeLine是重點(不知道我把一個后期效果的實現,比如bloom比作一次PipeLine行不行...),另外還涉及到其他一些知識,我認為還是非常有用的,如一些基礎圖像處理方法(blur,bloom,UpFilter,DownFilter等),注解的使用,字符串處理,控件處理,特效文件的封裝和管理。
?
其實我覺得,對每個例子,你能把它簡單的學學,看看主要實現方法也行。但是如果深究起來,想要完全的捋清程序的邏輯走向,就會發現細節真的很多,越看越復雜。我想這就是為什么看別人的代碼好像很簡單,自己實現起來,到處是問題的原因。
所以,我做下筆記,一是記錄下學到的知識點,以后實際應用時說不定想起了還能回來查閱下;
二是記下有些關鍵點或不理解的地方;
三是梳理出本例的主題的實現流程,比如一個bloom效果,到底經歷了哪些處理過程最終得以實現。
1.
在CPostProcess的Init中,闡釋了注解(Annotation)的實際應用:
假如一個材質系統什么的要加載上百個特效文件的話,而每個都有各自獨有的參數,像bloomscale什么的,我們不可能手動的為每個參數去GetParameterByName吧,這就可以像Init里面那樣利用注解了,把每個特效文件獨有的參數綁定到其technique的注解里面,然后在應用程序中我們通過操控注解就能掌握這些參數包括其默認值等等了。這樣Init函數就能抽象出來實現對任意特效文件的管理了。
?
2.
沒看懂的一個地方,CPostProcess中有個bool m_bWrite[4],從對它的賦值看出,應該表示的是當前特效文件的PS結果是否輸出到某個RT。但不是已經有了個m_nRenderTarget來表示輸出到哪里了嗎:
????? ?// Obtain the render target channel
??????? D3DXHANDLE hAnno;????????
??????? hAnno = m_pEffect->GetAnnotationByName( m_hTPostProcess, "nRenderTarget" );
??????? if( hAnno )
??????????? m_pEffect->GetInt( hAnno, &m_nRenderTarget );
而且我發現沒在程序中任何地方用到這個m_bWrite,這是什么意思呢?多此一舉?l
3.
在每個PostProcess特效文件中,PixelKernel數組是我們要在源采樣紋理中采樣的紋素與當前紋素的偏移量,比如在bloom vertical處理中,我們要采樣當前像素的上下各6個紋素顏色,就這樣:
static const int g_cKernelSize = 13;
float2 PixelKernel[g_cKernelSize] =
{
??? { 0, -6 },
??? { 0, -5 },
??? { 0, -4 },
??? { 0, -3 },
??? { 0, -2 },
??? { 0, -1 },
??? { 0,? 0 },
??? { 0,? 1 },
??? { 0,? 2 },
??? { 0,? 3 },
??? { 0,? 4 },
??? { 0,? 5 },
??? { 0,? 6 },
};
但我們知道,在紋理中采樣用的是紋理坐標,所以要做轉換,轉換后就放在特效文件的TexelKernel數組中。
為什么不直接在特效文件中計算TexelKernel呢,因為采樣紋理的大小是未知的,要在應用程序中給出,所以這里采用了在對象的OnReset方法中傳入采樣紋理的大小,再計算出TexelKernel傳入特效文件供其使用。
?
?
4.
scene.fx中,RenderNoLight是一個特別的technique,它用來將源RT的內容復制到目的RT。
本例中我們最終完成后期處理時,結果在一個texture里,我們通過往屏幕緩沖繪制一個全屏quad,并設定RenderNoLight來將最終圖像復制到屏幕上。
但本例文檔里提到,StrechRect也可以實現這個功能。于是我試了下,果然可以。
IDirect3DTexture9* pPrevTarget;
IDirect3DSurface9* pSrc, *pDest;
pPrevTarget = ( bPerformPostProcess ) ? g_RTChain[0].GetPrevTarget() : g_pSceneSave[0];
pPrevTarget->GetSurfaceLevel( 0, &pSrc );
pd3dDevice->GetRenderTarget( 0, &pDest );
?
pd3dDevice->StretchRect( pSrc, 0, pDest, 0, D3DTEXF_NONE );
?
不知道程序中沒這么用是為什么,而是用一個RenderNoLight的ps+DrawPrimitiveUP繪制一個全屏quad來完成的。
?
5.
在OnFrameRender中,當我們渲染完場景的顏色,法線,位置的紋理后:
?// Reset all render targets used besides RT 0
??? for( i = 1; i < g_nRtUsed; ++i )
??????? V( pd3dDevice->SetRenderTarget( i, NULL ) );????????
設備的RT1,RT2都被重設了,RT0卻沒被重設。我在這里就試了下把設備的原RT0還原了,結果運行畫面不正常。捋了捋邏輯,應該是這樣的:
在PerformSinglePostProcess的注釋中有:
//?????? This method changes render target without saving any. The caller
//?????? should ensure that the default render target is saved before calling
//?????? this.
也就是調用該函數執行RTT時,這個函數直接改變設備的RT但并不保存,他提醒調用者調用之前要注意保存設備的原始RT!也就是如果在上面我們就把設備的RT0還原了再調用PerformPostProcess的話,那么執行完PostProcess我們設備的幀緩沖也不知道跑哪兒去了。。。。。。所以這里不忙還原設備的原始RT0。
?
6.
在OnFrameRender和PostProcess處理中,都是往目的RT上繪制一個全屏大小的quad,令人不解的是,為什么要把每個頂點坐標減去0.5f呢,我開始以為是不是窗口的border占了點寬度,所以有個偏移量。。。。。。。。。想象力真太豐富了。
幸好google時無意看到了這篇文章,不然我還迷失在自己的誤解中:
http://www.cnitblog.com/wjk98550328/archive/2007/10/24/35258.html
上面這篇文章說,在有時候RTT的時候,我們會在目的RT上繪制一個全屏quad,利用rhw的頂點聲明我們直接指定4個頂點的屏幕空間坐標,但由于DX中《Directly Mapping Texels to Pixels》文檔闡述的,我們必須對每項頂點坐標減去0.5f.也就是頂點屏幕坐標的(0,0)不能準確對應頂點紋理坐標的(0,0),而(-0.5f,-0.5f)才能準確對應紋理坐標的(0,0).具體分析參見上述DX文檔。
?
7.
PerformSinglePostProcess中,對當前特效文件的每個pass:
如果當前Pass中含有fScaleX,fScaleY,則說明該pass要downfilter或upFilter.比如fScaleX,fScaleY為1/4,則目RT的大小是源RT的1/16。所以我們要修改在目的RT上渲染的quad的大小:
if( fScaleX != 1.0f )
{? //+0.5抵消我們在aQuad中預先做的-0.5的偏移量,再縮放,然后再減去-0.5實現Mapping Texels to Pixels
?? aQuad[1].x = ( aQuad[1].x + 0.5f ) * fScaleX - 0.5f;
? ?aQuad[3].x = ( aQuad[3].x + 0.5f ) * fScaleX - 0.5f;?? bUpdateVB = true;
}
if( fScaleY != 1.0f )
{
?? aQuad[2].y = ( aQuad[2].y + 0.5f ) * fScaleY - 0.5f;
?? aQuad[3].y = ( aQuad[3].y + 0.5f ) * fScaleY - 0.5f;
?? bUpdateVB = true;
}
然后在隨后的其他的PostProcess中,我們的目的RT的大小都設為這次Scale的結果,直到遇到下次fScaleX,fScaleY:
//fExtentX,fExtentY是PerformSinglePostProcess的參數,而且是引用,所以做了變動將會影響到以后PerformSinglePostProcess調用。
fExtentX *= fScaleX;??????????????
fExtentY *= fScaleY;
還沒完,我們這次的目的RT被縮放了,那么下次PostProcess時,我們要以這次的目的RT為源RT進行采樣,而下次的目的quad的紋理坐標還是對應這次的目的RT的整個texture的呢,我們得讓它對應這次縮放后的quad(確實很繞,不過要想搞清楚怎么設置每次目的quad的大小和采樣紋理坐標,必須強迫自己去捋清這些邏輯):
//
// If the extents has been modified, the texture coordinates
// in the quad need to be updated.
//
if( aQuad[1].tu != fExtentX )
{
?????aQuad[1].tu = aQuad[3].tu = fExtentX;
?????bUpdateVB = true;
?}
?if( aQuad[2].tv != fExtentY )
?{
??????aQuad[2].tv = aQuad[3].tv = fExtentY;??????//tu,tv是在源紋理中采樣的紋理坐標
??????bUpdateVB = true;
?}
?
8.
對于每個PostProcess特效文件,都包含了6個texture對象(那2個Velocity紋理對象沒看到設置和使用,就不管了,應該是擴展用的):
texture g_txSrcColor;
texture g_txSrcNormal;
texture g_txSrcPosition;
texture g_txSceneColor;
texture g_txSceneNormal;
texture g_txScenePosition;
?
這6個紋理對象是表示該PostProcess的源紋理采樣對象,前三個表示上一步PostProcess處理的結果texture,后三個表示在OnFrameRender中渲染的那三個texture。當本次PostProcess是序列中的第一次時,這兩套texture是相等的。
每次PostProcess,都設定頂點聲明為:pd3dDevice->SetVertexDeclaration( g_pVertDeclPP );
這個頂點聲明依次包含已轉換的頂點位置,源采樣紋理坐標(Texcoord for post-process source)和源場景紋理坐標(Texcoord for the original scene)。
所以每個PostProcess特效文件的PS的輸入參數都有兩套紋理坐標,第一套用來采樣上次PostProcess的結果texture,第二套用來采樣在OnFrameRender中渲染的三個texture中的任意一個。
?
9.
程序使用CRenderTargetChain來連接前一次和后一次的PostProcess。非常有技巧性,對于每條鏈來說,只需要2個texture,本次PostProcess結束后,翻轉該鏈中srcRT和destRT(本次的destRT作為下次的srcRT,本次的srcRT作為下次的destRT),然后進行下次PostProcess。
?
?
這讓人想到幀緩沖的SwapChain:一個FrontBuffer,一個BackBuffer,輪流顯示幀圖像并翻轉。
以后如果自己要實現PostProcess,可以借鑒這個做法。
?
10.
現在從大局上來梳理下一個PostProcess效果的實現歷程。就看Blur吧。它的后期處理特效序列是:
[Color]Down Filter 4x, [Color]Gaussian Blur Horizontal, [Color]Gaussian Blur Vertical,?[Color]Up Filter?4x
可以多來一次Gaussian Blur以使模糊效果更加明顯。
?
另外Blur效果只對場景的顏色信息進行處理,所以只涉及到color這一條CRenderTargetChain,即上圖中的第一條。
有些效果比如Edge Glow會同時涉及到顏色處理和法線處理,這時就要在2條鏈中操作,不過原理都差不多,沒什么大不了的。
?
第一步:在OnFrameRender中利用scene.fx往3個texture中分別輸出了場景的color,normal,position信息。
?
第二步:開始執行PostProcess序列的第一個:[Color]Down Filter 4x。因為這是首次往顏色鏈RTT,所以顏色鏈g_RTChain[0].m_bFirstRender為true。這種情況下我們設定該次處理的srcTexuture為第一步的color texture,然后設定destRT:? g_RTChain[0].GetNextTarget();
接下來把g_RTChain[0].m_bFirstRender為false。
然后就可以利用該次的特效文件進行繪制quad了。
最后,調用g_RTChain[0].Flip()來翻轉srcTexture和destRT為下次處理做準備。
?
第三步至第五步: 完成剩下的3次PostProcess,每步跟第二步基本相同,不同的是設定srcTexture為g_RTChain[0].GetNextSource()。
第五步完成后,我們最終的模糊效果圖像已經存在于顏色鏈的g_RTChain[0].GetPrevTarget()里了。
?
第六步: 在OnFrameRender中,我們可以通過StretchRect來將最終圖像復制到幀緩沖中顯示。
?????????????????????????????????????????????????? -------------------------完畢
?
11.
那十幾個PostProcess實現特效文件我覺得也很有必要看看是怎么實現的,以后自己要實現DOF,Blur,Bloom這些效果時可以借鑒它們的實現。
?
12.
對于有些后期效果,比如Blur來說,我們是通過先down filter4x使圖像變為原來的1/16,然后執行模糊處理,最后再up filter4x還原圖像的大小并顯示。
我發現如果不down filter4x和up filter4x,那么幀數會下降40乃至更多!DX文檔中關于這一點說得很清楚:
This can have a significant performance boost, because the pixel shaders have fewer pixels to process. A common postprocessing practice is to scale down the image, apply postprocess effects to the sub-area of the texture, then scale up the result image to the original size. The performance gain from having fewer pixels to process usually far exceeds the penalty of performing the extra steps of scaling.
轉載于:https://www.cnblogs.com/zengqh/archive/2012/08/29/2662100.html
總結
以上是生活随笔為你收集整理的dx postprocess的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 错误HIERARCHY_REQUEST_
- 下一篇: hdu 3577Fast Arrange