会员: 密码:  免费注册 | 忘记密码 | 会员登录 网页功能: 加入收藏 设为首页 网站搜索  
游戏开发 > 程序设计 > 3D图形
D3D初始化简介
发表日期:2007-01-19 16:03:09作者: 出处:  

介绍本文通过一个应用D3D的屏幕保护程序提供了在编程时对DDraw,D3D初始化过程的一个简介。相当不错。

本文作者:Jason Cwik, mail:jcwik@visi.com
文中所提的屏幕保护程序
源代码在此

BTW: 本人的E文实在是有点拙劣,水平不足,但自我感觉勇气可嘉, ;) 纯属乱翻一气,为提高本文的可用性,使用中英对照的方式发布,若想骂我敬请到留言簿处,或mailme


有关法律责任

本文和所有本文的相关部分的版权归Jason Cwik所有,授权Pigprince中文版版权所有,授权您可以通过电子手段(二进制形式)分发(e-mail、邮寄或存储)本文档的全部或部分,在没有对本文做任何改变的情况下授权容许打印拷贝。建议使用最新版本。 所有的信用和版权声明被保留,如果您要在其它站点加一个指向本文的连接请通知 Jason Cwik(E文版)或Pigprince(中英文对照版)取得授权。

需要拥有其它发布形式权利的,如在公司、组织、商业产品如:书籍、报刊杂志、CD-ROM、应用软件等中发布的,请尊重作者的权利(署名权,取得报酬权等)。

本文没有任何明确的或含蓄的表达说明本文是完全正确的,对于应用本文内容所产生的任何结果,本人概不负责。如果您在开发较高预算的工程,请您不要只依赖于本文的一家之言。

所有的名称,商标,版权等属于该名称,商标,版权等的法定所有者。


Probably the most complicated part of writing a DirectDraw or Direct3D program is the initalization of the drawing/rendering engine. Here I will present the initalization sequence of my screensaver in detail, pointing out the steps as we go. Be aware that there is more than one method to initialize Direct3D. In my opinion, this method is more complex, but allows the most flexibility.

也许在写一个DirectDraw或者Direct3D程序时最麻烦、最复杂的莫过于初始化部分了。在这里我将详细的介绍在我写的屏幕保护程序中的初始化部分,指出我们必需走的几步。需要注意的是不是只有一种方法来进行Direct3D的初始化,我认为虽然DirectX的初始化十分复杂,但是却具有极大的弹性。

The first step is to create the DirectDraw interface. We do this with a call to DirectDrawCreate. This call doesn't do much except for initalize the COM interface to DirectDraw. Also notice that throughout this example we are using C++ which allows us to remove the virtual table pointer and the first parameter of the function (which is basically just a pointer to this).

第一步: 创建DirectDraw接口

