Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Recording DirectX and OpenGL Rendered Animations

0.00/5 (No votes)
22 Feb 2007 1  
Explains methods for recording DirectX and OpenGL rendered animations programmatically.

Sample Image - SimulationRecording.jpg

Contents

Introduction

While creating games and simulations, sometimes it is necessary to record the rendered content for offline viewing. This is especially unavoidable in cases where the actual rendering process is too complex and time consuming to repeat on demand. Also for creating the cut-scene movies it is necessary to record the rendered animation apriori and use them in the games.

In DirectX, the library function D3DXSaveSurfaceToFile() helps in saving a surface to an image file. For OpenGl, we could use glReadPixels() to read the rendered image bits and then can manually save them to an image file. While these suffice for single frame recording, no such easy way exists for recording sequence of frames continuously (or selectively). In other words, no library functions exist to record our complete rendered animation results.

In this regard, this article presents few classes that are helpful in creating movies out of DirectX and OpenGL animations. Movies can be created from DirectX and OpenGL rendered frames, selectively or continuously, using the classes CDxToMovie and CGLToMovie presented below. In general, a typical movie creation process involves complex tasks such as reading the bitmap content, selecting the frame rate settings, codec settings, initalizing the media streams, writing the streams etc... (for a detailed discussion on how to create movies from normal HBitmap image sequences, please refer to the article Create Movie from HBitmap). The classes CDxToMovie and CGLToMovie presented here abstract out all such unnecessary complexity and provide easy to use interface with simple and straight forward methods as explained below.

Recording a Movie from DirectX Rendered Sequence

The class CDxToMovie can record DirectX rendered sequences into a movie file. This class uses DirectX 9.0 interfaces such as LPDIRECT3DSURFACE9 for its functionality and hence you should be using DirectX 9.0 SDK or its compatibles to use this class. Using this class is pretty straighforward as described below.

To start with, copy the files DxToMovie.h, RenderTarget.h, AviFile.h and AviFile.cpp from the DirectX source code of this article to your project directory and add them to your project. And add vfw32.lib to the linker input libraries. Once added to your project, you can access the CDxToMovie class from your code by #including the header file "DxToMovie.h".

The CDxToMovie constructor accepts various arguments such as the output movie filename, the movie frame width and height required, bits per pixel etc... as shown below.

 CDxToMovie(LPCTSTR lpszOutputMovieFileName = _T("Output.avi"),
       int nFrameWidth = GetSystemMetrics(SM_CXSCREEN),  /*Frame Width*/
       int nFrameHeight = GetSystemMetrics(SM_CYSCREEN), /*Frame Height*/
       int nBitsPerPixel = 32,     /*Bits per Pixel*/
       DWORD dwCodec = mmioFOURCC('M','P','G','4'),  /*Video Codec*/
       DWORD dwFrameRate = 1)      /*Frame Rate (FPS)*/

You can either pass your own values for these parameters or use the default values (which should work fine for most cases). However, it should be noted that these are one time settings and cannot be changed later during the movie recording time. Each CDxToMovie corresponds to a different movie file and recreating a CDxToMovie object with same output file name would not append to the previous movie content but would overwrite it.

 CDxToMovie g_MovieRecorder("Output.Avi", 320, 240);

Once an object has been created for the CDxToMovie, the method CDxToMovie::OnCreateDevice() should be invoked on that object at the time of Direct3D device creation for your application. Similarily, CDxToMovie::OnLostDevice(), CDxToMovie::OnResetDevice() and CDxToMovie::OnDestroyDevice() should be called at times when the device is lost, reset and destroyed respectively. The prototypes of these functions are as shown below.

  class CDxToMovie
  {
    HRESULT OnCreateDevice(LPDIRECT3DDEVICE9 pd3dDevice);
    HRESULT OnDestroyDevice(LPDIRECT3DDEVICE9 pd3dDevice);
    HRESULT OnLostDevice();
    HRESULT OnResetDevice(LPDIRECT3DDEVICE9 pd3dDevice, 
                        const D3DSURFACE_DESC* pBackBufferSurfaceDesc);
  };

