MFC
Børre Stenseth
Utviklingsverktøy>OpenGL og MFC

OpenGL og MFC

Hva
image
Standardoppsett for OpenGL brukt sammen med MFC, Microsoft Foundation Classes

Avsnittet Minimumsprogrammet går gjennom bygging av en et program helt fra bunnen av. Dette programmet er ikke spesielt anvendbart som plattform å bygge videre på, men er tatt med for å forklare grunnlaget.

Avsnittet Standardprogrammet beskriver et program som du kan bruke som utgangspunkt for dine egne programmer. Kopier det og bygg videre etter de anvisningene som er beskrevet.

Merk at dette standardprogrammet er bygget over den lesten Microsoft brukte for å realisere MVC (Model View Controller). Det betyr at skjelettkoden kan virke noe overdimensjonert (spurv med kanoner). Det er imidlertid et bevisst valg for lett å kunne implementere ulike vindusløsninger.

Minimumsprogrammet

Vi vil først se på hvordan vi lager et minimums utgangspunkt i C++ ved hjelp av Application Wizard i Visual Studio (VC++, versjon 5 eller 6). Vi ønsker å lage programmet tst under katalogen c:\grafisk.

Kortformen av dette er:
Opprett den aktuelle katalogen og la App Wizard lage en single document application uten databasekopling og uten OLE-egenskaper. Kutt ut toolbars og statusfelt. Lag programmet slik at det baserer seg på MFC som DLL.

Steg for steg blir det slik:

  1. Lag katalogen c:\grafisk
  2. Start Microsoft Developers Studio
  3. Velg New fra File-menyen. Det kommer opp en dialogboks som spør hva du vil lage. Velg: ProjectWorkspace.
  4. Du får opp en ny dialogboks med en rekke muligheter. Velg MFC AppWizard(exe). Det betyr at du ber Wizarden lede deg gjennom noen valg for å lage et komplett Windowsprogram, en .exe-fil.
  5. Du må videre angi hva du skal kalle programmet. Bruk Browse muligheten til å finne fram den aktuelle katalogen: c:\grafisk. Vårt program skal hete heter tst og vi skriver inn det i Name-feltet.
  6. Trykk så Create for å komme videre.
  7. Nå overtar Wizarden og leder oss gjennom en rekke valg. Det Første steget i Wizardens spørsmålsserie er om vi vil ha Single eller Multiple document. Vi velger Single, og rører ikke noen av de andre valgene i dialogboksen. Trykk Next.
  8. Neste beslutning gjelder databaser. Vi kvitterer med None og trykker Next.
  9. Neste beslutning gjelder OLE. Vi sier None igjen og går videre med Next.
  10. Neste dialogboks stiller flere spørsmål. Vi skrur av alt untatt 3D-Controls, og går videre, Next.
  11. Neste dialogboks konfronterer oss med to problemstillinger. Den første gjelder kommentarer i den genereret koden. Kanskje kan det være greitt med Yes,Please første gangen, men koden blir mer oversiktlige med No, Thank You. Det andre temaet gjelder hvordan vi skal benytte MFC. Velg As a shared DLL. Dette innbærer at programmet vårt blir lite og kompakt, men det forutsetter at den aktuelle DLLen er tilstede når programmet kjører. Vi prioriterer liten exe-fil, siden programmet vårt neppe skal distribueres og kjøres på uforberedte maskiner. Next.
  12. Den nest siste dialogboksen Wizarden presenterer oss for er forslag til navn på de aktuelle objektene. Vi sier at dette er OK og trykker Finish.
  13. Den siste dialogboksen summerer opp våre valg og gir oss en siste sjanse til å angre hele prosjektet. Det gjør vi ikke, og kjører på med OK.

Nå genereres komplett kode og vi kan kompilere og kjøre. Det ligger utenfor rammene av dette materialet å gå nærmere inn å funksjonaliteten i Visual Studio.

Programmet vi har laget har en del ressurser som vi ikke trenger. Det eneste vi plukker vekk er noen av menyene, slik at funksjonaliteten står i forhold til de menyene brukeren ser. Vi kan ta vekk hele Edit-menyen. Videre kan vi redusere File-menyen slik at vi bare sitter igjen med Exit. Dersom vi ønsker å signere programmet, kan vi gå inn på About- dialogboksen og endre teksten her.

Biblioteker

