Drawing and Printing OpenGL Graphics Using Device-Independent Bitmaps

Environment: VC++6, Win32

Overview

The OpenGL API has been available to PC programmers who wanted to display rendered 3D graphics since Windows NT 3.51 came out in the mid-1990s. It's a terrific platform for 3D visualization for two primary reasons: 1) it exposes some very advanced rendering features, and 2) it is very easy to use.

You might be thinking: "Easy to use???" Well, it is, and this article is intended to be a short primer for someone new to OpenGL programming on the PC.

Normally, a window receives the output from the OpenGL library, using a double-buffer approach. This article shows how to send the OpenGL graphics output directly to a DIB (device-independent bitmap) section.

The decision on whether to draw to a DIB or a window depends on your application; however, drawing straight to a DIB has several convenient advantages: mouse selection of 3D objects is simplified, printing high resolution graphics becomes trivial, the DIB can be displayed on any window (dialog, view, and so forth), and it allows you to use image processing techniques after OpenGL renders the 3D image.

Look and you will find an infinite number of articles and books on how to do 3D programming, with and without graphics libraries such as OpenGL, and how to optimize your 3D code for speed. This article takes a slightly different track and assumes you have some idea of how to do 3D programming and you want to learn to use the OpenGL API effectively.

The following steps allow you to render 3D graphics to a DIB:

  1. Create a DIB section. It can't be a regular bitmap; OpenGL requires that it be a DIB Section.
  2. Initialize the OpenGL library and connect the library with the new DIB.
  3. Execute the drawing code.
  4. Render the DIB to the screen or to the printer.

That's it. You're drawing 3D graphics directly into an image!

Creating a DIB Section

A simple call to CreateDIBSection sets up the image for drawing. In the sample project, the DIB section is encapsulated within the DIBSection class to make things a little easier.

Initialize the OpenGL library and connect the library with the new DIB

The OpenGL API is a C-style interface that operates like a state engine. Think of it like a car. You start it up, and then you put it in gear and give it commands. When you issue commands to the OpenGL engine, its state determines how those commands are implemented. Using the car analogy, what happens when you step on the gas depends very much on whether you are in first gear or reverse.

For initialization, call the following OpenGL functions: SetPixelFormat, wglCreateContext, wglMakeCurrent. The key for drawing directly to a DIB is the SetPixelFormat call: for the library to play nice with a DIB section you have to set the pixel descriptor flags to the following:

PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL |PFD_SUPPORT_GDI

Next, make the following calls to set up how you want your 3D model to look: glViewport, glMatrixMode, glPolygonMode, and glShadeModel.

Now OpenGL should be ready to go.

Execute the drawing code:

This section is intended to give you a quick idea of how to use the drawing calls. Complex drawings are typically built from lots of "primitive" drawing commands. For example, if you want to draw the surface of a rectangular grid, you may want to break the surface in to (flat) triangles. To accomplish this, you have to first tell the library that you are sending it triangle vertices, and when you are finished you call glEnd to tell the library you are no longer sending vertices:

glBegin(GL_TRIANGLES);
glDouble v1[3];
glDouble v2[3];
glDouble v3[3];
glDouble x, y, z;

for (int row = 0; row < (GRID_ROWS-1); row++){
  for (int col = 0; col < (GRID_COLUMNS-1); col++){
    // get some x, y, and z values

    x = (glDouble)row;
    y = (glDouble)col;
    z = (glDouble)((row%2) + (col%3));
    v1[0] = x; v1[2] = y; v1[1] = z;

    glVertex3dv(v1);
    z = (glDouble)((row%3) + (col%2));
    v2[0] = x; v2[2] = y + 1; v2[1] = z;

    glVertex3dv(v2);

    z = (glDouble)((row%4) + (col%3));
    v3[0] = x+1; v3[2] = y+1; v3[1] = z;

    glVertex3dv(v3);

    z = (glDouble)((row%3) + (col%2));
    v1[0] = x; v1[2] = y; v1[1] = z;

    glVertex3dv(v1);
    z = (glDouble)((row%3) + (col%4));
    v2[0] = x + 1; v2[2] = y; v2[1] = z;

    glVertex3dv(v2);
    z = (glDouble)((row%2) + (col%3));
    v3[0] = x+1; v3[2] = y+1; v3[1] = z;

    glVertex3dv(v3);

  }

}

glEnd();

The same approach is used for drawing lines, for example:

glBegin(GL_LINES);
// send the API pairs of vertices by calling glVertex3dv(v)
// as many times as necessary
glEnd();

A slightly different approach is necessary for drawing polygons because there is no way for the API to know ahead of time how many sides the polygons are going to have. So, you have to nest the glBegin(GL_POLYGON) and glEnd() within the polygon loop.