The functions OnCreateDevice() and OnDestroyDevice() accept a single parameter, the pointer to your application's Direct3D device object. OnLostDevice() takes no parameters, but OnResetDevice() requires a pointer to the surface description of your device's back buffer surface as a D3DSURFACE_DESC*. The CDxToMovie object uses the information provided in the D3DSURFACE_DESC to create an appropriate offscreen render target internally that can be used to record your application's rendering.

The actual recording is done by the functions CDxToMovie::StartRecordingMovie() and CDxToMovie::PauseRecordingMovie(). These two functions must be called for each frame between IDirect3DDevice9::BeginScene() and IDirect3DDevice9::EndScene() as shown below.

  g_pd3dDevice->BeginScene();

  // Capture the Rendering onto CDxToMovie's Render Target

  g_MovieRecorder.StartRecordingMovie(g_pd3dDevice);
     // Render as usual.....    

     g_pd3dDevice->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
                            D3DCOLOR_XRGB(0,0,200),1,0);
     g_pd3dDevice->SetStreamSource(0,g_pVB,0,sizeof(CUSTOMVERTEX));
     g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
     g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST,0,1);
  g_MovieRecorder.PauseRecordingMovie(g_pd3dDevice);

  // Copy the CDxToMovie's Render Target content onto BackBuffer's Surface

  g_pd3dDevice->StretchRect(g_MovieRecorder.RecordingSurface(),
                              NULL,pBackSurface,
                              0,D3DTEXF_NONE);

  g_pd3dDevice->EndScene();

In the above code snippet, the g_MovieRecorder.StartRecordingMovie(g_pd3dDevice) redirects all the subsequent rendering onto the CDxToMovie's internal render target until the g_MovieRecorder.PauseRecordingMovie(g_pd3dDevice) is called, which would restore the render target back to its original surface. Since all the rendering is done on the CDxToMovie's internal render target, your application's back surface would not be having any valid content to display in your application window. This would not matter if your application is just creating the movie directly wihtout presenting any animation on the screen (for example, if you are creating a rendered cut-scene to be inserted later into a game). However, if you are recording the movie from an interactive game session, it would be bad if the screen is not updated with the latest rendered content (because CDxToMovie has stolen the content by redirecting the render target). To avoid this, you can optionally copy back the CDxToMovie's internal render target content onto your application's back surface using the method IDirect3DDevice9::StretchRect(), followed by the usual g_pd3dDevice->EndScene() and g_pd3dDevice->Present() calls that would present the updated contents of the back buffer onto the screen, keeping the screen up-to-date.

