渲染到紋理(Render To Texture, RTT)詳解
RTT是現在很多特效裏面都會用到的一項很基本的技術,實現起來很簡單,也很重要。但是讓人不解的是網上搜索了半天只找到很少的文章說這個事兒,不知道是因爲太簡單還是因爲這項技術已經出現很長時間了。總之我是在摸索這個東西的時候繞了不少彎子。現在把具體的實現方法寫下來。
什麼是紋理
熟悉DX的兄弟們都知道什麼叫紋理了,這裏簡單介紹一下,先看看現實生活中的例子吧,其實紋理的例子比比皆是,比如地板,牆面都是紋理。在圖形學中,紋理主要是爲了增強場景的真實感,如果你想繪製一個地面,簡單一點可以直接使用一個矩形,稍微複雜一點可以用三角形網格,再複雜一點可以使用地面紋理,有了紋理以後真實感明顯增強了。DX中的紋理映射其實就是對現實生活中紋理的模擬,D3D中有專門的數據結構來管理紋理。
渲染到紋理
常規的渲染操作都是直接將場景呈現到backbuffer中的,backbuffer說白了其實就是一個表面,再說白了就是一塊內存,場景通過繪製函數載入顯存後,再通過Present函數送至顯示器。那麼爲什麼還要渲染到紋理呢?這是爲了實現一些特殊的效果,比如常見的環境映射,簡單的說,想象你有一個光滑的球體,它應該是可以反射周圍環境的,這就是環境映射。
實現步驟
上面說了常規的渲染操作是將場景送至backbuffer,而backbuffer實際上是一個Surface,而紋理恰恰又包含了Surface,所以我們只需要取得紋理的Surface,其次將場景送至這個Surface,最後再把這個紋理渲染到backbuffer中即可。舉個例子,假設你要在一面牆壁上畫一幅畫,你有兩種方法
1 直接在牆上畫,這個很好理解,就對應常規的backbuffer渲染。
2 先將畫繪製在紙上,然後將紙貼到牆上,這就對應渲染到紋理的過程。
這裏牆壁相當於backbuffer,而紙張相當於紋理的Surface,在紙上作畫相當於渲染到紋理,把紙貼到牆上相當於把紋理渲染到backbuffer,希望大家沒有迷糊就好。具體的步驟如下
1 創建紋理並獲得紋理的表面(Surface)
2 向紋理的表面渲染場景
3 渲染紋理本身
代碼
1. 聲明變量
LPDIRECT3DTEXTURE9 pRenderTexture = NULL; // 目標紋理
PDIRECT3DSURFACE9 pRenderSurface = NULL,pBackBuffer = NULL, pTempSurface;
// pRenderSurface是pRenderTexture 對應的Surface
// pBackBuffer用於保存原來的Render Target
2.創建一個紋理作爲渲染目標(Render Target)
//注意這裏的第三個參數必須爲D3DUSAGE_RENDERTARGET
//第四個參數決定紋理的格式,不同的場景會要求不同的格式
pd3dDevice->CreateTexture( TEX_WIDTH,TEX_HEIGHT,1,D3DUSAGE_RENDERTARGET,
D3DFMT_R5G6B5,D3DPOOL_DEFAULT,&pRenderTexture,NULL);
//獲得pRenderTexture對應的Surface
pRenderTexture->GetSurfaceLevel(0,&pRenderSurface);
3.渲染場景
//這裏保存下原來的Render target,在做完RTT後再恢復
pd3dDevice->GetRenderTarget(0,&pBackBuffer);
if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
//設置我們的紋理爲render target
pd3dDevice->SetRenderTarget(0, pRenderSurface);
pd3dDevice->Clear( 0, NULL,
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DXCOLOR(0.0f,0.00f,0.00f,1.00f), 1.0f, 0);
//重新將render target設置爲幀緩存
pd3dDevice->SetRenderTarget(0, pBackBuffer);
pd3dDevice->EndScene();
pBackBuffer->Release();
}
4. 善後
SAFE_RELEASE(pRenderSurface);
SAFE_RELEASE(pRenderTexture);
這裏需要注意的幾點:
渲染的時候要選擇正確的紋理格式。如果你要在紋理裏面保存高精度浮點數的話。通常所用的A8R8G8B8格式每一個顏色分量只有8位,只能表示0-255。詳情可以參考DirectX SDK Help中關於D3DFORMAT的說明。如果你的紋理長寬比和幀緩存的不同的話,那麼你需要在切換RenderTarget以後重新設置投影矩陣。否則渲染出來的圖像會被拉伸。
紋理的大小不能太大。經過試驗發現是在窗口模式下面窗口和紋理的大小不能超過屏幕減去任務欄的大小。如果超過的話似乎對紋理的任何操作都不會有效果。(可能是深度緩存不夠大,參考注意事項4)
如果想要驗證自己渲染出來的紋理是否正確,可以用D3DXSaveTextureToFile把紋理保存爲圖像。如果想要直接訪問紋理中的值則要麻煩一些。按照SDK文檔裏面的說法,作爲RenderTarget的紋理是保存在顯存上面的,無法lock與unlock。要向訪問其中的值需要做如下操作:
LPDIRECT3DTEXTURE9 text;
LPDIRECT3DSURFACE9 surf;
D3DLOCKED_RECT lockbits;
pd3dDevice->CreateTexture(TEX_WIDTH,TEX_HEIGHT,1,0,
D3DFMT_R5G6B5, D3DPOOL_SYSTEMMEM,
&text, NULL);
text->GetSurfaceLevel(0,&surf);
if (pd3dDevice->GetRenderTargetData(pRenderSurface, surf) == D3D_OK)
if (surf->LockRect(&lockbits, NULL, D3DLOCK_READONLY) == D3D_OK)
{
pRenderSurface->UnlockRect();
float* bits=(float*)(lockbits.pBits);
// SAVE BITS TO TEXT FILE
FILE* ofile = fopen("output.txt", "w");
for (int i=0; i<64; i++)
{
for (int j=0; j<64; j++)
fprintf(ofile, "(%2.2f,%2.2f,%2.2f) ", bits[i*64*4+j*4], bits[i*64*4+j*4+1], bits[i*64*4+j*4+2]);
fprintf(ofile, "\n");
}
fclose(ofile);
}
text->Release();
surf->Release();
這個技術可以用來在多通道渲染中傳遞渲染結果。比如可以把RTT出來的結果用來作爲第二編渲染中的紋理來使用,這樣可以實現水面反射等效果。另外在通用計算中可以用來保存數據。例如可以把GPU數值計算以後的結果保存在紋理中,再用上面所說的方法把數字讀出來(如果真要這麼做的話別忘了把紋理格式設置爲足夠大精度的格式,比如說A32B32G32R32F)。
注意事項
1. 設置一個RenderTarget會導致viewport變成跟RenderTarget一樣大
2. 反鋸齒類型必須跟DepthStencilBuffer一樣
3. RenderTarget的類型必須跟DepthStencilBuffer的類型兼容, 可以用IDirect3D9::CheckDepthStencilMatch進行檢測
4. DepthStencilBuffer的大小必須>=RenderTarget的大小
5. IDirect3DDevice9::SetRenderTarget的第0個不能爲NULL
6. Usage爲D3DUSAGE_RENDERTARGET的Texture不能進行反鋸齒, 而且Pool必須爲D3DPOOL_DEFAULT. 如果想利用RenderTarget做爲紋理又想反鋸齒, 可以先把場景渲染到一個CreateRenderTarget創建的Surface(或BackBuffer)上, 再用IDirect3DDevice9::StretchRect拷貝到紋理上
7. D3DX提供了一個ID3DXRenderToSurface, 簡化了RenderTarget的使用. 注意它的BeginScene跟EndScene與再用IDirect3DDevice9的同名函數對不能嵌套, 因爲實際上內部還是調用的IDirect3DDevice9的, 用PIX可以看到它進行了哪些調用. 還有就是這個接口仍然不能反鋸齒, 而且每次都要保存/恢復一堆狀態, 總覺得不爽
8. RTT不能既做爲輸入就做爲輸出目標, 某些顯卡上可能只會給一個warning, 有些顯卡上則會發生報錯/黑屏/死機之類不可預計的事情...另外, Depth stencil texture(參見Hareware shadow map)也有同樣的問題, 用完之後要SetTexture(n, NULL)清空, 不然A卡會黑屏/花屏/深度錯誤, 既使你沒有使用, 只要它被寄存器引用了, 顯卡還是會當做是正在使用的, 這時就不能做爲depth stencil buffer
9. RTT如果想保存到文件中, 是不能直接SaveToTexture的. 需要創建一個OffscreenSurface, 拷貝過去, 再保存. 不過N卡好像不支持DXT1格式的OffscreenSurface, 可以創建Texture, 取其level0的surface代替.
10. N卡在開啓了鋸齒後冒似所有的RTT都要反鋸齒, 不然深度測試會失敗-_-
11. Intel的顯卡在RTT沒有設置DepthBuffer時可能所有繪製全部深度測試失敗, 需要關閉深度測試再畫.
12. SRGBWRITE不支持Float格式的RT
13. MRT時使用第一個RT的alpha來做alpha test
14. MRT不支持反鋸齒, 必須相同bitdepth, 可以不同格式, 必須相同大小
http://www.cnblogs.com/lancidie/archive/2011/03/16/1985992.html