网页功能: 加入收藏 设为首页 网站搜索  
DirectX8中的二维图形高级技巧
发表日期:2006-08-20作者:[转贴] 出处:  

作者:Brand Gamblin

译者:炎龙工作室

[译序:精彩技术,不容错过!限于时间和能力,译文倘有讹误,当以英文原版为准。]

版本:v1.2

最后更新日期:2002-2-7


 

绪言

我们中的很多人使用DirectDraw已经有一段时间了,我们已经习惯性地去Lock一个Surface,然后在上面绘制我们所想要的东西,最后通过调用FilpBackSurface翻转到前面来。虽然我们已经书写了很多相关的代码,比如绘制Sprite,而且可以熟练地控制我们的Surface,但是好景不长,DirectX8改变了一切。如果你不想花时间去掌握它,而是指望利用DirectX8的向后兼容性而使用DX7中的东西时,是的,你不必改变什么,但是同时你也失去了DirectX8带来的好处:更快的BlitAlpha blending、缩放、还有很多……
 

Many of us have been programming in DirectDraw for a while. We are used to locking our surfaces, writing out our screens, and flipping our buffers. We all have code for how to blit a sprite, and how to handle the surfaces. However, DirectX8 changes all that. While you can still count on backwards compatibility with DX7, you will be short-changing yourself on the improvements gained by moving to 3D (blit speed, alpha blending, stretching, etc.) .

下面的例子,我把它叫做D2D,它将指导你怎样在3D的环境里延用你原来的2D编码技术,并且你可以通过书写类似原来的标准DirectDraw代码就可以得到上面所提到的诸多方面的提速。

This sample, called D2D, will show how you can still use 2D coding techniques in the 3D world, and how you can gain a speed increase over standard 2D DirectDraw code.

下面展示2个基本的步骤:绘制一个位图;直接存取Surface(如画线、画圆、或其他)。接下来,我将告诉你如何做到这一点。

There are really only two basic parts to 2D programming. Rendering a bitmap, and writing directly to a surface (for lines, circles, etc.) This tutorial will show how to do that.

新的对象

为了构造一个2D的环境,你需要创建几个对象(我这里使用C++,因为它可以很方便地封装对象):一个D3D的接口类,一个普通的多边形类(假定为一个矩形),一个对应此多边形的texture(译注:即纹理)类。

To render a 2D world, you only need a few object types (I using c++ here, because it is easier to show encapsulation). A D3D interface object, a simple polygon (assumed to be a rectangle), and a texture to apply to that polygon.

以下的这个例子将创建一个Window窗口,它只是简单地显示一张位图而已。虽然这并不算很酷,但它将是我们迈出的重要一步。

This sample will create a windowed program that simply displays a bitmap. The effect is not exactly stunning, but the process will be well worth the effort.

class D3DObj
{

public:

        D3DObj() : m_pD3D(NULL), m_pd3dDevice(NULL) { Clear(); }

        ~D3DObj() { Clear(); }

        void Init( HWND hWnd);

        void Clear();

        int Render();

 

        LPDIRECT3D8                           m_pD3D;

        LPDIRECT3DDEVICE8                     m_pd3dDevice;

}

在这个D3DObj类中有2个成员变量:LPDIRECT3D8LPDIRECT3DDEVICE8。在构造函数中,我们清空所有的成员变量(译注:通常,我们使用初始化列表来初始化类成员变量,这样的好处是:相比在构造函数中使用assignment来初始化成员变量,初始化列表要更有效率。详情请见《Effective C++》的条款12)。作为代替,我们利用另一个成员函数Init来进一步构造这2个成员变量,它需要传入一个窗口句柄来与之关联。
 

The D3D object has only two data members, a LPDIRECT3D8 and a LPDIRECT3DDEVICE8. While it is usually preferable to have a constructor provide the data initialization, this code will only clear out those data members. Instead, there is an Init function that provides the initialization of those data members, and receives the HWND of the rendering window.

void D3DObj::Clear()

{

        SAFE_RELEASE( m_pd3dDevice);

        SAFE_RELEASE( m_pD3D);

}

