什么是纹理
熟悉DX的兄弟们都知道什么叫纹理了,这里简单介绍一下,先看看现实生活中的例子吧,其实纹理的例子比比皆是,比如地板,墙面都是纹理。在图形学中,纹理主要是为了增强场景的真实感,如果你想绘制一个地面,简单一点可以直接使用一个矩形,稍微复杂一点可以用三角形网格,再复杂一点可以使用地面纹理,有了纹理以后真实感明显增强了。DX中的纹理映射其实就是对现实生活中纹理的模拟,D3D中有专门的数据结构来管理纹理。
渲染到纹理
常规的渲染操作都是直接将场景呈现到backbuffer中的,backbuffer说白了其实就是一个表面,再说白了就是一块内存,场景通过绘制函数载入显存后,再通过Present函数送至显示器。那么为什么还要渲染到纹理呢?这是为了实现一些特殊的效果,比如常见的环境映射,简单的说,想象你有一个光滑的球体,它应该是可以反射周围环境的,这就是环境映射。
实现步骤
上面说了常规的渲染操作是将场景送至backbuffer,而backbuffer实际上是一个Surface,而纹理恰恰又包含了Surface,所以我们只需要取得纹理的Surface,其次将场景送至这个Surface,最后再把这个纹理渲染到backbuffer中即可。举个例子,假设你要在一面墙壁上画一幅画,你有两种方法
1 直接在墙上画,这个很好理解,就对应常规的backbuffer渲染。
2 先将画绘制在纸上,然后将纸贴到墙上,这就对应渲染到纹理的过程。
这里墙壁相当于backbuffer,而纸张相当于纹理的Surface,在纸上作画相当于渲染到纹理,把纸贴到墙上相当于把纹理渲染到backbuffer,希望大家没有迷糊就好。具体的步骤如下
1 创建纹理并获得纹理的表面(Surface)
2 向纹理的表面渲染场景
3 渲染纹理本身
创建纹理并获取纹理表面
为了在纹理上渲染,我们应该先准备一个纹理,使用DX的函数CreateTexture就可以创建一个纹理了,注意在设置参数的时候需要将usage设置为D3DUSAGE_RENDERTARGET,因为只有这样才能在纹理上渲染。
IDirect3DTexture9* g_pRenderTexture = NULL ;
// Create texture
HRESULT hr = g_pd3dDevice->CreateTexture(
256,
256,
1,
D3DUSAGE_RENDERTARGET,
D3DFMT_R5G6B5,
D3DPOOL_DEFAULT,
&g_pRenderTexture,
NULL) ;
if (FAILED(hr))
{
MessageBox(NULL, "Create texture failed!", "Error", 0) ;
return E_FAIL ;
}
下面我们要取得纹理对应的Surface,以便在其上绘制场景,
IDirect3DSurface9* g_pRenderSurface = NULL ;
//
Get texture surface
hr
=
g_pRenderTexture
->
GetSurfaceLevel(
0
,
&
g_pRenderSurface) ;
if
(FAILED(hr))
{
MessageBox(NULL,
"
Get surface on texture failed!
"
,
"
Error
"
,
0
) ;
return
E_FAIL ;
}
将场景渲染至纹理表面
下面开始向纹理的Surface中绘制场景,为了简单起见,这里绘制一个茶壶,需要注意的是,绘制时需要将纹理的表面设置为当前的RenderTarget,所以首先要保存原来的RenderTarget。
//
Save old RenderTarget
g_pd3dDevice
->
GetRenderTarget(
0
,
&
g_pOldRenderTarget) ;
//
Set texture surface as RenderTarget
g_pd3dDevice
->
SetRenderTarget(
0
, g_pRenderSurface) ;
g_pd3dDevice
->
Clear(
0
, NULL, D3DCLEAR_TARGET
|
D3DCLEAR_ZBUFFER,
0xff0000ff
,
1.0f
,
0
);
if
( SUCCEEDED( g_pd3dDevice
->
BeginScene() ) )
{
g_pd3dDevice
->
SetTexture(
0
, NULL) ;
g_pd3dDevice
->
SetRenderState( D3DRS_LIGHTING , FALSE );
g_pd3dDevice
->
SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME) ;
g_pTeapotMesh
->
DrawSubset(
0
) ;
//
Draw teapot
g_pd3dDevice
->
EndScene();
}
渲染纹理本身
接下来将纹理渲染到backbuffer,在这之前要恢复原来的RenderTarget,因为这次是将纹理送到backbuffer,而backbuff而就是原来的RenderTarget。
// Restore RenderTarget
g_pd3dDevice
->
SetRenderTarget(
0
, g_pOldRenderTarget) ;
g_pd3dDevice
->
Clear(
0
, NULL, D3DCLEAR_TARGET
|
D3DCLEAR_ZBUFFER,
0xff00ff00
,
1.0f
,
0
);
if
( SUCCEEDED( g_pd3dDevice
->
BeginScene() ) )
{
g_pd3dDevice
->
SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID) ;
//
Draw texture
RenderQuad() ;
g_pd3dDevice
->
EndScene();
}
g_pd3dDevice
->
Present( NULL, NULL, NULL, NULL );
虽然是两次渲染,但是只需要调用一次present函数,因为之前的绘制只是将场景送至显存,而Present函数才真正将场景显示出来。
上面的代码用到了自定义函数RenderQuad,这个函数将纹理渲染出来,细节如下
void
RenderQuad()
{
//
Setup texture
g_pd3dDevice
->
SetTexture(
0
, g_pRenderTexture) ;
g_pd3dDevice
->
SetSamplerState(
0
, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
g_pd3dDevice
->
SetSamplerState(
0
, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_pd3dDevice
->
SetSamplerState(
0
, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
g_pd3dDevice
->
SetSamplerState(
0
, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP );
g_pd3dDevice
->
SetSamplerState(
0
, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP );
//
Set stream source
g_pd3dDevice
->
SetStreamSource(
0
, g_pVB,
0
,
sizeof
(Vertex) );
g_pd3dDevice
->
SetFVF(VertexFVF) ;
g_pd3dDevice
->
DrawPrimitive(D3DPT_TRIANGLESTRIP,
0
,
2
) ;
}
最后,看一下效果图
其实这个图并没有真正体现渲染到纹理的威力,虽然技术上是,但给人的感觉就好像直接渲染了一张图片一样,为了实现更加高级的效果,我们可以设置一个Cube,并给这个Cube加上纹理,然后在每个纹理的表面绘制场景,甚至可以是动态场景,效果如下。
其实这个Cube上的茶壶是动态旋转的,并且可以使用鼠标右键来旋转,滚轮来缩放,可以下载下面的文件来看一下动态效果。
动态效果文件
Happy coding!!!
== THE END ==