Also, the API uses a naming convention for its function calls that helps programmers keep the parameters straight. For example, the call to glVertex3dv actually has many variations:

  • glVertex3fv—passing in 3 glFloat types
  • glVertex3di—passing in 3 glInt types
  • glVertex3dv—passing in 3 glDouble types...and so on

Render the DIB to the screen or to the printer:

Create a CClientDC (MFC) for the window you wish to show the graphics in and transfer the image to the window using bitblt. In the sample project, this is accomplished by using the DIBSection object.

The following code snippet (from the sample project) shows some of the implementation of the OpenGL view class.

GLView3D::GLView3D(void)
{
  InitImage();
}

GLView3D::~GLView3D(void)
{
}

void GLView3D::CreateImage(UINT32 width, UINT32 height)
{
  if ((width > 0) && (height > 0))
  {
    m_dib.Create(width,height,32);
    if (!IsInitialized())
    {
      InitializeOpenGL();
      if (CreateView())
      {
        m_IsInitialized = 1;
      }
    }
  }
}

void GLView3D::SizeImage(UINT32 width, UINT32 height)
{
  if ((width > 0) && (height > 0))
  {
    if (!IsInitialized())
    {
      CreateImage(width,height);
    }
    else
    {
      if (!m_dib.IsCreated())
      {
        m_dib.Create(width,height,32);

        if (SetImagePixelFormat(*m_dib.GetDC()))
        {
          CreateImageGLContext(*m_dib.GetDC());
        }
      }
      else if ((width != m_dib.Width()) ||
               (height != m_dib.Height()))
      {
        m_dib.Create(width,height,32);
        CreateView();
      }
    }

    glViewport(0,0,width,height);
    double aspect;
    aspect = (height == 0) ? (double)width :
             (double)width/(double)height;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45,aspect,0.01,25.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  }
}

void GLView3D::InitializeOpenGL(void)
{
  m_hGLContext = NULL;
  m_GLPixelIndex = 0;
  InitGeometry();
}

void GLView3D::InitGeometry(void)
{
  m_model_matrix.SetRotation(45.0,0.0,0.0);
  m_model_matrix.SetScale(1.0,1.0,1.0);
  m_model_matrix.SetPosition(0.0,0.0,-5.0);
}


// Lights, camera, action...
int GLView3D::CreateView(void)
{
  int result = 0;

  // do the standard OpenGL initialization
  if(SetImagePixelFormat(*m_dib.GetDC()) &&
     CreateImageGLContext(*m_dib.GetDC()))
    result = 1;
  else
    return result;

  glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_NORMALIZE);
  glEnable(GL_COLOR_MATERIAL);

  // Lights, material properties
  GLfloat ambientProperties[] = {0.3f, 0.3f, 0.3f, 0.7f};
  GLfloat diffuseProperties[] = {0.9f, 0.9f, 0.9f, 1.0f};
  GLfloat specularProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};
  GLfloat mat_amb_diff[] = { 0.7f, 0.7f, 0.7f, 1.0f};
  GLfloat shine[] = { 128.0f};
  GLfloat emissionProperties[] = {0.2f, 0.2f, 0.2f, 1.0f};
   
  glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);
  glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);
  glLightfv( GL_LIGHT0, GL_SPECULAR, specularProperties);

  glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1.0);

  // lighting
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);

  return result;
}

BOOL GLView3D::SetImagePixelFormat(HDC hDC)
{
  PIXELFORMATDESCRIPTOR pixelDesc;

  pixelDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
  pixelDesc.nVersion = 1;

  pixelDesc.dwFlags = PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL |
    PFD_SUPPORT_GDI;

  pixelDesc.iPixelType = PFD_TYPE_RGBA;
  pixelDesc.cColorBits = 32;
  pixelDesc.cRedBits = 0;
  pixelDesc.cRedShift = 0;
  pixelDesc.cGreenBits = 0;
  pixelDesc.cGreenShift = 0;
  pixelDesc.cBlueBits = 0;
  pixelDesc.cBlueShift = 0;
  pixelDesc.cAlphaBits = 0;
  pixelDesc.cAlphaShift = 0;
  pixelDesc.cAccumBits = 0;
  pixelDesc.cAccumRedBits = 0;
  pixelDesc.cAccumGreenBits = 0;
  pixelDesc.cAccumBlueBits = 0;
  pixelDesc.cAccumAlphaBits = 0;
  pixelDesc.cDepthBits = 32;
  pixelDesc.cStencilBits = 0;
  pixelDesc.cAuxBuffers = 0;
  pixelDesc.iLayerType = PFD_MAIN_PLANE;
  pixelDesc.bReserved = 0;
  pixelDesc.dwLayerMask = 0;
  pixelDesc.dwVisibleMask = 0;
  pixelDesc.dwDamageMask = 0;

  int pix_index = ChoosePixelFormat(hDC,&pixelDesc);

  if(!SetPixelFormat(hDC,pix_index,&pixelDesc))
  {
    DWORD code = GetLastError();
    return FALSE;
  }

  return TRUE;
}