Clear函数清除对应的指针,同时设为NULL(译注:SAFE_RELEASE的原型为#define SAFE_RELEASE(p)      { if(p) { (p)->Release(); (p)=NULL; } } 这个宏在dxutil.h中定义)。

The Clear function just releases the pointers, and sets them to NULL.

void D3DObj::Init( HWND hWnd)

{

        // 第一步,创建主D3d对象

        m_pD3D = Direct3DCreate8( D3D_SDK_VERSION );

 

        // 第二步,创建渲染设备(用来适应当前屏幕的像素格式)

        D3DDISPLAYMODE d3ddm;

        m_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm );

 

        D3DPRESENT_PARAMETERS d3dpp;

        ZeroMemory( &d3dpp, sizeof(d3dpp) );

        d3dpp.Windowed   = TRUE;

        d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

        d3dpp.BackBufferFormat = d3ddm.Format;

        d3dpp.EnableAutoDepthStencil = TRUE;

        d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

 

        m_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,

                D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &m_pd3dDevice );

 

        // 第三步,设置渲染设备的属性

        m_pd3dDevice->SetRenderState ( D3DRS_CULLMODE, D3DCULL_NONE);

        m_pd3dDevice->SetRenderState ( D3DRS_LIGHTING, FALSE);

        m_pd3dDevice->SetRenderState ( D3DRS_ZENABLE, TRUE);

 

        // 设置Alpha Blending的顶点

        m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE,   TRUE );

        m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );

        m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

 

       m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_MODULATE);

       m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

       m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);

       m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,  D3DTOP_SELECTARG1);  

 

        m_pd3dDevice->SetVertexShader( D3DFVF_CUSTOMVERTEX );

}


这段代码非常简单,但很清楚。可能你已经发现,我没有检查这些函数的返回值,那是因为它只是用来示范如何创建一个D3D对象而已。

The code in the Init function is fairly straightforward. It should be noted that all of those functions have return values, and should be handled in proper debug fashion. The only reason it is not handled here is because this is sample code.

首先,我们需要创建主要的D3D对象,然后检查默认的显示模式(这个例子可以在16、24和32位色下正常地工作,但不支持8位的调色板模式)。利用默认的显示模式,Init函数将通过主窗口的句柄来创建一个设备(译注:即LPDIRECT3DDEVICE8)。接下来,我们设置一些属性,用来告诉渲染设备如何工作,这些属性在DX8的SDK中可查询到更加完整的文档说明,以及其他的一些使用指南。

First, the main D3D object is created, and then the default display mode is examined (this sample works well in 16, 24, and 32-bit color, but not in 8-bit palletized mode). Using the default display mode, the Init function will create a device that utilizes the HWND of the main window. Next, the code sets a series of flags that tell how to render. These flags are well documented in the DX8 SDK, and other flipcode tutorials.

可以选择是否打开Z Buffer(译注:即深度)属性,这样在绘制如tile这样(译注:如游戏地图与精灵的关系)场景的时候,通过z值,可以很清楚地表现出位图之间的层次关系(译注:如树应该挡住人,而人应该挡住草地……),当然这会有一定开销。或者你更喜欢自己通过一个list来实现这种效果,这完全视个人的喜好。

Notice that the Z buffer is enabled for this sample. It might seem to make more sense to disable the Z-buffer, however, when tiling bitmaps inside a program (for dialog boxes, target reticules, etc.) it is a great comfort to be able to set a z value, rather than resort the render list. This is, of course, purely a matter of personal preference.

因为在3D世界里,所有的渲染都是针对多边形的,接下来我们需要设置多边形的诸个顶点的值和颜色,这里不需要对它们进行映射或culling(所有的对象将直接对应整个屏幕)。我们会在texture上绘制我们想要的颜色,它不需要光照(虽然它是一个很酷的效果,如果运用得当的话)。打开AlphaBlending,接着设置texture的Alpha Blending相关属性,同样,也需要设置使用标记顶点的渐变属性。

Since this program will provide the vertex coordinates and colors on its own, there is no need for projection or culling (all objects will be facing the screen). Since the texture will be providing its own color data, there is no need for lighting (although this could be a cool special effect if applied properly). Alpha blending is enabled, and the texture is set to provide the alpha blending parameters. Also, vertex shader is set to recognize the vertex type that is used.