In case you want to selectively avoid few frames from being recorded into the movie, just do not call the g_MovieRecorder.StartRecordingMovie() and g_MovieRecorder.PauseRecordingMovie() (and the correspoding g_pd3dDevice->StretchRect()) for those frames, and the animation would be directly rendered onto the screen (without being redirected to the CDxToMovie's internal render target).

The demo code supplied with this article presents a simple DirectX application that renders on the screen a triangle that moves as the mouse moves over the window, which would simulatenously be rendered and recorded into a movie file (named output.avi). To run the demo executable, make sure you have the MPG4 codec installed on your machine and that the directory has write permissions to create the output movie file. For more details on Codecs and FPS settings, please refer to the article Create Movie From HBitmap.

Recording a Movie from OpenGL Rendered Sequence

The class CGLToMovie can record OpenGL rendered sequences into a movie file. This is a very simple and straight forward class as explained below.

To start with, copy the files GLToMovie.h, AviFile.h, and AviFile.cpp from the OpenGL source code of this article to your project directory and add them to your project. And add vfw32.lib to the linker input libraries. Once added to your project, you can access the CGLToMovie class from your code by #include-ing the header file "GLToMovie.h".

The CGLToMovie constructor accepts various arguments such as the output movie filename, the movie frame width and height required, bits per pixel etc... as shown below.

 CGLToMovie(LPCTSTR lpszOutputMovieFileName = _T("Output.avi"), 
       int nFrameWidth = GetSystemMetrics(SM_CXSCREEN),  /*Frame Width*/
       int nFrameHeight = GetSystemMetrics(SM_CYSCREEN), /*Frame Height*/
       int nBitsPerPixel = 24,  /*Bits per Pixel*/
       DWORD dwCodec = mmioFOURCC('M','P','G','4'),    /*Video Codec */
       DWORD dwFrameRate = 1)  /*Frames Per Second (FPS)*/

You can either pass your own values for these parameters or use the default values (which should work fine for most cases). However, it should be noted that these are one time settings and cannot be changed later during the movie recording time. Each CGLToMovie corresponds to a different movie file and recreating a CGLToMovie object with same output file name would not append to the previous movie content but would overwrite it.

  CGLToMovie g_MovieRecorder("Output.Avi", VIEWPORTWIDTH, VIEWPORTHEIGHT);

After creating an object of CGLToMovie, all that need be done to record the animation is to call CGLToMovie::RecordFrame() method for each frame before invoking the SwapBuffers(). The code snippet for this would look like below.

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
   // Render as usual

   for (int i = 0; i < NUM_SHARKS; i++) 
   {
      glPushMatrix();
      FishTransform(&sharks[i]);
      DrawShark(&sharks[i]);
      glPopMatrix();
   }
   
   // Capture the Rendering into CGLToMovie's movie file

   g_MovieRecorder.RecordFrame();

   SwapBuffers(wglGetCurrentDC());

The function CGLToMovie::RecordFrame() internally uses the glReadPixels() method to read the content of the frame buffer and appends it the frame to the output movie file. In case you want to selecitvely avoid few frames from being recorded into the movie, just do not call the g_MovieRecorder.RecordFrame() method for those frames and they would be skipped from being append into the movie.

The demo code supplied with this article presents a simple OpenGL application that animates few sharks on the screen, which would simulatenously be rendered and recorded into a movie file (named output.avi). To run the demo executable, make sure you have the Cinepak codec installed on your machine and that the directory has write permissions to create the output movie file. For more details on Codecs and FPS settings, please refer to the article Create Movie From HBitmap.

Few Points to Note to avoid Errors

  • In the rendering applications it is often possible to change the render surface format (or width and height settings) and continue the animation with the new settings. However, once a movie gets started with a certain width, height, and bits per pixel settings for its first frame, it is not quite possible to change these settings in-between for later frames. All frames of the movie should have same format and size. Thus, it is required that you should restrict your application rendering window from being resized or its device and surface from being changed to a different format while recording the movie. Once the movie recording starts with a particual setting and format, the whole movie should be recorded with same settings and format.
  • As explained in the article Create Movie from HBitmap, a movie requires a Codec to compress its frames. In the code supplied with this article, the movie creation functionality is handled in the AviFile.h and AviFile.cpp files. The default codec used is MPG4, which must be installed on your machine to be able to succesfully create the movie file. You can modify the code in AviFile.cpp to use your own choice of codec for the movie creation. However, if the codec you are using is not present on the system or that your frame size settings do not meet the codec format requirements, then you might encounter an error while rendering the movie. (However, due to the fault-tolerant nature of the movie creation library, your rendering application would continue to execute despite the movie rendering error.) For more details, refer to the afore mentioned article.
  • The movie creation library uses function pointers for its functionality. However, due to the breaking changes in Visual Studio 2005 the function pointer assignment is different between Visual Studio.Net compiler the latest Visual Studio 2005 compiler. With Visual Studio 2005, pointer-to-members now require qualified name and &. In case you encounter errors in this regard while compiling the source code, please refer to: Breaking Changes in the Visual C++ 2005 Compiler.

Conclusions

This articles presented few classes that are helpful in recording movies from DirectX and OpenGL rendered animations. The generated movie quality may vary depending on a variety of settings ranging from the video frame rate settings to the codec being used. The choice of the codec highly influences the quality of the output movie generated. For example, for some screen capture applications choosing a particular codec known as Windows Media Video 9 Screen codec should give optimal results in both quality and size, though the same may not be the case for high bit rate animation applications (refer to the article Capturing the Screen for more details about capturing the screen programmatically). The above described classes use avi as the movie type. However, they can be used to created other types of movies such as wmv and mov with same ease.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here