Resultatet av operasjonene våre ovenfor er et minimumsprogram som vi må bearbeide videre for at vi skal kunne kjøre OpenGL. Det første er å gjøre OpenGL bibliotekene tilgjengelig. Det betyr to ting:

  1. Vi må gjøre selve bibliotekene, lib-filene tilgjengelige i applikasjonen. Dette gjør vi ved: Project / Add To project / files. De to aktuelle bibliotekene er: Opengl32.lib og Glu32.lib. Begge ligger i katalogen Program Files/DevStudio/vc/lib ved standard installasjon av Visual C++ for Developers Studio eller Program Files/Microsoft Visual Studio/Vc98/lib ved standard installasjon av Visual C++ for Visual Studio
  2. Vi må inkludere headerfilene til de to bibliotekene. Siden vårt enkle standardprogram kun skal referere OpenGL i CTstView-fila, kunne vi godt inkludere de aktuelle filene der, men for generalitetens skyld legger vi inn:

    
    #include "gl/gl.h"
    #include "gl/glu.h"
    
    

    bakerst i fila stdAfx.h som er en av de filene som alltid genereres som del av et prosjekt. Det er to fordeler med å inkludere headerfiler her. For det første er de enkelt tilgjengelig for alle filer i prosjektet og for det andre drar vi automatisk fordel av mulighetene for prekompilerte headere.

Meldinger og funksjoner

Nå må vi rette fokus på endringer i selve koden. I vår enkle applikasjonen skal vi endre kode kun i CtstView. Alle andre filer, untatt StdAfx.h som vi justerte ovenfor, forblir uendret fra det som ble generert automatisk.

Før vi går løs på de enkelte rutinene vil jeg bare minne om sammenhengen mellom Microsoft Foundation Classes og den grunnleggende Windows APIen. Alle objekter som er av typen CWnd eller avledede klasser (og det er vårt CTstView) har en medlemsvariabel av typen HWND (handle-to-window) som heter m_hWnd. Når vi skaper en ny forekomst av klassen CTstView kalles konstruktoren CTstView() på vanlig C++ måte. På dette tidspuktet er ikke klassen tilordnet noen vindusstruktur, dvs. m_hWnd er meningsløs. Det er først når CTstView objektet vårt får meldinga WM_CREATE at det skjer en tilordning til en vindusstruktur. Derfor kan vi ikke gjøre noe med vindusstrukturens egenskaper eller Device Context i konstruktoren, vi må vente til PreCreateWindow og OnCreate. Se dokumentasjon av disse rutinene.

På samme måte er det med OnDestroy og destruktoren. Ved destruksjon (~CTstView())er vindusstrukturen, m_hWnd, allerede ødelagt.

PreCreateWindow

PreCreateWindow er en medlemsfunksjon som kalles før CTstView objektet får tilordnet seg en vindusstruktur, m_hWnd. Funksjonen skal gi oss mulighet for å påvirke hva slags vindusstruktur vi ønsker. OpenGL forutsetter at alle vinduer vi skal tegne i har egenskapene WS_CLIPSIBLINGS og WS_CLIPCHILDREN. Vi modifiserer beskrivelsen av vinduet og får:

BOOL CTstView::PreCreateWindow(CREATESTRUCT& cs)
{
  // necessary in all OpenGL-windows:
  cs.style|=WS_CLIPSIBLINGS|WS_CLIPCHILDREN;
  return CView::PreCreateWindow(cs);
}

OnCreate

Vi benytter muligheten når vinduet skapes til å etablere en tegneomgivelse, rendering contekst. Vi må lage en meldingshandler for å plukke opp meldinga WM_CREATE. Det gjør vi i Class Wizard, og vi skriver den slik:

int CTstView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  if (CView::OnCreate(lpCreateStruct) == -1)
    return -1;
  CClientDC* pDC = new CClientDC(this);
  HDC hdc=pDC->GetSafeHdc();
  ASSERT(pDC != NULL);
  static PIXELFORMATDESCRIPTOR pfd =
  {
    // this is what we want
    sizeof(PIXELFORMATDESCRIPTOR),
    1,      // version number
    PFD_DRAW_TO_WINDOW |  // support window
    PFD_SUPPORT_OPENGL |  // support OpenGL
    PFD_DOUBLEBUFFER,     // double buffered
    PFD_TYPE_RGBA,        // RGBA type
    24,                   // 24-bit color depth
    0, 0, 0, 0, 0, 0,     // color bits ignored
    0,                    // no alpha buffer
    0,                    // shift bit ignored
    0,                    // no accumulation buffer
    0, 0, 0, 0,           // accum bits ignored
    32,                   // 32-bit z-buffer
    0,                    // no stencil buffer
    0,                    // no auxiliary buffer
    PFD_MAIN_PLANE,       // main layer
    0,                    // reserved
    0, 0, 0               // layer masks ignored
  };
  int nPixelFormat;
  // negotiate it
  nPixelFormat = ChoosePixelFormat(hdc,&pfd);
  SetPixelFormat(hdc,nPixelFormat,&pfd);
  // do the best we can
  DescribePixelFormat(hdc,nPixelFormat,
      sizeof(PIXELFORMATDESCRIPTOR),&pfd);

  HGLRC hRC=wglCreateContext(hdc);
  // keep it current in the windows lifetime
  wglMakeCurrent(hdc,hRC);
  // call our function to do one-time initializing
  InitializeRContext();
  return 0;
}

OpenGL fordrer som det framgår av koden en ganske detaljert beskrivelse av hvordan tegneomgivelsene skal være. Vi må spesifisere detaljert de betingelsene som skal ligge til grunn for beregningene OpenGL skal foreta når hvert pixel i tegnebufferet skal settes.

Merk at vi lager en HGLRC (Handle to GL Rendering Context) lokalt. Det kan vi gjøre bare dersom vi skal beholde en og samme tegneomgivelse i hele programmet. I vårt enkle program er det ok. Vi ser to eksempler på wgl-funksjoner (wglCreateContext ,wglMakeCurrent ), altså MS Windows spesifikke funksjoner. I dette tilfellet funksjoner som hhv. skaper en OpenGL Rendering Context og binder denne til en MS-Windows Drawing Context.

InitializeRContext() er en rutine som vi skriver selv, se nedenfor.

OnDestroy

Vi bruker meldinga WM_DESTROY til å plukke ned igjen det vi etablerte under WM_CREATE

void CTstView::OnDestroy()
{
  HGLRC hrc = wglGetCurrentContext();
  // detach it
  wglMakeCurrent(NULL, NULL);
  // delete it
  if (hrc)
    wglDeleteContext(hrc);
  CView::OnDestroy();
}

OnSize

Når vi skalerer vinduet sendes en WM_SIZE melding til vinduet. Vi ønsker å plukke opp denne og sørge for at vår tegning alltid dekker hele innmaten i vinduet, vinduets Client Area. Vi gjør dette slik:

void CTstView::OnSize(UINT nType, int cx, int cy)
{
  CView::OnSize(nType, cx, cy);
  // the whole clientarea
  glViewport(0, 0, cx, cy);
  // work on the projection phase
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(70.0f, (GLdouble)cx/cy, 0.1f, 70.0f);
  //glOrtho(-2.0f,2.0f,-2.0f,2.0f,-10.f,10.0f);
  // work on model and view
  glMatrixMode(GL_MODELVIEW);
}

Her er det et par viktige ting å merke seg. Vi kaller glViewport med parametre som dekker hele klientområdet siden cx og cy er størrelsen i pixler av vinduets innmat etter skaleringen.

Vi ser at vi endrer matrise-modus, glMatrixMode, to ganger. Først setter vi modus til GL_PROJECTION. Det betyr at vi angir at den matrisen som skal bearbeides i det følgende er den matrisen som har med projeksjonen fra View-koordinatsystemet og ned på skjermen å gjøre. Deretter endrer vi modus (tilbake) til GL_MODELVIEW. Det betyr at vi sier at fra nå av vil matrise operasjoner ha med den matrisen å gjøre som bearbeider modellen og overgangen fra modellkoordinater til Viewkoordinater. Se den rekkefølgen som er beskrevt i modulen: Modell til skjerm.

Se også at vi har brukt en perspektivprojeksjon, gluPerspective(), og har kommentert ut en parallellprojeksjon, glOrtho().

OnDraw

Vår respons på et behov for å tegne ut innholdet i CtstView:

void CTstView::OnDraw(CDC* pDC)
{
  // My std name on the actual drawing func:
  DrawScene();
  // Get it on the screen
  SwapBuffers(wglGetCurrentDC());
}

Vi har standardisert på å gjøre all tegning i en egen rutine som vi har kalt DrawScene(). Når rendring er gjort i bakgrunnsbufferen provoserer vi fram en oppdatering av skjermen, en bitblt, ved hjelp av SwapBuffers().

