Over here I’ll shove in some basics, like coordinate systems, world and object coordinate systems, etc. For now I’ll assume you’re at least a little familiar with 3D programming. Blah blah blah, differences between immediate and retained mode, etc etc.
Devices
Direct3D interfaces with the surface it is rendering to (e.g. screen memory, system memory) using an IDirect3DRMDevice object. More than one type of rendering device can exist and a specific rendering device must be chosen for a scene. For example, there is normally a device for RGB rendering and a device for Mono rendering (these names refer to the lighting model used for rendering. Mono means that only white lights can exist in the scene, while RGB supports colored lights, and is thus slower). Additional devices may be installed that make use of 3D hardware acceleration. It is possible to iterate through the installed D3D devices by enumarating through them (EnumDevices). It is possible to have two different devices rendering to the same surface.
Viewports
The IDirect3DRMViewport object is used to keep track of how our 3D scene is rendered onto the device. It is possible to have multiple viewports per device, and it is also possible to have a viewport rendering to more than one device. The viewport object keeps track of the camera, front and back clipping fields, field of view etc.
Frames
A frame> in Direct3D is basically used to store an object’s position and orientation information, relative to a given frame of reference, which is where the term frame comes from. Frames are positioned relative to other frames, or to the world coordinates. Frames are used to store the positions of objects in the scene as well as other things like lights. OK, so I’m explaining it badly. It’s late, I’m tired, I’ll revise it soon. To add an object to the scene we have to attach the object to a frame. The object is called a visual in Direct3D, since it represents what the user sees. So, a visual has no meaningful position or orientation information itself, but when attached to a frame, it is transformed when rendered according to the transformation information in the frame. Multiple frames may use the same visual. This can save a lot of time and memory in a situation like, for example, a forest or a small fleet of spacecraft, where you have a bunch of objects that look exactly the same but all exist in different positions and orientations.
Here is a crummy ASCII diagram of a single visual attached to two frames which are at different positions:
1 2 3 4 5 6 7 | _____ / /| <- Cube (visual) / / |<==========================>[Frame1: (21, 3, 4)] +----+ | | | /<===========================>[Frame2: (-12, 10, -6)] | |/ +----+ |
If both of these frames were attached to the scene frame, then our scene would have 2 cubes in it; one at (21, 3, 4) and the other at (-12, 10, -6).
The Direct3D RM Sample
Setting up global variables
Before we start we’ll need a few global variables.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | LPDIRECTDRAW pDD; // A DirectDraw object LPDIRECT3DRM pD3DRM; // A Direct3D RM object LPDIRECTDRAWSURFACE pDDSPrimary; // DirectDraw primary surface LPDIRECTDRAWSURFACE pDDSBack; // DirectDraw back surface LPDIRECTDRAWPALETTE pDDPal; // Palette for primary surface LPDIRECTDRAWCLIPPER pClipper; // Clipper for windowed mode LPDIRECT3DRMDEVICE pD3DRMDevice; // A device LPDIRECT3DRMVIEWPORT pViewport; // A viewport LPDIRECT3DRMFRAME pCamera; // A camera LPDIRECT3DRMFRAME pScene; // The scene LPDIRECT3DRMFRAME pCube; // The one and only object in // our scene BOOL bFullScreen; // Are we in full-screen mode? BOOL bAnimating; // Has our animating begun? HWND ddWnd; // HWND of the DDraw window |
Note that we need both a DirectDraw object and a Direct3D object to create a Direct3D application. This is because Direct3D works in conjunction with DirectDraw. As before, we need a primary and a back surface for our double-buffering, and a clipper to handle window-clipping in windowed mode. The palette object is still not discusses in this tutorial (yet). We have objects for the device and viewport, and we have frame objects to keep track of the scene and the scene’s camera. Also, we have a frame that is used for the object we’ll have in this scene.
Here is a routine just to initially flatten these globals:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | void InitDirectXGlobals() { pDD = NULL; pD3DRM = NULL; pDDSPrimary = NULL; pDDSBack = NULL; pDDPal = NULL; pClipper = NULL; pD3DRMDevice = NULL; pViewport = NULL; pCamera = NULL; pScene = NULL; pCube = NULL; bFullScreen = FALSE; bAnimating = FALSE; } |
From ‘Initializing the DirectDraw system’ to ‘Creating the clipper’
These steps all proceed exactly as in the DirectDraw sample, with the exception of the CreateSurface function, where the back surface has to created with the DDSCAPS_3DDEVICE, since it will be used for 3d rendering:
1 2 3 4 5 6 7 8 9 10 11 | UINT CreatePrimarySurface() { . . . // Create an offscreen surface, specifying 3d device ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE; . . . } |
Creating the Direct3D Retained Mode object
Now we need to create an IDirect3DRM object. This is achieved, quite simply, by calling the Direct3DRMCreate function.
1 2 3 4 5 6 7 8 9 10 11 | UINT CreateDirect3DRM() { HRESULT hr; // Create the IDirect3DRM object. hr = Direct3DRMCreate(&pD3DRM); if (FAILED(hr)) { TRACE("Error creating Direct3d RM objectn"); return 1; } return 0; } |
Creating the device for rendering
We create the device object from the back surface, since this surface is the one we will render to.
1 2 3 4 5 6 7 8 9 10 11 12 | UINT CreateDevice() { HRESULT hr; hr = pD3DRM->CreateDeviceFromSurface( NULL, pDD, pDDSBack, &pD3DRMDevice); if (FAILED(hr)) { TRACE("Error %d creating d3drm devicen", int(LOWORD(hr))); return 1; } // success return 0; } |
Creating the viewport
We do a bit more than just create the viewport here. We create the scene object and the camera object, as well as set the ambient light for the scene, and create a directional light.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | UINT CreateViewport() { HRESULT hr; // First create the scene frame hr = pD3DRM->CreateFrame(NULL, &pScene); if (FAILED(hr)) { TRACE("Error creating the scene framen"); return 1; } // Next, create the camera as a child of the scene hr = pD3DRM->CreateFrame(pScene, &pCamera); if (FAILED(hr)) { TRACE("Error creating the scene framen"); return 2; } // Set the camera to lie somewhere on the negative z-axis, and // point towards the origin pCamera->SetPosition( pScene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(-300.0)); pCamera->SetOrientation( pScene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0)); // create lights LPDIRECT3DRMLIGHT pLightAmbient = NULL; LPDIRECT3DRMLIGHT pLightDirectional = NULL; LPDIRECT3DRMFRAME pLights = NULL; // Create two lights and a frame to attach them to // I haven't quite figured out the CreateLight's second // parameter yet. pD3DRM->CreateFrame(pScene, &pLights); pD3DRM->CreateLight(D3DRMLIGHT_AMBIENT, pD3DRMCreateColorRGB( D3DVALUE(0.3), D3DVALUE(0.3), D3DVALUE(0.3)), &pLightAmbient); pD3DRM->CreateLight(D3DRMLIGHT_DIRECTIONAL, D3DRMCreateColorRGB( D3DVALUE(0.8), D3DVALUE(0.8), D3DVALUE(0.8)), &pLightDirectional); // Orient the directional light pLights->SetOrientation(pScene, D3DVALUE(30.0), D3DVALUE(-20.0), D3DVALUE(50.0), D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0)); // Add ambient light to the scene, and the directional light // to the pLights frame pScene->AddLight(pLightAmbient); pLights->AddLight(pLightDirectional); // Create the viewport on the device hr = pD3DRM->CreateViewport(pD3DRMDevice, pCamera, 10, 10, 300, 220, &pViewport); if (FAILED(hr)) { TRACE("Error creating viewportn"); return 3; } // set the back clipping field hr = pViewport->SetBack(D3DVAL(5000.0)); // Release the temporary lights created. It seems // they will have been copied for the scene during AddLight pLightAmbient->Release(); pLightDirectional->Release(); // success return 0; } |
Putting it all together
Here is the tail-end of the app’s InitInstance function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | InitDirectXGlobals(); TRACE("Calling InitDDrawn"); InitDDraw(); SetMode(); // TRACE("Calling LoadJascPaletten"); // LoadJascPalette("inspect.pal", 10, 240); TRACE("Calling CreatePrimarySurfacen"); CreatePrimarySurface(); TRACE("Calling CreateClippern"); CreateClipper(); // TRACE("Calling AttachPaletten"); // AttachPalette(pDDPal); TRACE("Calling CreateDirect3DRMn"); CreateDirect3DRM(); TRACE("Calling CreateDevicen"); CreateDevice(); TRACE("Calling CreateViewportn"); CreateViewport(); TRACE("Calling CreateDefaultScenen"); CreateDefaultScene(); bAnimating = TRUE; return TRUE; } |
Restoring lost surfaces
Same as the DirectDraw sample:
1 2 3 4 5 6 7 8 9 10 11 | BOOL CheckSurfaces() { // Check the primary surface if (pDDSPrimary) { if (pDDSPrimary->IsLost() == DDERR_SURFACELOST) { pDDSPrimary->Restore(); return FALSE; } } return TRUE; } |
The Rendering loop
Same as the DirectDraw sample:
1 2 3 4 5 6 7 8 9 | BOOL CD3dRmAppApp::OnIdle(LONG lCount) { CWinApp::OnIdle(lCount); if (bAnimating) { HeartBeat(); Sleep(50); } return TRUE; } |
The HeartBeat function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | BOOL CD3dRmAppApp::HeartBeat() { HRESULT hr; // if (!CheckSurfaces) bForceUpdate = TRUE; // if (bForceUpdate) pViewport->ForceUpdate(10,10,300,220); hr = pD3DRM->Tick(D3DVALUE(1.0)); if (FAILED(hr)) { TRACE("Tick error!n"); return FALSE; } // Call our routine for flipping the surfaces FlipSurfaces(); // No major errors return TRUE; } |