DirectX 8模板2





0/5 (0投票)
2001年10月19日
8分钟阅读

90385

841
DirectX框架的扩展,
引言
在本文中,我们将研究如何为 Directx 框架类添加一个类,该类允许我们将任意数量的背景添加到我们创建的场景中。这意味着我们将研究如何将纹理应用于对象以及如何定位纹理以使其正确显示。我还会简要介绍 Directx Eight 中使用的灵活顶点格式。
添加一个类。
为已提供的框架添加新类的原因是为了灵活性和可读性。正如您从代码中看到的,如果所有游戏代码都从 3dApp.cpp 文件运行,最终会得到一个庞大、几乎无法阅读的文件,并且很难跟踪所有正在发生的事情。因此,我们将一些功能封装在一个单独的类中,然后将代码移出,以便不再是这样的
HRESULT hResult; ... /// 40 lines deleted hResult = pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 ); if( FAILED( hResult ) ) { SetError( _T( "DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 )" ) ); SetLastResult( hResult ); return hResult; }
我们得到
m_bgBackGround.Render( m_pd3dDevice );
类的构成非常简单,并且编写方式模仿了 Directx 框架类的工作方式,尽管应注意这些类与框架类本身没有直接关系。由于该类模仿了框架类,因此它应该具有熟悉的函数。
virtual HRESULT OneTimeSceneInit(); /// called next to initialize app objects virtual HRESULT InitDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice, CString strTexture ); virtual HRESULT DeleteDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice ); /// render the scene virtual HRESULT Render( LPDIRECT3DDEVICE8 pd3dDevice ); /// move the frame virtual HRESULT FrameMove( LPDIRECT3DDEVICE8 pd3dDevice ); /// called when device dependent objects are about to be lost virtual HRESULT InvalidateDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice ); /// restore the surfaces virtual HRESULT RestoreDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice, D3DXVECTOR3 vec1, D3DXVECTOR3 vec2, D3DXVECTOR3 vec3, D3DXVECTOR3 vec4 ); virtual HRESULT FinalCleanup();
请注意,函数将 LPDIRECT3DDEVICE8
作为函数调用的参数传递。这是为了使被调用的代码与调用代码保持同步,并且两个版本都使用相同的设备来调用它们的绘图函数。不将 LPDIRECT3DDEVICE8
传递给它们的两个函数是 InitDeviceObjects
和 FinalCleanup
。这是因为 InitDeviceObjects
函数在 DirectX 完全初始化之前被调用,应该用于加载外部数据,而 FinalCleanup
函数用于释放 InitDeviceObjects
中加载的项目。
使用该类
类对象在 3DApp 类中声明为成员变量
CBackGround m_bgBackground;
然后将其用作 DirectX 框架类的任何正常部分。如果您查看 3DApp.cpp 文件,您可以看到它用于在代码中调用其自己的框架函数版本。CBackground
类中主要关注的函数是 RestoreDeviceObjects
函数,该函数在 D3DDevice 正在绘制的表面丢失时被调用。这意味着这段代码不是每帧运行一次,而是在需要时运行。它声明为
virtual HRESULT RestoreDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice,
D3DXVECTOR3 vec1,
D3DXVECTOR3 vec2,
D3DXVECTOR3 vec3,
D3DXVECTOR3 vec4 );
并且它被调用为
/// top left x,y,z m_bgBackGround.RestoreDeviceObjects( m_pd3dDevice, D3DXVECTOR3( 0.0f, /// start x position 0.0f, /// start y position 0.5f ),/// start z position /// top right x,y,z D3DXVECTOR3( d3dViewPort.Width, /// top x position or width 0.0f, /// still at y 0 0.5f ), /// bottom right x,y,z D3DXVECTOR3( d3dViewPort.Width, /// x position at the bottom d3dViewPort.Height, /// y position at the bottom 0.5f ), /// bottom left x,y,z D3DXVECTOR3( 0.0f, /// x at 0 d3dViewPort.Height, /// y at bottom 0.5f ) );
m_pd3dDevice
成员与四个向量一起传递给函数调用,这些向量以顺时针方向绘制屏幕坐标。第一个向量在 x 位置 0 和 y 位置 0 处传递,这是屏幕的左上角。然后,右上角位置作为 x 位置的视口宽度发送,y 位置再次为 0。等等绕屏幕一圈。z 位置保持恒定为 0.5。这意味着所有角都处于相同的深度,但没有任何东西可以阻止代表屏幕右下角的第三个向量设置为 -100.0f。事实上,如果图像只想出现在屏幕的左侧,只需在代码读取 d3dViewPort.width 的任何地方添加 /2。CBackGround::RestoreDeviceObjects
函数实现为
BACKGROUNDVERTEX cvVertices[] = { { vec1.x, vec1.y, vec1.z, 1.0f, 0.0f, 0.0f, }, { vec2.x, vec2.y, vec2.z, 1.0f, 1.0f, 0.0f, }, { vec3.x, vec3.y, vec3.z, 1.0f, 1.0f, 1.0f, }, { vec4.x, vec4.y, vec4.z, 1.0f, 0.0f, 1.0f }, }; m_dwSize = sizeof(cvVertices); /// Create the vertex buffer if( FAILED( pd3dDevice->CreateVertexBuffer( m_dwSize, 0, D3DFVF_BACKGROUNDVERTEX, D3DPOOL_MANAGED, &m_pvbVertexBuffer ) ) ) return E_FAIL; VOID* pVertices; if( FAILED( m_pvbVertexBuffer->Lock( 0, m_dwSize, (BYTE**)&pVertices, 0 ) ) ) return E_FAIL; memcpy( pVertices, cvVertices, m_dwSize ); m_pvbVertexBuffer->Unlock();
BACKGROUNDVERTEX
类型是灵活顶点缓冲区的一个例子,稍后将对此进行讨论。目前我们可以看到数组的每个部分包含六个浮点数,前三个浮点数是在函数调用期间传递的值。
数组中每个部分中的第四个浮点数是 rhw 值,它告诉 DirectX 管线该顶点的变换和光照由用户处理,并且它不需要对其执行任何操作。
最后两个参数是纹理坐标。
添加纹理
关于纹理可以涵盖的内容太多了,我只会谈论代码中发生的事情,而不是可能做到的事情。
背景纹理在 CBackGround::InitDeviceObjects 函数中加载,该函数从 CNew3dApp::InitDeviceObjects 函数调用,传递 LPDIRECT3DDEVICE8 指针和要加载的纹理文件的名称。
if( FAILED( D3DUtil_CreateTexture( pd3dDevice, strTexture.GetBuffer( 0 ), &m_ptBackGroundTexture ) ) ) return D3DAPPERR_MEDIANOTFOUND;
代码使用 D3DUtil_CreateTexture
函数加载传入的文件名并将其存储在 LPDIRECT3DTEXTURE8
m_ptBackGroundTexture 中。这在 CBackGround::Render
函数中被提及,当它传递给 SetTexture 函数时。
hResult = pd3dDevice->SetTexture( 0, m_ptBackGroundTexture );
这表示第一个纹理阶段将使用我们之前加载的纹理。大多数设备现在支持一次八个纹理,然后通过 SetTextureStageState 单独设置。
hResult = pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
此函数是使用 DirectX 进行纹理贴图的基础部分,所以现在就学会爱上它吧。该函数设置每个纹理的绘图要求,每个表面最多可容纳八个纹理,需要将第一个参数遍历 0-7 以获取每个纹理对象,您将经常看到此函数。在上面的第一个参数中,我们指示要应用参数的纹理。由于我们只使用一个纹理,因此应为零。D3DTSS_COLORARG1
参数指定这是纹理的第一个颜色参数,D3DTA_TEXTURE
指定代码应使用纹理中的颜色。对 SetTextureStageState
的第二次调用再次使用阶段零,并使用参数 D3DTSS_COLOROP
,它告诉 DirectX 对此纹理阶段执行某些颜色操作。D3DTOP_SELECTARG1
告诉 DirectX 它应该使用纹理而不对其进行任何修改。基本上,对 SetTextureStageState
的两次调用表示只需绘制纹理。
如果您查看代码的纹理格式化部分
0.0f, 0.0f /// top left 1.0f, 0.0f /// top right 1.0f, 1.0f /// bottom right 0.0f, 1.0f /// bottom left
您可以看到这里的格式遵循与上面绘制顶点相同的顺时针格式。当然,这是为了绘制到直接面向屏幕的平面。绘制事物的方式不同可能会更复杂。一种简单的给对象添加纹理的例子是将 1.0 值更改为 5.0。这将以水平和垂直方向绘制图像的 5 倍副本。
但是,如果您想在屏幕上绘制两个不同的图像,您需要使用 CBackground
类的两个版本,每个版本加载不同的图像文件,然后通过更改向量的 x 位置将它们绘制到屏幕上。左侧向量的 x 位置将是
0.0f /// top left, d3dViewPort.Width/2 /// top right, d3dViewPort.Width/2 /// bottom right, 0.0f /// bottom left
右侧将是
d3dViewPort.Width/2 /// top left, d3dViewPort.Width /// top right, d3dViewPort.Width /// bottom right, d3dViewPort.Width/2 /// bottom left
灵活顶点格式
首次提及灵活顶点格式时,它听起来和看起来都很吓人,主要是因为它通常需要几次尝试才能找到有关它是什么的信息,所以我将在这里尝试解释它,
取一个结构
struct FVF { float x; float y; float z; float rhw; float tu; float tv; };
这是我们在上面 BACKGROUNDVERTEX
数组中使用的数据,6 个浮点数,前三个用于指定对象的位置,一个用于告诉 DirectX 管线我们希望顶点按原样绘制并指定浮点坐标。然后说我们想要改变主意的能力。现在我们想添加一个镜面颜色和另一个纹理。现在我们的结构看起来像
struct FVF { float x; float y; float z; float rhw; DWORD dwSpec; float tu; float tv; float tu2; float tv2; };
现在我们遇到了一个问题,如果我们想重写我们的应用程序,以便它在某些时候使用这两种结构类型,它就会崩溃,除非我们区分这两种类型。这将导致我们定义一个类型来传递给 DirectX 管线。
#define STRUCT_TYPE_1 #define STRUCT_TYPE_2
我们可以只使用这两种结构类型,一切都会正常,直到我们决定想使用镜面颜色但不使用第二种纹理类型。这意味着我们必须再定义两个不同的结构和两个不同的结构类型,然后我们记得 DirectX 允许一次最多八个纹理,这意味着再定义六个结构和结构类型。必须有一种更简单的方法来告诉 DirectX 管线我们希望它绘制什么,而微软的做法是指定一个固定的数据结构。我这样称呼它,因为它在任何地方都没有被定义为结构,而是作为一种将数据传递给 DirectX 管线的方式规则集。如果它被定义为结构,它看起来会是这样(摘自 DirectX Eight 帮助文件中的“关于顶点格式”图表)
struct VERTEXDEMOSTRUCT { /// position float x; float y; float z; /// rhw float rhw; /// Blending Weight data float b1; float b2; float b3; /// Vertex Normal float normalx; float normaly; float normalz; /// Vertex Point Size float vpSize; /// Diffuse Colour DWORD dwDiffuse; /// Specular colour DWORD dwSpecular /// Text Cordinates 1-8 each containing 4 floats };
现在,当您将顶点格式传递给 DirectX 管线时,您不会使用所有可用参数,因此 Microsoft 使用 define 来告诉 DirectX 管线您正在使用数据的哪些部分,并且这些数据部分必须与上面相同的顺序定义,就像在 BACKGROUNDVERTEX
结构中一样,它在 Background.cpp 文件中定义为
/// Our custom FVF, which describes our custom vertex structure #define D3DFVF_BACKGROUNDVERTEX (D3DFVF_XYZRHW|D3DFVF_TEX1)
然后将此 define 传递给
pd3dDevice->CreateVertexBuffer( m_dwSize, 0, D3DFVF_BACKGROUNDVERTEX,
D3DPOOL_MANAGED, &m_pvbVertexBuffer )
这会在分配的顶点缓冲区之前创建大小为定义的 BACKGROUNDVERTEX
的顶点缓冲区,然后将定义的数组复制到已分配的顶点缓冲区。这会得到一个内存块,其中包含我们要绘制到屏幕上的信息,我们将在 CBackground::Render
函数中这样做。
首先我们调用
hResult = pd3dDevice->SetStreamSource( 0, m_pvbVertexBuffer, sizeof( BACKGROUNDVERTEX ) );
这会告诉 DirectX 管线我们要绘制的内存块以及该块的大小。然后我们调用
hResult = pd3dDevice->SetVertexShader( D3DFVF_BACKGROUNDVERTEX );
这会告诉 DirectX 管线流或数据内存块的定义。一旦 DirectX 管线拥有了它需要的所有信息,我们就可以告诉 DirectX 将流绘制到屏幕上。
示例项目主要在 Devstudio 7 中开发,但包含 Devstudio 6 的项目文件。由于 Devstudio 7 尚未发布正式版,并且并非所有人都将立即切换,因此从现在起,所有项目将尽可能以两种格式发布。