OnEraseBkgnd

Det kan være hensiktsmessig å modifisere CTstViews reaksjon på WM_ERASEBKGND. Vi gjør det slik at vi aldri får blanket vinduet. En blanking før neste uttegning fører til flikker ved eventuelle animasjoner eller oppdateringer basert på interaktive handlinger. Den enkleste måten å gjøre dette på er:

BOOL CTstView::OnEraseBkgnd(CDC* pDC)
{
  return TRUE;
}

Vi "lurer" systemet til å tro at blankingen er gjort. Vi kan trygt gjøre det slik så lenge vi alltid tegner ut i hele klientområdet.

DrawScene

Her kommer den egentlige uttegningen.

void CTstView::DrawScene()
{
  // Clear the color and depth buffers.
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  // work on model and view
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  // position the eye relative to scene
  gluLookAt(  4.0f,0.0f,0.0f,  // eyepos
              0.0f,0.0f,0.0f,  // looking at
              0.0f,0.0f,1.0f); // is up
  // set material kind of yellow, no shiny
  GLfloat mat_ambient[] = { 0.7f, 0.7f, 0.7f, 1.0f };
  GLfloat mat_diffuse[] = { 0.8f, 0.8f, 0.0f, 1.0f };
  glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
  // use a quadric to draw a sphere
  GLUquadricObj *glpQ;
  glpQ=gluNewQuadric();
  gluSphere(glpQ,1,20,20);
  gluDeleteQuadric(glpQ);
  // finnish drawing
  glFlush();
}

Her foregår det mange ting. Vi begynner med å klargjøre for ny uttegning ved å blanke to buffere, fargebuffer og dybdebuffer. Det betyr at vi setter bakgrunnsfarge, se spesifikasjon nedenfor, og vi angir at all foran og bak beregning skal startes på nytt.

Så bruker vi en glu-rutine som bestemmer øye, hvilket punkt vi ser mot og hva som skal være opp.

Vi spesifiserer så et matt, gult materiale og tegner ut en kule ved hjelp av en OpenGL-mekanisme som kalles quadric. Vi drøfter ikke dette nærmere her.

InitializeRContext

Denne funksjonen er laget for å ta seg av OpenGL-spesifikasjoner som bare skal gjøres en gang i programmets levetid.

void CTstView::InitializeRContext()
{
  // Here we set the attributes that will be constant
  // during the Rendering contexts lifetime
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);
  // prepare ligthsource
  GLfloat ambient[] = {0.2f,0.2f,0.2f,1.0f };
  GLfloat diffuse[] = {1.0f,1.0f,1.0f,1.0f };
  GLfloat position[] = {200.0f,300.0f,100.0f,0.0f };
  GLfloat lmodel_ambient[] = {0.4f,0.4f,0.4f,1.0f };
  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
  glLightfv(GL_LIGHT0, GL_POSITION, position);
    glEnable(GL_LIGHT0);
  glEnable(GL_LIGHTING);
  // set background to deep blue
  glClearColor(0.0f, 0.0f, 0.4f, 0.0f);
}

Det første vi gjør er å angi at vi skal arbeide med dybdeberegninger. Videre spesifiserer vi en lyskilde og skrur på lyset. Til slutt angir vi at bakgrunnsfargen i bildet skal være dyp blå.

Standardprogrammet

Det komplette programmet som utvikles i dette avsnittet er tilgjengelig som std, se ref. Det anbefales å bruke en kopi av dette som utgangspunkt for egne eksperimenter med OpenGL programmering. I så fall kan kodingen rettes mot en modifisering av CStdView. Det er ikke nødvendig å røre koden i COGLView.

Når vi skal lage enkle OpenGL-programmer kan det være godt å slippe så mye kode i CxxView-fila. Vi ønsker å rasjonalisere noe av koden ovenfor. Det er alltid noen vanskelige avveininger om hvor mye som skal skjules. På den ene siden ønsker vi greie og enkle løsninger på enkle problemer og på den annen side ønsker vi mulighet for fleksibilitet og smidige mekanismer for tilpassing til ikke-triviell programmeringsoppgaver. Den løsningen som foreslås her er basert på at standardapplikasjonen har et view som er subklasset fra et standardisert view som er preparert for OpenGL, kalt COGLView.

image

