Bitmaps
Nu zijn we dan eindelijk toe aan het echte werk, bitmaps toevoegen aan onze objecten maar voor ik je de code hiervoor laat zien moet ik je eerst iets uitleggen over bitmaps. Bitmaps bestaan uit 3 delen, bitmap file header, bitmap information header, en de bitmap data. De informatie die in de bitmap file header staat geeft ondere andere de grootte van de file aan en het bestands type wat in dit geval dus een bitmap moet zijn (0X4D42 is de bitmap ID). De information header bevat de data over de bitmap zelf zoals de hoogte, breedte, bits per pixel, en nog een aantal andere dingen. En als laatste komt dan nog de bitmap data die de tekening bevat. Dit voorbeeld zal ik in delen uitleggen anders wordt het wel heel veel in een keer.

//alle defines
#define BITMAP_ID 0x4d42        

//alle header files 
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <gl/gl.h>
#include <gl/glu.h>

// variabelen
HDC g_HDC;
bool key[256];
bool fullscreen = TRUE;

//bitmapvariabelen
BITMAPINFOHEADER landinfo;
BITMAPINFOHEADER waterinfo;

unsigned char* landtexture;
unsigned char* watertexture;
unsigned int land;
unsigned int water;

//rotatie variabele
float hoek;
Hier komen we de eerste nieuwe code tegen, namelijk de variabelen voor de bitmaps. De eerste 2 zijn ervoor om de infoheaders van de 2 bitmaps op te slaan waar we informatie over de grootte, kleurdiepte, en andere dingen uit kunnen halen. In de volgende 2 variabelen komt het adres waar de bitmap data is opgeslagen te staan, en de laatste 2 variabelen zijn de uiteindelijke objecten die we kunnen gebruiken om de bitmap op het scherm te krijgen.

///////////////////////////////////////////////////////////////////////////////
//laden van een bitmap
///////////////////////////////////////////////////////////////////////////////
unsigned char *loadbmp(char *filename, BITMAPINFOHEADER *bmpinfo)
{
	FILE *fileptr;
	BITMAPFILEHEADER bmpfile;
	unsigned char *bmpimage;
	int imageidx = 0;
	unsigned char swaprgb;

	fileptr = fopen(filename, "rb");
	if (fileptr == NULL){return 0;}

	fread(&bmpfile, sizeof(BITMAPFILEHEADER), 1, fileptr);
	if (bmpfile.bfType != BITMAP_ID)
	{
		fclose(fileptr);
		return 0;
	}

	fread(bmpinfo, sizeof(BITMAPINFOHEADER), 1, fileptr);

	fseek(fileptr, bmpfile.bfOffBits, SEEK_SET);

	bmpimage=(unsigned char*)malloc(bmpinfo->biSizeImage);
	if (!bmpimage)
	{
		free(bmpimage);
		fclose(fileptr);
		return 0;
	}

	fread(bmpimage, 1, bmpinfo->biSizeImage, fileptr);
	if (bmpimage == NULL)
	{
		fclose(fileptr);
		return 0;
	}

	for (imageidx = 0; imageidx < bmpinfo->biSizeImage; imageidx+=3)
	{
		swaprgb = bmpimage[imageidx];
		bmpimage[imageidx] = bmpimage[imageidx + 2];
		bmpimage[imageidx + 2] = swaprgb;
	}

	fclose(fileptr);
	return bmpimage;
}
Dit deel zorgt ervoor dat de bitmap ingeladen wordt in het geheugen. Eerst wordt het bestand geopend in read binary mode en gecontroleerd of het bestand geopend is. Dan wordt de bitmap file header gelezen en wordt gecontroleert of dit een bitmap is door het ID te controleren. Nu je weet dat het een bitmap is kun je de infoheader inlezen en vervolgens de filepointer naar het begin van de bitmap data verplaatsen. Als we deze data in hebben gelezen moeten we geheugen vrij maken om de bitmap in op te slaan en controleren dit. Als we geheugen hebben om de bitmap op te slaan kunnen we de bitmap daar inlezen en voor de zekerheid controleren of de bitmap ook echt ingelezen is. Dan moeten we als laatste nog de kleur waarden omdraaien aangezien een bitmap bgr kleur waarden heeft en het dus RGB moet worden en kunnen we de file pointer sluiten en de bitmap retourneren.

//////////////////////////////////////////////////////////////////////////////
//Voorwerp Box
//////////////////////////////////////////////////////////////////////////////
void box(float xpos,float ypos,float zpos)
{
	glPushMatrix();
		glTranslatef(xpos,ypos,zpos);
				
		glBegin(GL_QUADS);
		// Front Face
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	
		// Back Face
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	
		// Top Face
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
		// Bottom Face
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	
		// Right face
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	
		// Left Face
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	
	glEnd();

	glPopMatrix();
}

Zoals je weet heeft een bitmap een lengte en een breedte en om deze goed op 
een voorwerp te plakken moet je dus aangeven welk hoekpunt waar moet komen. 
Dit wordt gedaan door glTexCoord2f(float s, float t); waarmee je de x en y 
co÷rdinaat van de texture aangeeft. Zo heeft links onder de co÷rdinaten (0.0, 0.0),
links boven (0.0, 1.0) rechts boven (1.0, 1.0) en rechts onder (1.0, 0.0). Deze 
co÷rdinaten ken je dus toe aan de bijbehorende co÷rdinaten van je voowerpen en 
je texture's worden netjes op je voorwerp geplakt.

///////////////////////////////////////////////////////////////////////////////
//box1
///////////////////////////////////////////////////////////////////////////////
void box1(float xpos,float ypos,float zpos)
{
	glPushMatrix();
		glColor3f(1.0f,1.0f,1.0f);
		glTranslatef(xpos,ypos,zpos);
		glScalef(1.0f,1.0f,1.0f);
		glRotatef(hoek, 1.0f,1.0f,1.0f);
		box(0.0f,0.0f,0.0f);
	glPopMatrix();
}

////////////////////////////////////////////////////////////////////////////////
//box2
////////////////////////////////////////////////////////////////////////////////
void box2(float xpos,float ypos,float zpos)
{
	glPushMatrix();
		glColor3f(1.0f,1.0f,1.0f);
		glTranslatef(xpos,ypos,zpos);
		glScalef(1.0f,1.0f,1.0f);
		glRotatef(hoek, 1.0f,1.0f,1.0f);
		box(0.0f,0.0f,0.0f);
	glPopMatrix();
}

////////////////////////////////////////////////////////////////////////////////
//Rendering
////////////////////////////////////////////////////////////////////////////////
void render()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();


	glPushMatrix();
		glLoadIdentity();
		glTranslatef(0.0f,0.0f,-10.0f);
		glBindTexture(GL_TEXTURE_2D, land);
		box1(-3.0f,-2.0f,0.0f);
		glBindTexture(GL_TEXTURE_2D, water);
		box2(3.0f,2.0f,0.0f);
	glPopMatrix();
	
	glFlush();
	SwapBuffers(g_HDC);

	hoek +=1;
	if (hoek>=360){hoek=0;}
}
Het meeste moet tot aan hier nog wel bekend zijn neem ik aan behalve glBindTexture wat je in de render functie tegenkomt. Het zal je vast al wel duidelijk zijn dat je hiermee een texture bind aan het object dat erna komt. Hier zeg je dus dat je het 2D texture land aan box1 wilt binden, hoe je aan de texture komt zie je verderop in de code nog.

//////////////////////////////////////////////////////////////////////////////
//initialisatie van opengl
////////////////////////////////////////////////////////////////////////////// 
void initgl()
{
	glClearColor(0.0f,0.0f,0.0f,0.0f);
	glShadeModel(GL_SMOOTH);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
	glEnable(GL_TEXTURE_2D);
}
In de bovenstaande functie gebeurt de initialisatie van OpenGL en wordt de achtergrondkleur ingesteld als zwart. De glShademodel be´nvloed de schaduw werking en je hebt de keuze uit GL_SMOOTH en GL_FLAT. Daarna stellen we nog de depth test in zodat OpenGL kan bepalen of voorwerpen zichtbaar zijn of achter andere voorwerpen staan. Gl_CULL_FACE zorgt ervoor dat de achterkant van een voorwerp niet wordt getekend. En als laatste wordt het mogelijk gemaakt om 2D textures in OpenGL te gebruiken.

////////////////////////////////////////////////////////////////////////////
//laden van textures
////////////////////////////////////////////////////////////////////////////
bool loadtextures()
{
	landtexture=loadbmp("land.bmp", &landinfo);
	watertexture=loadbmp("water.bmp",&waterinfo);

	glGenTextures(1, &land);
	glBindTexture(GL_TEXTURE_2D, land);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, landinfo.biWidth, 
			  landinfo.biHeight, GL_RGB, GL_UNSIGNED_BYTE, 
			  landtexture);

	glGenTextures(1, &water);
	glBindTexture(GL_TEXTURE_2D, water);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, waterinfo.biWidth, 
          		  waterinfo.biHeight, GL_RGB, GL_UNSIGNED_BYTE, 
 			  watertexture);

	return true;
}
En dit is alweer het laatste nieuwe deel, de functie die de texture data klaar houdt voor gebruik door gebruik te maken van texture objecten wat een enorme snelheidswinst oplevert aangezien je de textures op elk tijdstip zo op kunt vragen. Het eerste wat je hier ziet is de loadbmp functie die we aan het begin gemaakt hebben die de bitmap data in het geheugen leest. Je hebt nu dus de data ingeladen in het geheugen en je hebt het adres van het geheugen, de volgende stap is nu het maken van de texture naam. Om te voorkomen dat je de zelfde naam 2 keer maakt gebruik je de glGenTextures(); functie. De eerste parameter geeft het aantal textures aan voor als je met een array werkt en voor de tweede parameter vul je de naam van de texture in. Nu we de naam voor een texture hebben kunnen we de texture data daaraan binden door glBindTexture() te gebruiken. De eerste parameter is hier 1D,2D, of 3D afhankelijk van wat voor bitmap het is, en de tweede is de texture naam. Er staan nu nog standaard texture instellingen in het object die door de volgende texture functie oproepen verandert worden. Met de glTexParameteri functie kun je vervolgens instellen hoe de textures zich gedragen. We kunnen de magnification instellen wat inhoud dat een pixel een klein deel van een texel beslaat. We kunnen de minification instellen wat inhoud dat een pixel meerdere texels beslaat. En we kunnen de texture wrap instellen, GL_REPEAT betekend dat de texture continu herhaald wordt.

De laatste regel, gluBuild2DMipmaps() is een zeer makkelijke functie die uit zichzelf mipmaps voor je maakt. Een texture heeft de neiging om op bepaalde afstanden niet meer scherp weergegeven te worden en om dat te voorkomen kun je meerdere textures maken van verschillende grootte. Hoe verder de texture nu verwijdert is hoe kleiner de texture die op het scherm komt. Je kunt nu dus zelf textures maken met verschillende groottes maar dat kun je ook door gluBuild2DMipmaps() laten doen. Het enigste wat jij moet doen is het originele texture aan te geven als laatste parameter en het werk wordt voor jou gedaan.


////////////////////////////////////////////////////////////////////////////
//Pixelformatdescriptor
////////////////////////////////////////////////////////////////////////////
void setuppfd(HDC hDC)
{
	int nPixelFormat;

	static PIXELFORMATDESCRIPTOR pfd={
		sizeof(PIXELFORMATDESCRIPTOR),
		1,
		PFD_DRAW_TO_WINDOW|
		PFD_SUPPORT_OPENGL|
		PFD_DOUBLEBUFFER,
		PFD_TYPE_RGBA,
		32,
		0,0,0,0,0,0,
		0,
		0,
		0,
		0,0,0,0,
		16,
		0,
		0,
		PFD_MAIN_PLANE,
		0,
		0,0,0};

		nPixelFormat = ChoosePixelFormat(hDC, &pfd);
		SetPixelFormat(hDC,nPixelFormat, &pfd);
}

///////////////////////////////////////////////////////////////////////////
//Windows Procedure
///////////////////////////////////////////////////////////////////////////

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM	lParam)	
{
	static HGLRC hRC;
	static HDC hDC;
	int width, height;

	switch (message)
	{
		case WM_CREATE:
			hDC = GetDC(hwnd);
			g_HDC = hDC;
			setuppfd(hDC);

			hRC = wglCreateContext(hDC);
			wglMakeCurrent(hDC,hRC);
			break;

		case WM_CLOSE:
			wglMakeCurrent(hDC,NULL);
			wglDeleteContext(hRC);

			PostQuitMessage(0);
			break;
			
		case WM_SIZE:
			height = HIWORD(lParam);
			width = LOWORD(lParam);

			if (height==0)
			{
				height=1;
			}

			glViewport(0,0,width,height);
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();

			gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,
				       1.0f,1000.0f);

			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
			break;
			
		case WM_KEYDOWN:
			switch (wParam)
			{
				case VK_ESCAPE:
					wglMakeCurrent(hDC,NULL);
					wglDeleteContext(hRC);
					PostQuitMessage(0);
					break;
			}

	}
	return DefWindowProc(hwnd,message,wParam,lParam);
}

/////////////////////////////////////////////////////////////////////////////
//Main Functie
/////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hInstance,	HINSTANCE hPrevInstance,
		   LPSTR lpCmdLine, int	nShowCmd)
{
	WNDCLASSEX	wc;	
	HWND hwnd;
	MSG		msg;
	DWORD dwExStyle, dwStyle;
	bool	done=FALSE;
	RECT windowRect;

	int width=800,height=600,bits=32;
					
	windowRect.top = 0;
	windowRect.left = 0;
	windowRect.bottom = height;
	windowRect.right = width;

	wc.cbSize		= sizeof(WNDCLASSEX);	
	wc.style		= CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc		= WndProc;
	wc.cbClsExtra		= 0;
	wc.cbWndExtra		= 0;
	wc.hInstance		= hInstance;	
	wc.hIcon		= LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);	
	wc.lpszMenuName		= NULL;
	wc.lpszClassName	= "OpenGL";
	wc.hIconSm		= LoadIcon(NULL,IDI_WINLOGO);

	RegisterClassEx(&wc);

	if(fullscreen)
	{
	DEVMODE devModeScreen;
	memset(&devModeScreen, 0,sizeof(devModeScreen));
	devModeScreen.dmSize = sizeof(devModeScreen);
	devModeScreen.dmPelsWidth = width;
	devModeScreen.dmPelsHeight = height;
	devModeScreen.dmBitsPerPel = bits;
	devModeScreen.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

	ChangeDisplaySettings(&devModeScreen, CDS_FULLSCREEN);

	dwExStyle = WS_EX_APPWINDOW;
	dwStyle = WS_POPUP;
	ShowCursor(FALSE);
	}

	else
	{
		dwExStyle = NULL;
		dwStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_SYSMENU;
	}

	
	AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);

	hwnd=CreateWindowEx(dwExStyle,
			    "OpenGL",	
			    "glwindow",
			    dwStyle | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 
			    0,0,
			    windowRect.right - windowRect.left,
			    windowRect.bottom - windowRect.top,
			    NULL,
			    NULL,
			    hInstance,	
			    NULL);
	
	ShowWindow(hwnd, SW_SHOW);
	UpdateWindow(hwnd);
	initgl();
	loadtextures();
	
	while(!done)
	{
		PeekMessage(&msg,NULL,0,0,PM_REMOVE);	
			
			if (msg.message==WM_QUIT)
			{
				done=TRUE;
			}
			else
			{
				render();
				TranslateMessage(&msg);	
				DispatchMessage(&msg);
			}
	}
	return (msg.wParam);
}

Het is weer een hele lap text geworden en om het niet nog veel langer te maken heb ik niet alle informatie hier neergezet over parameters die je aan functies mee kunt geven. Om je deze informatie toch niet te onthouden kun je deze finden in OpenGL referenties.

Succes

Vampire,