BOOL GLView3D::CreateImageGLContext(HDC hDC)
{
  m_hGLContext = wglCreateContext(hDC);

  if(m_hGLContext==NULL)
    return FALSE;

  if(wglMakeCurrent(hDC,m_hGLContext)==FALSE)
    return FALSE;

  return TRUE;
}

void GLView3D::RenderImage(void)
{
  if (m_IsInitialized && m_dib.IsCreated())
  {
    glClearColor((float)1.00,(float)1.00,
      (float)1.00,1.0f);

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // put a working copy on the matrix stack
    glPushMatrix();

    // Position / translation / scale
    glTranslated(m_model_matrix.X(),
                 m_model_matrix.Y(),
                 m_model_matrix.Z());
    glRotated(m_model_matrix.XRot(), 1.0, 0.0, 0.0);
    glRotated(m_model_matrix.YRot(), 0.0, 1.0, 0.0);
    glRotated(m_model_matrix.ZRot(), 0.0, 0.0, 1.0);
    glScaled(m_model_matrix.XScale(),
             m_model_matrix.YScale(),
             m_model_matrix.ZScale());

    // build the scene...if necessary
    RenderPrimaryImage();

    glFlush();

    // pop the working copy of the matrix stack
    glPopMatrix();
  }
}

void GLView3D::RenderPrimaryImage(void)
{
  glColor3ub(128,128,128);
  GLUquadricObj * q = gluNewQuadric();
  gluQuadricDrawStyle(q, GLU_LINE);
  gluQuadricNormals(q, GLU_SMOOTH);
  gluSphere(q,1.0,16,16);
  gluDeleteQuadric(q);

  GLdouble v1[3], v2[3];

  glBegin(GL_LINES);
  v1[0] = v1[1] = v1[2] = 0.0;
  v2[0] = 1.0; v2[1] = v2[2] = 0.0;
  glColor3ub(224,0,0);
  glVertex3dv(v1);
  glVertex3dv(v2);
  v2[0] = 0.0; v2[1] = 1.0; v2[2] = 0.0;
  glColor3ub(0,224,0);
  glVertex3dv(v1);
  glVertex3dv(v2);
  v2[0] = 0.0; v2[1] = 0.0; v2[2] = 1.0;
  glColor3ub(0,0,224);
  glVertex3dv(v1);
  glVertex3dv(v2);
  glEnd();
}

void GLView3D::DrawImage(CWnd * wnd)
{
  CClientDC dc(wnd);
  m_dib.Draw(&dc,0,0);
}

Some Information about the Sample Project

This article is NOT intended to be the final word on OpenGL programming. You could probably spend a career on OpenGL and 3D programming and still not know everything.

The sample project has printing code within the view class's "OnDraw" method.

Enjoy.

Downloads

Download demo project - 179 Kb



About the Author

Andy McGovern

Andy McGovern - Software Developer/Engineer. Special interests: astronomy, image processing, orbital mechanics, mathematics. Currently work at the Johns Hopkins University Applied Physics Laboratory on the science operations team for the CRISM instrument on the Mars Reconnaissance Orbiter.

Comments

  • Can't use hardware acceleration

    Posted by mforce on 12/11/2008 12:17am

    This demo can use Software Rendering(GDI generic) only How can I use a hardware acceleration? I think , it's little weird that can't use hardware acceleration on the buffer PLZ help me T_T

    Reply
  • How to print a view containing both openGL and GDI drawings?

    Posted by RaviStrs on 11/09/2006 01:28am

    Could you please spend some time to look at my query? Along with OpenGL drawing, my application view have some other drawings on screen (pDC->LineTo,Rectangle etc..)to show some legends on screen. On printing I need to add the GDI drawings also to the existing DIB. I am kindly requesting you to suggest me some guide line to do that? I have used GDI commands in OnDraw() function of view class. Thank you. Ravikumar

    • Drawing and Printing After Rendering from OpenGL

      Posted by andyf4i on 11/11/2006 03:46pm

      hi Ravi, Its really very simple. In your OnDraw method just render the 3D graphics to the DIB like in the article, then add your additional drawing code...but when you draw your graphics use the device context from the DIB class (i.e. call GetDC() on the m_dib object), then output the DIB to the screen just like the example. Hope this helps. Andy

      Reply
    Reply
  • I like it

    Posted by Deniz on 02/23/2005 05:05pm

    The article is NOT intended to be the final word on OpenGL programming but it is a nice simple to understand example. Thanks for your time.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • A global data storage provider whose business is booming needed a best-in-class data center to serve as the backbone of its technical operations going forward—and it needed it delivered within a year.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds