RTT是现在很多特效里面都会用到的一项很基本的技术,实现起来很简单,也很重要。但是让人不解的是网上搜索了半天只找到很少的文章说这个事儿,不知道是因为太简单还是因为这项技术已经出现很长时间了。总之我是在摸索这个东西的时候绕了不少弯子。现在把具体的实现方法写下来。
渲染到纹理,顾名思义就是把渲染目标从帧缓存变成一个纹理。这样就可以把一个场景渲染后在进行Post Process,做出现在流行的各种特效。另外在利用GPU做通用计算的时候程序也是通过RTT和GPU交换数据的。
实现步骤:
1. 声明变量
LPDIRECT3DTEXTURE9 pRenderTexture = NULL; // 目标纹理
LPDIRECT3DSURFACE9 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以后重新设置投影矩阵。否则渲染出来的图像会被拉伸。
* 纹理的大小不能太大。经过试验发现是在窗口模式下面窗口和纹理的大小不能超过屏幕减去任务栏的大小。如果超过的话似乎对纹理的任何操作都不会有效果。
* 如果想要验证自己渲染出来的纹理是否正确,可以用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)。
DX9中,render target 可以同时向多少个渲染目标绘制?
由D3D_MAX_SIMULTANEOUS_RENDERTARGETS这个数值决定,一般是4个。
http://irrlicht.sourceforge.net/phpBB2/viewtopic.php?t=24255
As you maybe know it's not possible to create a rendertarget larger than the screen when using Direct3D9 driver, even though it seems to work fine with OpenGL.
The reason for this is the ZBuffer: one ZBuffer is used for all rendertargets.
The solution? Every rendertarget needs its own ZBuffer.