Figuren ovenfor viser de viktigste klassene i en single dokument application. I forhold til den enkle applikasjonen som App Wizard genererer, er den eneste endringen at vi har skutt inn COGLView som baseklasse for CStdView.

COGLView har i hovedsak den funksjonaliteten som er beskrevet ovenfor for tst-applikasjonens view.

COGLView har følgende medlemsvariable:


  // a dc
  CClientDC* m_pDC;
  HDC m_hDC;

  // rendering context handle
  HGLRC m_hRC;

De meldingene som plukkes opp i COGLView er følgende:


  afx_msg void OnDestroy();
  afx_msg void OnSize(UINT nType, int cx, int cy);
  afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
  afx_msg BOOL OnEraseBkgnd(CDC* pDC);

Disse kan, dersom det er ønskelig, plukkes opp i CStdView og eventuelt rutes videre til baseklassen. I tillegg er følgende virtuelle funksjoner definerte:


  virtual void SetUpDrawingEnvironment();
  virtual void InitializeRContext();
  virtual void SetPerspective(int cx, int cy);
  virtual void DrawScene();

Det minste vi må gjøre med CStdView for å få programmet til å tegne noe annet enn en kule, er å redefinere DrawScene().

Meldingshandtererne og funksjonene i COGLView er definert som angitt nedenfor:

OnCreate Setter opp Device Context og Rendering Context for vinduet, og trengs normalt ikke redefineres
int COGLView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  // sets up the drawing environment and do
  // one time initialization of OGL-settings

  if (CView::OnCreate(lpCreateStruct) == -1)
    return -1;

  m_pDC = new CClientDC(this);
  m_hDC=m_pDC->GetSafeHdc();
  ASSERT(m_hDC != NULL);

  // Set up the drawing environment
  SetUpDrawingEnvironment();
  return 0;
}
   
OnDestroy Plukker ned Device Context og Rendering Context og trengs normalt ikke redefineres
void COGLView::OnDestroy()
{
  // only one dynamic variable to release
  if(m_pDC!=NULL)
    delete m_pDC;
  CView::OnDestroy();
}
   
OnSize Kaller rutina SetPerspective() for den aktuelle jobben, og trengs normalt ikke redefineres
void COGLView::OnSize(UINT nType, int cx, int cy)
{
  // Called whenever the size of the view changes
  // We must set the viewport in the rendering
  // context accordingly. The proper rendering context
  // is set and released.This must be done the same way
  // in subclasses. No OpenGL-action must take place if we
  // have not attached the correct rendering context
  // Since SetPerspective() is wrapped with the
  // proper rendering context set, we dont have to
  // tamper with the rendering context is redefinitions of
  // SetPerspective()

  CView::OnSize(nType, cx, cy);
  wglMakeCurrent(m_hDC,m_hRC);
  // use the whole clientarea
  glViewport(0, 0, cx, cy);
  SetPerspective(cx,cy);
  wglMakeCurrent(0,0);
}
   
OnEraseBkgnd Korslutter blanking av vinduet, og trenger ikke redefineres
BOOL COGLView::OnEraseBkgnd(CDC* pDC)
{
  // To avoid flicker. This assures that
  // the clientarea is not blanked before a redraw starts
  // Ok as long as we draw in the whole clientarea
  return TRUE;
}
   
PreCreateWindow Preparerer vinduet, trenger ikke redefineres
BOOL COGLView::PreCreateWindow(CREATESTRUCT& cs)
{
  // all OpenGL windows must have this window-style
  cs.style|=WS_CLIPSIBLINGS|WS_CLIPCHILDREN;
  return CView::PreCreateWindow(cs);
}
   
SetUpDrawingEnvironment Setter opp spesifikasjonene for OpenGL's omgivelser, og trengs sjelden redefineres. Ingen av applikasjonene i dette materialet har redefinert denne funksjonen.
void COGLView::SetUpDrawingEnvironment()
{
  // This function must be changed if you want
  // to bargain for other specs than those
  // specified as initial values to
  // PIXELFORMATDESCRIPTOR below

   static PIXELFORMATDESCRIPTOR pfd =
  {
        sizeof(PIXELFORMATDESCRIPTOR),  // size of this pfd
        1,                              // version number
        PFD_DRAW_TO_WINDOW |            // support window
        PFD_SUPPORT_OPENGL |            // support OpenGL
        PFD_DOUBLEBUFFER,               // double buffered
        PFD_TYPE_RGBA,                  // RGBA type
        24,                             // 24-bit color depth
        0, 0, 0, 0, 0, 0,               // color bits ignored
        0,                              // no alpha buffer
        0,                              // shift bit ignored
        0,                              // no accumulation buffer
        0, 0, 0, 0,                     // accum bits ignored
        32,                             // 32-bit z-buffer
        0,                              // no stencil buffer
        0,                              // no auxiliary buffer
        PFD_MAIN_PLANE,                 // main layer
        0,                              // reserved
        0, 0, 0                         // layer masks ignored
    };

   int nPixelFormat;

  nPixelFormat = ChoosePixelFormat(m_hDC, &pfd);
  SetPixelFormat(m_hDC, nPixelFormat, &pfd);

  DescribePixelFormat(m_hDC,nPixelFormat,
    sizeof(PIXELFORMATDESCRIPTOR),&pfd);
  if(pfd.dwFlags & PFD_NEED_PALETTE)
    AfxMessageBox("Colors will not display properly...");

  // create rendering context associated with  windows dc
  m_hRC=wglCreateContext(m_hDC);
  ASSERT(m_hRC!=NULL);
  // make the rendering context current while
  wglMakeCurrent(m_hDC,m_hRC);
  // we do one-time initializing
  InitializeRContext();
  // release the rendering context
  wglMakeCurrent(0,0);
}
   
DrawScene Må alltid redefineres
void COGLView::DrawScene()
{
  // you will allways want to redefine this
  // in your subclass
  // Here we assume that the
  // one and only rendering context is selected
  // If it for some reason is not, this must be
  // done before we do any OpenGL-action

  // Clear the color and depth buffers.
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // Set up for scene
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  // position the eye relative to scene
  gluLookAt(  4.0f,0.0f,0.0f,  // eye
      0.0f,0.0f,0.0f,  // looking at
      0.0f,0.0f,1.0f);// is up

  // setMaterial kind of yellow
      GLfloat mat_ambient[] = { 0.7f, 0.7f, 0.7f, 1.0f };
      GLfloat mat_diffuse[] = { 0.8f, 0.8f, 0.0f, 1.0f };
  glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);

  // use a quadric to draw a circle
  GLUquadricObj *glpQ;
  glpQ=gluNewQuadric();
  gluSphere(glpQ,1,20,20);
  gluDeleteQuadric(glpQ);

  // finnish drawing
  glFlush();
}
   
InitializeRContext Gjør en gangs operasjoner i OpenGL. Må nesten alltid redefineres
void COGLView::InitializeRContext()
{
  // Here we set the attributes that probably
  // will be constant during the rendering
  // contexts lifetime.

  // you may or may not want this function
  // if you dont need it, simply redefine an empty
  // function in your subclass

      glEnable(GL_DEPTH_TEST);
      glDepthFunc(GL_LESS);

  // prepare ligthsource
  GLfloat ambient[] = {0.2f,0.2f,0.2f,1.0f };
  GLfloat diffuse[] = {1.0f,1.0f,1.0f,1.0f };
  GLfloat position[] = {20.0f,30.0f,10.0f,0.0f };
  GLfloat lmodel_ambient[] = {0.4f,0.4f,0.4f,1.0f };

  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
  glLightfv(GL_LIGHT0, GL_POSITION, position);

  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHTING);

  // smooth the drawing
  glShadeModel(GL_SMOOTH);

  // set background to light gray
  glClearColor(0.8f, 0.8f, 0.8f, 0.0f);
}
   
SetPerspective Setter opp kameraspesifikasjoner. Må normalt redefineres
void  COGLView::SetPerspective(int cx, int cy)
{
  // We will work on the projection matrix,
  // so set matrix mode
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  // set up your viewing volume
  // gluPerspective:  pyramide
  gluPerspective(70.0f, (GLdouble)cx/cy, 0.1f, 70.0f);

  // Alternative: glOrtho:box, parallell projection
  // glOrtho(-2.0f,2.0f,-2.0f*cy/cx,2.0f*cy/cx,-10.f,10.0f);
  // NOTE: want x/y-ratio to match viewports x/y-ratio

  // set back matrix mode
  glMatrixMode(GL_MODELVIEW);
}
   

Merk følgende: COGLView er designet slik at den skal kunne fungere som superklasse for vinduer i et et flervinduprogram (MDI-application). For at dette skal fungere må kallene:


  wglMakeCurrent(m_hDC,m_hRC);
  wglMakeCurrent(0,0);

alltid omslutte alle kall på OpenGL-rutiner (gl og glu). I et enkelt, standard vindu der vi redefinerer en eller flere av følgende:


  virtual void DrawScene();
  virtual void InitializeRContext();
  virtual void SetUpDrawingEnvironment();
  virtual void SetPerspective(int cx, int cy);

trenger vi ikke tenke på dette. Dersom vi imidlertid kaller OpenGL-rutiner i andre sammenhenger må vi huske wglMakeCurrent. Et typisk eksempel er rutina PickObject() som er beskrevet i modulen Identifikasjon.

Standardprogrammet bruker en klasse CMaterial for enkelt å kunne angi materialegenskaper når vi tegner. . Vi finner


#include "mater.h"

i stdafx.h - fila. Dessuten er det lagt inn en dialog-boks blant ressursene. Denne dialogboksen, IDD_MULTI, brukes av CMaterial dersom vi vil tilby brukeren en dialog for å velge materiale.

Palette strategi

Dersom vi har for svak hardware får vi problemer med fargeangivelsen når vi tegner med OpenGL. Det er selvsagt ganske krevende for fargemulighetene når vi skal tegne med skygger og en masse fargenyanser, slik vi gjør i OpenGL. I praksis er det lite interessant å bruke OpenGL på 8-bits fargesystemer; 256 samtidige farger gir ingen god løsning.

Dersom det likevel er ønskelig å forberede programmer for en slik situasjon må vi innføre administrasjon av paletter i programmet.

Vi går ikke i detalj om koden. Grovt sett kan vi si at en palettstrategi innebærer følgende betraktninger.

  • Vi må identifisere 8-bits situasjonen. Dette skjer typisk i funksjonen SetUpDrawingEnvironment().
  • Vi må lage, og endre, en palette som passer til vårt fargebehov.
  • Vi må ha en strategi for å realisere vår palette når vårt program får fokus. For å få til dette må vi plukke opp og behandle meldingene :

WM_PALETTECHANGED
WM_QUERYNEWPALETTE

Studer Windows dokumentasjonen for å finne ut mer om dette.

Flere vinduer

De to applikasjonene vi har sett på i denne modulen er single document standard applikasjoner. Det er ingentig i veien for å bruke OpenGL i applikasjoner med flere views og vi kan godt bruke OpenGL i dialogbokser og "løse" vinduer etter behov.

Nøkkelen til dette er å skille OpenGLs operasjoner i de forskjellige vinduene fra hverandre ved å gi vinduene hver sin rendering context, og å sørge for å sette denne riktig hver gang en OpenGL-rutine blir kalt.

Navnekonvensjoner

Det er flere grupper rutiner i OpenGL som det er greitt å skille fra hverandre:

  • gl-funksjonene er kjernen i OpenGL og er plattformuavhengige. Se /include/gl/gl.h i VC installasjonen.
  • glu-funksjonene er basert utelukkende ( i hvert fall logisk) på gl-funksjonene og er derfor også plattformuavhengige. glu-funksjonene er innført for å forenkle programmering av hyppig forekommende kombinasjoner av gl-funksjoner som er nyttige og litt tungvindte å finne opp selv etter behov. Se /include/gl/glu.h i VC-installasjonen.
  • glut-funksjonene er et primitivt sett med med funksjoner for skjerm- og inputadministrasjon som hjelper oss å skrive komplette programmer som er plattformuavhengige. Det er disse som brukes i eksemplene i Red Book, versjon 1.2.
  • wgl-funksjonene binder OpenGL til MS Windows og er derfor plattformavhengige. Disse rutinene er parallelle til glX-rutinene som er referert i OpenGL-boka for X-implementasjon. Sammenhengen mellom wgl og glX rutinene er dokumentert i hjelpesystemet til VC++, "GLX and WGL/Win32"
Referanser
  • Hjelp/litteratur for VC++
  • VC++ programmet std som er et skjelett for OpenGL-programmer under MS-Windows: std.zip(VC++/gml) eller std.zip(VC++/.Net)
  • Tilsvarende program, tegner sylinder og er utvidet med enkel muserespons: stdmouse.zip(VC++/gml) eller stdmouse.zip(VC++/.Net)
Vedlikehold
Børre Stenseth, Egenkontroll, Revidert mai 2002
(Velkommen) Utviklingsverktøy>OpenGL og MFC (C#/.Net)