这个工作由DirectDrawCreate函数来完成。这个调用只是初始化DirectDraw的COM接口,还要注意在这个例子中我们使用的是C++,可以很方便的移动虚拟表头指针和函数的第一个参数 (可以简单到只用一个this指针)。
 

    LastResult = DirectDrawCreate(NULL, &lpDD1, NULL);
    if(FAILED(LastResult))
    {
        Msg("Error creating DirectDraw\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }
DirectDraw version 2 has some features that are not present in DirectDraw, and some of our calls later require it, so we will query for DirectDraw2 from the DirectDraw interface. This call is a perfect example of where COM rears it's ugly head. With COM, all different versions of the API are available at once, depending on which one you are looking at. Also, all of the interfaces know about the other ones, so we can query for them. You will see code segments similar to this one throughout the code, whenever we need to get a related interface from an existing object.

DirectDraw2有一些特别,它不是包含在DirectDraw中的,并且我们一会还要用到它,所以我们在directDraw接口中查询它,这个调用依赖于COM接口,COM接口使不同版本、不同性质的的API协同工作,同样所有的COM接口也是协同起来的,您可以在下面程序中的多处看到相似的代码来实现已存在对象与相关接口的关联。

    LastResult = lpDD1->QueryInterface(IID_IDirectDraw2, (void **) &myglobs.lpDD2);
    if(FAILED(LastResult))
    {
        Msg("Error querying DirectDraw2\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }
Since Direct3D is related to DirectDraw, we can query for our Direct3D (version 2) interface from the DirectDraw interface.

既然Direct3D也同样可以关联于DirectDraw,我们也可以在DirectDraw接口中查询Direct3D2接口。
 

    LastResult = myglobs.lpDD2->QueryInterface(IID_IDirect3D2, (void **) &myglobs.lpD3D2);
    if(FAILED(LastResult))
    {
        Msg("Error querying Direct3D2\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }
The example program also uses the Retained Mode interface, so we create it with a call to Direct3DRMCreate.

这个例子是基于Direct3D Retained-Mode接口,我们将使用Direct3DRMCreate进行创建。
 

    // Create D3DRM
    LastResult = Direct3DRMCreate(&lpD3DRM1);
    if(FAILED(LastResult))
    {
        Msg("Error creating D3DRM\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }
Again, we want to use the newer version of the interface, so we will query for it from the older interface.

再者我们要使用接口的新实例,要在旧的接口中查询。

    LastResult = lpD3DRM1->QueryInterface(IID_IDirect3DRM2, (void **) &myglobs.lpD3DRM2);
    if(FAILED(LastResult))
    {
        Msg("Error querying D3DRM2\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }
Since we are done with the D3DRM (version 1) interface, we can now release it. The RELEASE() macro just makes sure the pointer isn't NULL, calls the release() method of the interface and then sets the pointer to NULL .

既然D3DRM接口我们已用完了,现在应该释放它。RELEASE()宏判断指针为非空(!NULL) 后调用接口的release()方法将指针置空。

    RELEASE(lpD3DRM1);
The next step which isn't completely shown right here (Look for it later) enumerates the drivers available and the modes for each driver. Earlier in the program, we loaded strings from the registry specifying which driver and mode the user wanted to use. After enumeration, we search through the results to find their selection. If the driver/mode isn't found, we pop up the dialog prompting them to change the selection.

第二步:例举了已有的驱动和每个驱动的具体模式。
但对这一步的叙述并不完整。在这个程序的早期版本里,我们从注册表中读取指明用户要使用的驱动和模式的字符串,在实例化以后我们通过搜索结果值来确定用户的选择。如果该驱动或驱动模式未被发现则弹出一个对话框,提示用户改变他们的选择。

    if(!EnumDrivers())
    {
        Msg("Error enumerating D3D device drivers.\n");
        goto exit_with_error;
    }
    if(!EnumModes())
    {
        Msg("Error enumerating DirectDraw display modes.\n");
        goto exit_with_error;
    }

    // Find our driver and mode. If we can't find the driver / mode
    // they want, run the configuration dialog.
    if(!SelectDriver(driver_buffer))
    {
        Msg("Could not find requested D3D Driver: %s. Please choose a new one.\n",  driver_buffer);
        theApp.DoConfig();
        goto exit_with_error;
    }

    if(!SelectMode(mode_buffer))
    {
        Msg("Could not find requested display mode: %s. Please choose a new one.\n",  mode_buffer);
        theApp.DoConfig();
        goto exit_with_error;
    }

The next section we must branch depending on whether we are doing a full screen mode or not (this is to facilitate debugging and for the little preview window in the screen saver dialog). Right before the if, I have a line that I can uncomment if I want to force the program to run in a window instead of fullscreen for debugging. I am only going to present the fullscreen code here, since that's what most people are interested in. The windowed code is available here.

下面我们要处理一个满屏/非满屏的分支,非满屏的窗口模式是为了更容易的调式和处理在屏幕保护对话框中的预览窗。在IF语句之前的注释处应是强迫程序运行于窗口的代码,这段代码在这里

    // For debugging purposes, set this to FALSE and the screensaver will
    // run in a window instead of fullscreen.
    //myglobs.bFullscreen = FALSE;

    if(myglobs.bFullscreen)
    {

The first part of setting the video mode involves setting the Cooperative Level. In this call, we tell DirectDraw that we want to go into fullscreen mode (DDSCL_FULLSCREEN), which in turn requires exclusive mode (DDSCL_EXCLUSIVE). As an added precaution, we also set the flag DDSCL_ALLOWREBOOT, which makes DirectDraw trap for alt-ctl-delete so it can switch back to windowed mode.

显示模式设置的第一部分包括“合作水平”(Cooperative Level),在下面的调用中,我们告诉DirectDraw我们要进入排他模式(DDSCL_EXCLUSIVE)的满屏模式(DDSCL_FULLSCREEN), 作为一项附加的防范,还要置标志DDSCL_ALLOWREBOOT使DirctDraw接管Alt-Ctl-Del消息,以便恢复为窗口模式。
 

    LastResult = myglobs.lpDD2->SetCooperativeLevel(myglobs.hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWREBOOT);
    if(FAILED(LastResult))
    {
        Msg("Error setting Exclusive mode\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }
Now that we've told DirectDraw to operate in exclusive mode, we must tell it what mode to use. This was read in earlier from the options stored in the registry. The last two arguments are the refresh rate and flags, neither of which are used here. Setting the refresh rate to zero assumes the default value, which should be fine for almost every application. After setting the mode, we make sure the global variables know about the size of the screen. The ClientOnPrimary field is used in windowed mode, because DirectDraw doesn't care about the client area of the window, coordinates are still specified in absolute screen coordinates.

现在我们已经告诉了DirectDraw工作在排他模式,记住一定要对工作模式进行设定,这是在先前从注册表中读取的。最后二项内容是刷新率和标志,这里没有用到这二项。将刷新率设为0来使用刷新率的缺省值,在绝大多数的程序中都可以工作得很好。在模式设置后,要确信全局变量中包含了屏幕的大小。ClientOnPrimary部分用于窗口模式,因为DirectDraw不管窗口客户区的大小,坐标系还是使用绝对屏幕的坐标系。

    LastResult = myglobs.lpDD2->SetDisplayMode(
       myglobs.Mode[myglobs.ModeSelected].w,
       myglobs.Mode[myglobs.ModeSelected].h,
       myglobs.Mode[myglobs.ModeSelected].bpp, 0, 0);
    bIgnoreWM_SIZE = FALSE;
    if(FAILED(LastResult))
    {
        Msg("Error setting video mode. Try running configuration again.\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }

    myglobs.WindowWidth = myglobs.Mode[myglobs.ModeSelected].w;
    myglobs.WindowHeight = myglobs.Mode[myglobs.ModeSelected].h;
    myglobs.pClientOnPrimary.x = myglobs.pClientOnPrimary.y = 0;

Now that we have the mode set up, we need to create the drawing surfaces. To do good animation, we need to double-buffer, so we're going to set up the flags so that it creates one back buffer (you can make as many as you have VRAM for, but two should be sufficient unless you're doing stereo imaging... but that's a whole other topic). This call is similar to may DirectX calls that have lots of fields. You initlaize the structure to zero, then set flags in the dwFlags element specifiing which fields you have filled in.

现在我们已经进行完了模式设置,我们还需要双缓冲来显示效果良好的动画,可以通过设置一些标志来创建一个后缓冲(你可以在你的显示内存足够情况下创建任意多个,但是一般二个应该足够了,除非你要做立体图形实时绘演,可那完全是另一个问题了)。这个调用可以应用于许多的DirectX功能调用,将这个结构初始化为0,然后置dwFlags元件指明你要置位的功能。
 

    // Create the front and back surfaces
    memset(&ddsd, 0, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_3DDEVICE | DDSCAPS_FLIP |
       DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX;
    if(myglobs.Driver[myglobs.DriverSelected].bIsHardware)
        ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    ddsd.dwBackBufferCount = 1;

    LastResult = CreateSurface(&ddsd, &myglobs.lpFrontBuffer);
    if(FAILED(LastResult))
    {
        Msg("Error creating Buffers\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }

    // Get a handle on the back buffer

    ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
    LastResult = myglobs.lpFrontBuffer->GetAttachedSurface(&ddsd.ddsCaps, &myglobs.lpBackBuffer);
    if(FAILED(LastResult))
    {
        Msg("Error Getting Back Buffer\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }

If the hardware does Z-buffering (for 3D), we'll create another surface in video memory for it. We also need to check what depth Z buffer is needed (because different cards do different levels). After the surface is created, it is attached to the others.

如果硬件支持3D的Z-buffering,我们就要在显示内存中创建另一个表面,还需测试 Z-buffer 的深度(因为不同的显示卡支持的深度不同)。创建完的表面隶属于其它的表面。
 

    // Create a Z buffer if we need it.
    if(myglobs.Driver[myglobs.DriverSelected].bDoesZBuffer)
    {
        memset(&ddsd, 0, sizeof(ddsd));
        ddsd.dwSize = sizeof(ddsd);
        ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS| DDSD_ZBUFFERBITDEPTH ;
        ddsd.dwWidth = myglobs.Mode[myglobs.ModeSelected].w;
        ddsd.dwHeight = myglobs.Mode[myglobs.ModeSelected].h;
        ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER;
        if(myglobs.Driver[myglobs.DriverSelected].bIsHardware)
            ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
        else
            ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;

        devDepth = myglobs.Driver[myglobs.DriverSelected].Desc.dwDeviceZBufferBitDepth;
        if (devDepth & DDBD_32)
            ddsd.dwZBufferBitDepth = 32;
        else if (devDepth & DDBD_24)
            ddsd.dwZBufferBitDepth = 24;
        else if (devDepth & DDBD_16)
            ddsd.dwZBufferBitDepth = 16;
        else if (devDepth & DDBD_8)
            ddsd.dwZBufferBitDepth = 8;
        else
        {

            Msg("Unsupported Z-buffer depth requested by device.\n");
            goto exit_with_error;
        }
        LastResult = CreateSurface(&ddsd, &myglobs.lpZBuffer);
        if(FAILED(LastResult))
        {
            Msg("Error creating ZBuffer Surface.\n%s", D3DRMErrorToString(LastResult));
            goto exit_with_error;
        }
        myglobs.lpBackBuffer->AddAttachedSurface(myglobs.lpZBuffer);
    }

Now that the surfaces are created, we can create the rendering device (D3DDevice2). 现在表面创建完毕,下面我们创建绘演设备(D3DDevice2),
 

    // Create a D3DDevice2 from the surfaces.
    LastResult = myglobs.lpD3D2->CreateDevice(myglobs.Driver[myglobs.DriverSelected].Guid,(LPDIRECTDRAWSURFACE) myglobs.lpBackBuffer, &myglobs.lpD3DDevice2);
    if(FAILED(LastResult))
    {
        Msg("Error creating D3DDevice from surface\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }
    }
    //** Windowed mode initialization goes here **
All of the drivers are now initialized. If you are doing immediate mode, stop here. The rest of the code is for setting up retained mode. The first thing we do is to create the D3DRMDevice from the D3DDevice.

所有的驱动已初始化完毕,如果您是工作在D3D Immediate-mode模式,初始化就已完成。下面的代码是设置D3D Retained-mode模式,首先要做的是从D3DDevice中创建D3DRMDevice。
 

    // Create the RM from the IM
    LastResult = myglobs.lpD3DRM2->CreateDeviceFromD3D(myglobs.lpD3D2, myglobs.lpD3DDevice2, &myglobs.lpD3DRMDevice2);
    if(FAILED(LastResult))
    {
        Msg("Error creating D3DRMDevice2 from D3D\n%s", D3DRMErrorToString(LastResult-1));
        goto exit_with_error;
    }

There is a chance that our dimensions have changed, so we will do a quick check of the height and width.

因为存在环境尺寸变化的可能性,我们要对环境的宽、高进行快速检查。
 

    // Get the final width and height
    if(!myglobs.bFullscreen)
    {
        myglobs.WindowWidth = myglobs.lpD3DRMDevice2->GetWidth();
        myglobs.WindowHeight = myglobs.lpD3DRMDevice2->GetHeight();
    }
     
We can now create the scene and the camera frames. Since the camera is part of the scene, it is created inside the scene. The scene is the master frame so it has no parent (the NULL pointer). The camera is then placed at 0,0,0 in the scene.

现在可以创建场景和照相机桢,照相机是场景的一部分,是创建于场景中的。此场景是主画面对象,没有祖先。照相机是放置在场景的圆点处(0,0,0)。
 

    // Create the master scene frame and camera frame.
    myglobs.lpD3DRM2->CreateFrame(NULL, &myglobs.scene);
    myglobs.lpD3DRM2->CreateFrame(myglobs.scene, &myglobs.camera);
    myglobs.camera->SetPosition(myglobs.scene, D3DVAL(0.0),D3DVAL(0.0), D3DVAL(0.0));

Now that we have the camera, we must map 3D space to a 'viewport' which handles the translation of coordinates from 3D space to the 2D screen. 现在我们已经有了照相机,但是还要将3D空间影射到把三维空间坐标系转换到二维屏幕坐标系的“视口”上。
 

    // Create the viewport
    LastResult = myglobs.lpD3DRM2->CreateViewport(myglobs.lpD3DRMDevice2,myglobs.camera, 0, 0, myglobs.WindowWidth, myglobs.WindowHeight,&myglobs.lpD3DRMView);
    if (FAILED(LastResult))
    {

        Msg("Error creating viewport\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }

The back clipping plane determines the cutoff point for rendering polygons. Here we're setting it to 5000. This value will vary depending on your coordinate system.

背景剪裁平面决定了展现多边形时剪裁的点数,在这里我们设定的值是5000,这个值取决于您使用的坐标系。
 

    // Set the back clipping plane
    LastResult = myglobs.lpD3DRMView->SetBack(D3DVAL(5000.0));
    if (FAILED(LastResult))
    {

        Msg("Error setting back clipping plane\n%s", D3DRMErrorToString(LastResult));
        goto exit_with_error;
    }
 
The function call to SetRenderState sets up the rendering state variables within RM to control the lighting, shading and texturing otions. A call is then made to BuildScene so the user can set up their objects.

这个函数设置了展现状态变量来控制如光源、阴影和材质等。这个调用将建立场景使用户可以在场景中放置他们要放置的物体。
 

    // Set the render quality, fill mode, lighting state,
    // and color shade info.
     
    if (!SetRenderState())
    {
        Msg("Error setting render state and/or building scene");
        goto exit_with_error;
    }
    if (!BuildScene(myglobs.lpD3DRMDevice2, myglobs.lpD3DRMView, myglobs.scene, myglobs.camera))
    {
        Msg("Error setting render state and/or building scene");
        goto exit_with_error;
    }

The next two lines set up the statistics (FPS, TPS, mode). This code was basically stolen from Micro$oft's demo programs.

下面二行是进行设置统计表(FPS,TPS,模式)。这二行是从微软的演示例子程序中抄来的。
 

    D3DAppIClearBuffers();
    InitFontAndTextBuffers();

Finally, we release DD1 and set our RenderingIsOK flag.

最后我们将释放DD1,并且设置我们的RenderingIsOK
 

    // Done with DD1, release it.
    RELEASE(lpDD1);
     
    // Looks like everything is good, return TRUE.
    myglobs.bRenderingIsOK = TRUE;
    return TRUE;
 
  The following code is called if an error occurs during the initialization. It releases any created interfaces and then returns false. At the end a small test is done to see if we're in windowed mode or not. This is a workaround because when a screensaver is previewing in the Win95 display properties window quits, windows immediately respawns it. So, if an error occurs, you can't get out of the display properties dialog because the respawning screensaver monopolizes the system.

下面的代码用在初始化出错时。它可以释放所有已创建的接口,并且返回一个false,在结尾处有一个是否处于窗口模式的测试。当屏幕保护程序在瘟95的显示属性窗口的预览窗运行时关闭显示属性窗时,瘟95要立刻关闭这个屏保,如果这时产生一个错误,因为屏保程序独占了系统资源,将无法退出显示属性窗。

      exit_with_error:
      // An error occured. Release any created interfaces and return FALSE
      RELEASE(myglobs.lpD3DRMView);
      RELEASE(myglobs.camera);
      RELEASE(myglobs.scene);
      RELEASE(myglobs.lpD3DRMDevice2);
      RELEASE(myglobs.lpD3DRM2);
      RELEASE(myglobs.lpDDClipper);
      RELEASE(myglobs.lpDD2);
      RELEASE(lpD3DRM1);
      RELEASE(lpDD1);
      // if We're in preview mode, don't exit if there is an error. Otherwise,
      // moronic windows will respawn the saver, screwing everything up.
      if(!myglobs.bFullscreen)
      {
          return TRUE;
      }
      else
      {
          return FALSE;
      }
     
返回顶部】 【打印本页】 【关闭窗口

关于我们 / 给我留言 / 版权举报 / 意见建议 / 网站编程QQ群   
Copyright ©2003- 2024 Lihuasoft.net webmaster(at)lihuasoft.net 加载时间 0.00296