int D3DObj::Render()
{

      m_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,

                            D3DCOLOR_XRGB(0,0,255), 1.0f, 0L );

      m_pd3dDevice->BeginScene();

 

      // Rendering of scene objects happens here.

 

      m_pd3dDevice->SetTexture( 0, MyRect.m_Texture.pTexture );

    m_pd3dDevice->SetStreamSource( 0, MyRect.m_VertexBuffer, sizeof(CUSTOMVERTEX) );

      m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP , 0, 2 );

 

      // End the scene.

      m_pd3dDevice->EndScene();

 

        m_pd3dDevice->Present( NULL, NULL, NULL, NULL );

        return 0;

}

从Render函数开始,我们才真正开始实际的工作:渲染整个场景。它对于一个2D的程序员而言,等同于我们原先所熟悉的DirectDraw中的Flip函数。

This is the only other function in the D3Dobj definition. This is the function that actually does all the work; for a 2D programmer, this is the equivalent to the DirectDraw Flip function.

首先,清空所有的缓冲(注:虽然这只是个2D的程序,不过Z Buffer的效果依然存在),接着,我们将背景用纯蓝色填充,当前的texture和顶点缓冲指向一个MyRect对象(下面很快会讲到这个)。在做完所有的设置工作后,调用DrawPrimitive函数来绘制这个矩形,我们通过设置4个顶点来表示2个三角形(译注:3D环境下的渲染单位为三角形),用它们来表示出我们所想要的矩形。先用3个顶点定义出第1个三角形Triangle0,再用第4个顶点来定义出第2个三角形Triangle1,它与Triangle0有两个相同的顶点(如图)。

First, all buffers are cleared (note, even though this is a 2D program, the Z buffer is still cleared) and the screen is filled with blue. Then the current texture and vertex buffer are pointed at the MyRect object (we will get to that soon.). Once everything is set, DrawPrimitive is called to draw the rectangle. We draw a rectangle by drawing two triangles defined by four points. The first three points define Triangle0, the next point defines a triangle based on itself and the previous two points. (see graph).

在调用DrawPrimitive函数后,渲染工作还尚未开始,而接下来的EndScene和Present才是真正的完成了渲染工作。EndScene用来告诉Direct3D在区域中有多少个多边形需要渲染,而Present相当于DirectDraw中的Flip操作。它将绘制出指定的位图(定义在MyRect中)。在实际的程序中,通常我们会在一个循环中渲染出所有的多边形,方法是读取一个多边形的列表(我使用vector)。

After the primitive is cached (not really rendered yet), EndScene and Present are called, to really finish the frame rendering. EndScene tells Direct3D that there aren any more polygons to render, and Present is the 3D equivalent of DirectDraw Flip. It should be noted that this will only draw one bitmap (the one defined by MyRect). In any real application, this would be a loop, which read from a list (I use a vector) of polygons, setting the texture, setting the vertex buffer, and rendering the primitive for each of the polygons in the list.

多边形

在这个Poly类中,我们封装了1个顶点缓冲和1个指向texture的指针。在Poly类的构造函数中,初始化顶点缓冲(设置为全屏)。

The Poly object encapsulates a vertex buffer and a texture pointer. The Poly object initializes the vertex buffer, and sets it to arbitrary (full-screen) coordinates.

class Poly

{

public:

        Poly() : m_VertexBuffer(NULL) { Init(); }

        ~Poly() { Clear(); }

        void Clear();

        void Init();

        void SetVertRect(int x, int y, int w, int h, float d); // Sets the

rectangle used by the Verticies.



        LPDIRECT3DVERTEXBUFFER8        m_VertexBuffer;

        Texture                               m_Texture;

}



void Poly::Clear()

{

        SAFE_RELEASE(m_VertexBuffer);

}

Clear函数用来清除顶点缓冲,然后设置为NULL

The Clear function just wipes the vertex buffer, and sets it to NULL.

void Poly::Init ()

{

        // Create a 4-point vertex buffer

        if( FAILED( MyD3D.m_pd3dDevice->CreateVertexBuffer(

            4*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT,

            &m_VertexBuffer) ) )

               return ;



        CUSTOMVERTEX* pVertices;

        if( FAILED( m_VertexBuffer->Lock( 0, 4*sizeof(CUSTOMVERTEX),

          (BYTE**)&pVertices, 0 ) ) )

               return ;



        pVertices[0].position=D3DXVECTOR3( -1.0f,  1.0f, 0.5f );

        pVertices[0].color=0xffffffff;

        pVertices[0].tu=0.0;

        pVertices[0].tv=0.0;

        pVertices[1].position=D3DXVECTOR3( 1.0f,  1.0f, 0.5f );

        pVertices[1].color=0xffffffff;

        pVertices[1].tu=1.0;

        pVertices[1].tv=0.0;

        pVertices[2].position=D3DXVECTOR3( -1.0f,  -1.0f, 0.5f );

        pVertices[2].color=0xffffffff;

        pVertices[2].tu=0.0;

        pVertices[2].tv=1.0;

        pVertices[3].position=D3DXVECTOR3(  1.0f,  -1.0f, 0.5f );

        pVertices[3].color=0xffffffff;

        pVertices[3].tu=1.0;

        pVertices[3].tv=1.0;



        m_VertexBuffer->Unlock();

}

首先,Init函数创建一个拥有4个顶点的顶点缓冲,它是那么清晰明了(传入缓冲的大小、顶点的类型、缓冲的地址,最后CreateVertexBuffer函数会填写你的顶点缓冲然后返回它)。

First, the Init function creates a vertex buffer with four points. This is a fairly straightforward function (receives the size of the buffer, the type of vertex, the memory pool to grab it from, and the pointer used to return the buffer).

接下来,我们通过Lock来锁定创建的顶点缓冲,以此得到直接操作的权限。Lock函数返回一个指针,用来让我们往里面填写数据。

Next, the buffer is locked so that we have access to the actual data. The lock function returns the pointer to our data.

然后,正确地填写每一个顶点的值。需要注意的是,所有在屏幕上的坐标,都是基于-1.01.0之间的。很明显,第3个值是指的Z Buffer,它基于0.01.0之间。

After that, we set each vertex to the correct values. All position values are based on the assumption that the screen goes from -1.0 to 1.0 in both the X and Y direction, with the origin at the center of the screen. The third value is, of course, the Z value, and has valid values of 0.0 to 1.0.
 

Color的默认值设为白色(不透明),理由是:我们将从texture中读取颜色,而不是从多边形的某个角,接着设置不透明的颜色为0xff,但这并不影响texture中本身带有alpha通道。

The color defaults to white (with full opacity) the reason for this is that we will be reading all of our color data from the texture, not from the individual corners of the polygon. Note that although we set the opacity to 0xff, this does not preclude the texture map from using its own alpha mask.

TuTv2个值用来指定texture的范围,基于0.01.0之间。当然,聪明的你是不会漏掉这个的。

The TU and TV values are anchors into the texture map. These values are also valid from 0.0 to 1.0. Usually, you won ever want to mess with these.

现在所有顶点的值都设置好了,然后使用Unlock来释放对这个矩形的锁定,从而得到对应的多边形,为接下来的渲染作准备。

Once all the vertices are set up, you unlock the rectangle to get your polygon ready for rendering.

现在多边形也全部搞定了,如果你要改变这个多边形的顶点缓冲的值(毕竟不是所有的多边形都是全屏的),可以这么做:

Now that the polygon is all set up, suppose you want to alter the vertex locations (after all, not every polygon is going to be full-screen).

void Poly::SetVertRect(int x, int y, int w, int h, float d)

{

        float left, top, right, bottom;

        left    = ((float)x  (float)VIEWPORT_WIDTH) * 2.0f - 1.0f;

        right   = ((float)(x + w)  (float)VIEWPORT_WIDTH) * 2.0f - 1.0f;

        top     = ((float)y  (float)VIEWPORT_HEIGHT) * 2.0f - 1.0f;

        bottom  = ((float)(y + h)  (float)VIEWPORT_HEIGHT) * 2.0f - 1.0f;



        CUSTOMVERTEX* pVertices;

        m_VertexBuffer->Lock( 0, 4*sizeof(CUSTOMVERTEX), (BYTE**)&pVertices, 0);



        pVertices[0].position=D3DXVECTOR3( left,  bottom, d );

        pVertices[1].position=D3DXVECTOR3( right,  bottom, d );

        pVertices[2].position=D3DXVECTOR3( left,  top, d );

        pVertices[3].position=D3DXVECTOR3( right,  top, d );



        m_VertexBuffer->Unlock();

}

这个函数将根据传入的值(x,y,width,height)来生成新的一系列顶点,以改变这个矩形的范围。最后,它通过Lock住这个缓冲以写入改变的值。假如你想将一个sprite从屏幕的右边移动到左边的话,你可以在每一帧里调用SetVertRect(--x,y,width,height,depth)。这段代码根据传入的虚拟坐标值(基于系统的可视范围),来传换成 -1.0至1.0中的有效值。

This function translates a rectangle (x, y, width, height) into correct vertex locations, and alters the vertex buffer accordingly. If, for instance, you were slowly moving a sprite from the right side of the screen to the left you might call SetVertRect(--x, y, width, height, depth) once per frame. This code takes the assumed locations (based on a Viewport_width, Viewport_height coordinate system), and resizes them to the correct -1.0 to 1.0 value. After that, it locks the buffer and writes out the changes.

纹理

class Texture

{

public:

        Texture() { Clear(); }

        Texture(const char * Filename) { Init(Filename); }

        ~Texture() { Clear(); }

        void Init (const char * Filename);

        void Clear();



        LPDIRECT3DTEXTURE8     pTexture;

        int m_Width;

        int m_Height;

        int m_Pitch;

};

Texture对象中,包含了图像数据,并用一成员变量LPDIRECT3DTEXTURE保存它。

The Texture Object holds image-specific data, and the buffer to the actual texture.

void Texture::Init (const char * Filename)

{

        TGAFile file;

        file.read(Filename);

        m_Width=file.m_Width;

        m_Height=file.m_Height;

        m_Pitch=file.m_Pitch;



        MyD3D.m_pd3dDevice->CreateTexture( file.m_Width, file.m_Height, 0, 0,

        D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &pTexture );



        D3DLOCKED_RECT d3dlr;

        pTexture->LockRect( 0, &d3dlr, 0, 0 );

        DWORD * pDst = (DWORD *)d3dlr.pBits;

        int DPitch = d3dlr.Pitch4;

        DWORD * pSrc = (DWORD *)file.m_PixelData;

        int SPitch = file.m_Pitch4;



        for (int i=0; i<file.m_Height; ++i)

               for (int j=0; j<file.m_Width; ++j)

                       pDst[i*DPitch + j] = pSrc[i*SPitch + j];                       



        pTexture->UnlockRect (0);

}

上面的例子通过读取一个TGA图像文件的数据来填充texture。呵呵,读取TGA文件在任何时候都不是一件有趣的事情,真正有意思的部分是从调用CreateTexture函数开始。它需要传入Texture的宽、高、图像格式,以及一个指向成功调用后所开辟的缓冲指针:LPDIRECT3DTEXTURE8

In this sample, textures are filled with TGA file data. However, reading TGA files is not the interesting thing about this function. The interesting part starts with the CreateTexture function. This function receives as parameters a width, height, image format, and the type of memory used to allocate the texture. It returns a pointer to the newly created texture.

在成功地获得缓冲的指针后,同样通过Lock来锁定texture缓冲,就像在大多数DirectX 程序那样:锁定某个资源后,通过返回的指针填写数据,最后在所有的工作完成后Unlock资源。

Once the pointer is received, we can lock the rectangle that holds that texture. Again, we follow the standard DirectX pattern of locking a resource, filling the data it points us to, and unlocking the resource.

请注意下面这一段代码:

Consider this section:

        for (int i=0; i<file.m_Height; ++i)

               for (int j=0; j<file.m_Width; ++j)

                       pDst[i*DPitch + j] = pSrc[i*SPitch + j]; 

这里是整个程序的关键之所在。当你操作texture的指针时,可以像过去我们所熟悉的,就像是在操作DirectDraw Surface指针。这个时候,你可以做任何你想做的事情,比方说:绘制 Bresenham直线、画圆或者绘制一个位图(就像这个例子所展示的这样)。在上面的代码中,我们从一个源位图中读取数据,并拷贝到目标位图中去。同样,我们可以指定某种颜色,用来填充目标位图,就像这样:

This is the key to the whole program. While you have access to the texture pointer, you can treat the texture just like a DirectDraw Surface Pointer. You can use this pointer to draw Bresenham lines, circles, or bitmaps (as in this sample case). In this section of code, we copy from a source bitmap to a destination bitmap. However, we could just as easily fill with a specific color:

pDst[y*Dpitch + x] = ( alpha << 24 | red << 16 | green<< 8 | blue )

  另外有一点需要注意的是,上面这些函数中,我在使用其中的一些函数时,对于某些参数的具体意义没有去详细地解释它的意义,不过你可以通过查询DX8的SDK中的帮助文件,从而得到更多的信息。例如:在Lock和Unlock函数中,第1个参数是0, 这是因为每个texture(译注:在Mip-mapping的情况下纹理可能包含多个表面,所谓Mip-mapping是指为远处的物体存一个更小的版本的位图Mip-Mapping。在3D中,当物体很远,不需要细节的时候使用最小尺寸的位图。在2D中我们并不关心这个(Mip-Mapping),因此我们将是使用的纹理为单顶层表面,调用0层)都有8个"面",它意味着Lock的是第0个"面",而在2D渲染中,我们只需要使用第0个就可以了。而直到这之前,我都没有像刚才那样把每一件事都说明得很清楚,因为这已经不属于本文的范围,你应该去花时间来熟悉这些函数的使用方法。

One other point, I have glossed over several of the parameters in some of these functions, and it would be well worth the trouble to look them up in the DX8 SDK. For instance, in the Lock and Unlock functions, the first parameter is a 0, which indicates that you are locking the 0th stage identifier. Every surface has up to 8 surfaces, but for 2D rendering we only use the 0th. Since it was not needed for 2D rendering, I didn mention this, however, you may still want to read up on these functions when you are implementing them.

结束语

在这个例子中,我们见到了如何在Direct3D环境下构造我们高性能的2D程序。同样,我们也知道了在不放弃过去所掌握的2D知识的情况下,如何获得DirectX8所带来的好处:更快的BlitAlpha blending、缩放、还有很多……

In this sample we have seen how to implement Direct3D to perform fast 2D functions. Also, we have seen how we can easily gain important 2D functions (stretching, alpha blending, etc.) without sacrificing our 2D coding knowledge.

下面我给出本文所介绍的程序源码,相比文中所介绍的,它做了很少的一些改动。为了你能更清楚地理解这段程序,我去掉了读取TGA图像文件的部分。你可以从这里下载源代码:dx8adv2d.zip(43K)

There have been some slight changes in the source that is bundled with this text, but the changes are largely cosmetic. I took out the TGA reader to make the code more readable. Download the source code here: dx8adv2d.zip (43k)

Brand Gamblin在过去的5年中一直在Microprose Hunt Valley StudioKinesoft Austin担任游戏程序员。不过他现在正在找工作(译注:呵呵,跟我一样^^),他还维护着他的个人主页,你可以在这里访问:http://www.niftycode.com(有空的话,也请访问我的主页^^:http://tlovexyj.yeah.net

Brand Gamblin has worked as a game programmer for the last five years at Microprose Hunt Valley Studio and Kinesoft Austin. He's now looking for work, and maintaining his own website athttp://www.niftycode.com
 

我来说两句】 【加入收藏】 【返加顶部】 【打印本页】 【关闭窗口
中搜索 DirectX8中的二维图形高级技巧
本类热点文章
  在DirectX 8 中进行2D渲染
  DirectDraw打造极速图形引擎(一)
  用窗口模式运行游戏
  DirectDraw编程基础
  Windows的位图alpha混合技术
  再谈GDI模式作图
  终极优化你的游戏——使用脏矩形技术
  D3D8里面的2D图形编程
  全屏模式
  对2D游戏引擎设计的一些思考
  DirectX8中的二维图形高级技巧
  使用标准GDI实现游戏品质的动画系统
最新分类信息我要发布 
最新招聘信息

关于我们 / 合作推广 / 给我留言 / 版权举报 / 意见建议 / 广告投放  
Copyright ©2003-2024 Lihuasoft.net webmaster(at)lihuasoft.net
网站编程QQ群   京ICP备05001064号 页面生成时间:0.00452