//***********************************************************************// // // // - "Talk to me like I'm a 3 year old!" Programming Lessons - // // // // $Author: Ben Humphrey digiben@gametutorilas.com // // // // $Program: Texture Mapping // // // // $Date: 3/3/01 // // // //***********************************************************************// using System.Runtime.InteropServices; using Tao.Sdl.Sdl; using Tao.Sdl.SdlImage; using Tao.OpenGl.Gl; using Tao.OpenGl.Glu; using Nemerle.IO; module Game { g_Texture : array [int] = array (1); ///////////////////////////////// INIT GAME WINDOW \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function initializes the game window. ///// ///////////////////////////////// INIT GAME WINDOW \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal Init() : void { Init.InitializeOpenGL(Init.SCREEN_WIDTH, Init.SCREEN_HEIGHT); // Initialize openGL // This is where we load all of our texture. We are loading // just one in this case, but you can add many many more if you want. // We pass in our global textureArray, our file we want to load, and the texture ID we want associated with it. Init.CreateTexture(g_Texture, "../../doc/styles/logo.png", 0); // Load "bitmap.bmp" into openGL as a texture } ///////////////////////////////// RENDER SCENE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function renders the entire scene. ///// ///////////////////////////////// RENDER SCENE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal RenderScene() : void { glClear(GL_COLOR_BUFFER_BIT %| GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer glLoadIdentity(); // Reset The View // Position View Up Vector gluLookAt(0.0, 0.0, 6.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // This determines where the camera's position and view is // Below we draw a texture mapped square. Remember, GL_QUADS draws a 4 point polygon. // In order to assign a texture map to a polygon, we need to call glBindTexture(). // This passes in the type of texture map (always use GL_TEXTURE_2D) and the index // into our generated texture array - g_Texture[]. If we want to use another // texture map, and we have multiple loaded, we just change the index into the array. // Bind the texture stored at the zero index of g_Texture[] glBindTexture(GL_TEXTURE_2D, g_Texture[0]); // Display a quad texture to the screen glBegin(GL_QUADS); // glTexCoord2f() takes the X and Y offset (or U and V) into the bitmap. // Then, the next point sent to be rendered attaches that part of the bitmap // to itself. The (U, V) coordinates range from (0, 0) being the top left corner // of the bitmap, to (1, 1) being the bottom left corner of the bitmap. // You can go above 1 but it just is wrapped around back to zero and repeats the texture. // Try setting the 1's to 2's and see what it does, then try setting them to 0.5's. // The higher the number, the more instances of the texture will appear on the square, // Where the lower the number, it stretches the incomplete texture over the surface of the square. // For every vertice we need a U V coordinate, as shown below. You might have to play // around with the values to make it texture correctly, otherwise it will be flipped, upside down, // or skewed. It also depends on where you are looking at it. We are looking down the -Z axis. // Display the top left vertice glTexCoord2f(0.0f, 0.0f); glVertex3f(0.5f, 1.0f, 0.0f); // Display the bottom left vertice glTexCoord2f(0.0f, 1.0f); glVertex3f(0.5f, -1.0f, 0.0f); // Display the bottom right vertice glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, -1.0f, 0.0f); // Display the top right vertice glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f); glEnd(); // Stop drawing QUADS SDL_GL_SwapBuffers(); // Swap the backbuffers to the foreground } ////////////////////////////// MAIN GAME LOOP \\\\\\\\\\\\\\\\\\\\\\\\\\\\* ////// ////// This function handles the main game loop ////// ////////////////////////////// MAIN GAME LOOP \\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal MainLoop() : void { mutable done = false; // is our job done ? not yet ! mutable even = SDL_Event (); while(!done) // as long as our job's not done { while( SDL_PollEvent (out even) != 0 ) // look for events (like keystrokes, resizing etc.) { def ty = (even.@type :> int); // what kind of event have we got ? when (ty == SDL_QUIT) // if user wishes to quit done = true; // this implies our job is done when (ty == SDL_KEYDOWN) // if the user has pressed a key Init.HandleKeyPressEvent( even.key.keysym ); // callback for handling keystrokes, arg is key pressed when (ty == SDL_VIDEORESIZE) { // if there is a resize event // request SDL to resize the window to the size and depth etc. that we specify Init.MainWindow = SDL_SetVideoMode(even.resize.w, even.resize.h, Init.SCREEN_DEPTH, Init.VideoFlags ); Init.SizeOpenGLScreen(even.resize.w, even.resize.h); // now resize the OpenGL viewport when (Init.MainWindow == System.IntPtr.Zero) // if window resize has failed { printf ("Failed resizing SDL window : %s\n", SDL_GetError()); // report error Init.Quit(0); } } } // while( SDL_ ... RenderScene(); // draw our OpenGL scene } // while( ! done) } } module Init { public SCREEN_WIDTH : int = 800; // We want our screen width 800 pixels public SCREEN_HEIGHT : int = 600; // We want our screen height 600 pixels public SCREEN_DEPTH : int = 24; // We want 16 bits per pixel mutable internal VideoFlags : int; // Video Flags for the Create Window function mutable internal MainWindow : System.IntPtr; // drawing surface on the SDL window /////////////////////////////////// CREATE TEXTURE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This creates a texture in OpenGL that we can texture map ///// /////////////////////////////////// CREATE TEXTURE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal CreateTexture(textureArray : array [int], strFileName : string, textureID : int) : void { unless (strFileName == null) { // We need to load the texture data, so we use a cool function that SDL offers. def basetc = System.DateTime.Now.Ticks; def Bitmap = System.Drawing.Bitmap (strFileName); System.Console.WriteLine ((System.DateTime.Now.Ticks - basetc :> double) * 0.0000001); def basetc = System.DateTime.Now.Ticks; // Load the bitmap and store the data // Now that we have the texture data, we need to register our texture with OpenGL // To do this we need to call glGenTextures(). The 1 for the first parameter is // how many texture we want to register this time (we could do a bunch in a row). // The second parameter is the array index that will hold the reference to this texture. // Generate a texture with the associative texture ID stored in the array glGenTextures(1, out textureArray[textureID]); // Now that we have a reference for the texture, we need to bind the texture // to tell OpenGL this is the reference that we are assigning the bitmap data too. // The first parameter tells OpenGL we want are using a 2D texture, while the // second parameter passes in the reference we are going to assign the texture too. // We will use this function later to tell OpenGL we want to use this texture to texture map. // Bind the texture to the texture arrays index and init the texture glBindTexture(GL_TEXTURE_2D, textureArray[textureID]); // the following lines extract R,G and B values from any bitmap def data = array (3 * Bitmap.Width * Bitmap.Height); def channels = 3; System.Console.WriteLine ((System.DateTime.Now.Ticks - basetc :> double) * 0.0000001); def basetc = System.DateTime.Now.Ticks; for(mutable i = 0; i < Bitmap.Height; ++i) for(mutable j = 0; j < Bitmap.Width; ++j) { def offset = (i * Bitmap.Width + j) * channels; def pixel = Bitmap.GetPixel (j, i); data[offset + 0] = pixel.R; // in our tImage classes we store r first data[offset + 1] = pixel.G; // then g data[offset + 2] = pixel.B; // (for bmps - three channels only) } System.Console.WriteLine ((System.DateTime.Now.Ticks - basetc :> double) * 0.0000001); def basetc = System.DateTime.Now.Ticks; // Now comes the important part, we actually pass in all the data from the bitmap to // create the texture. Here is what the parameters mean in gluBuild2DMipmaps(): // (We want a 2D texture, 3 channels (RGB), bitmap width, bitmap height, It's an RGB format, // the data is stored as unsigned bytes, and the actuall pixel data); // What is a Mip map? Mip maps are a bunch of scaled pictures from the original. This makes // it look better when we are near and farther away from the texture map. It chooses the // best looking scaled size depending on where the camera is according to the texture map. // Otherwise, if we didn't use mip maps, it would scale the original UP and down which would // look not so good when we got far away or up close, it would look pixelated. // Build Mipmaps (builds different versions of the picture for distances - looks better) // Build Mipmaps (builds different versions of the picture for distances - looks better) // ignore (gluBuild2DMipmaps(GL_TEXTURE_2D, 3, Bitmap.Width, Bitmap.Height, // GL_RGB, GL_UNSIGNED_BYTE, data)); // glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Bitmap.Width, Bitmap.Height, -1, GL_RGB, glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, data); System.Console.WriteLine ((System.DateTime.Now.Ticks - basetc :> double) * 0.0000001); // Lastly, we need to tell OpenGL the quality of our texture map. GL_LINEAR_MIPMAP_LINEAR // is the smoothest. GL_LINEAR_MIPMAP_NEAREST is faster than GL_LINEAR_MIPMAP_LINEAR, // but looks blochy and pixilated. Good for slower computers though. Read more about // the MIN and MAG filters at the bottom of main.cpp // glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); printf ("Texture loaded: w %d h %d\n", (Bitmap.Width :> int), (Bitmap.Height :> int)); }; } /////////////////////////////////// TOGGLE FULL SCREEN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* /////// /////// This function TOGGLES between FULLSCREEN and WINDOWED mode /////// /////////////////////////////////// TOGGLE FULL SCREEN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ToggleFullScreen() : void { when (SDL_WM_ToggleFullScreen (MainWindow) == 0) // try to toggle fullscreen mode for window 'MainWindow' { printf ("Failed to Toggle Fullscreen mode : %s\n", SDL_GetError()); // report error in case toggle fails Quit(0); } } /////////////////////////////// CREATE MY WINDOW \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* //////// //////// This function CREATES our WINDOW for drawing the GL stuff //////// /////////////////////////////// CREATE MY WINDOW \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* CreateMyWindow(strWindowName : string, width : int, height : int, VideoFlags : int) : void { // SCREEN_DEPTH is const for bits per pixel MainWindow = SDL_SetVideoMode(width, height, SCREEN_DEPTH, VideoFlags); when ( MainWindow == System.IntPtr.Zero ) // if window creation failed { printf ("Failed to Create Window : %s\n", SDL_GetError()); // report error Quit(0); } SDL_WM_SetCaption(strWindowName, strWindowName); // set the window caption (first argument) and icon caption (2nd arg) } ///////////////////////////// SETUP PIXEL FORMAT \\\\\\\\\\\\\\\\\\\\\\\\\\\\* /////// /////// Sets the pixel format for openGL and video flags for SDL /////// ///////////////////////////// SETUP PIXEL FORMAT \\\\\\\\\\\\\\\\\\\\\\\\\\\\* SetupPixelFormat() : void { //////// SURFACE IS THE DRAWABLE PORTION OF AN SDL WINDOW \\\\\\\\* ///////////// we set the common flags here VideoFlags = SDL_OPENGL; // it's an openGL window VideoFlags |= SDL_HWPALETTE; // exclusive access to hardware colour palette VideoFlags |= SDL_RESIZABLE; // the window must be resizeable def VideoInfo = SDL_GetVideoInfo(); // query SDL for information about our video hardware ///////////// we set the system dependant flags here if(VideoInfo.hw_available != 0) // is it a hardware surface VideoFlags |= SDL_HWSURFACE; else VideoFlags |= SDL_SWSURFACE; // Blitting is fast copying / moving /swapping of contiguous sections of memory // for more about blitting check out : // http://www.csc.liv.ac.uk/~fish/HTML/blitzman/bm_blitter.html when (VideoInfo.blit_hw != 0) // is hardware blitting available VideoFlags |= SDL_HWACCEL; // tell SDL that the GL drawing is going to be double buffered def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1 ); // size of depth buffer def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_DEPTH_SIZE, SCREEN_DEPTH); // we aren't going to use the stencil buffer def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_STENCIL_SIZE, 0); // this and the next three lines set the bits allocated per pixel - def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_ACCUM_RED_SIZE, 0); // - for the accumulation buffer to 0 def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_ACCUM_GREEN_SIZE, 0); def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_ACCUM_BLUE_SIZE, 0); def _ = SDL_GL_SetAttribute( SDL_GLattr.SDL_GL_ACCUM_ALPHA_SIZE, 0); () } //////////////////////////// RESIZE OPENGL SCREEN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function resizes the viewport for OpenGL. ///// //////////////////////////// RESIZE OPENGL SCREEN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal SizeOpenGLScreen(width : int, height : int) : void // Initialize The GL Window { def height = // Prevent A Divide By Zero error if (height == 0) 1 else height; // Make the Height Equal One glViewport(0, 0, width, height); // Make our viewport the whole window // We could make the view smaller inside // Our window if we wanted too. // The glViewport takes (x, y, width, height) // This basically means, what our drawing boundries glMatrixMode(GL_PROJECTION); // Select The Projection Matrix glLoadIdentity(); // Reset The Projection Matrix // Calculate The Aspect Ratio Of The Window // The parameters are: // (view angle, aspect ration of the width to the height, // The closest distance to the camera before it clips, // FOV // Ratio // The farthest distance before it stops drawing) gluPerspective(45.0, (width :> double) / (height :> double), 1.0, 150.0); // * Note * - The farthest distance should be at least 1 if you don't want some // funny artifacts when dealing with lighting and distance polygons. This is a special // thing that not many people know about. If it's less than 1 it creates little flashes // on far away polygons when lighting is enabled. glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix glLoadIdentity(); // Reset The Modelview Matrix } //////////////////////////////// INITIALIZE GL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function handles all the initialization for openGL ///// //////////////////////////////// INITIALIZE GL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal InitializeOpenGL(width : int, height : int) : void { glEnable (GL_DEPTH_TEST); // This allows us to use texture mapping, otherwise we just use colors. glEnable(GL_TEXTURE_2D); // Enable Texture Mapping SizeOpenGLScreen (width, height); // resize the OpenGL Viewport to the given height and width } /////////////////// HANDLE KEY PRESS EVENT \\\\\\\\\\\\\\\\\\\\\\\ ////// ////// This function handles the keypress events generated when the user presses a key ////// /////////////////// HANDLE KEY PRESS EVENT \\\\\\\\\\\\\\\\\\\\\\\\ internal HandleKeyPressEvent(keysym : SDL_keysym) : void { def sym = (keysym.sym :> SDLKey); // which key have we got when (sym == SDLKey.SDLK_F1) // if it is F1 ToggleFullScreen(); // toggle between fullscreen and windowed mode when (sym == SDLKey.SDLK_ESCAPE) // if it is ESCAPE Quit(0); // quit after cleaning up } ////////////////////////////// MAIN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ////// ////// create the window and calling the initialization functions ////// ////////////////////////////// MAIN \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* Main () : void { // print user instructions printf (" Hit the F1 key to Toggle between Fullscreen and windowed mode\n"); printf (" Hit ESC to quit\n"); if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) // try to initialize SDL video module // report error if it fails printf ("Failed initializing SDL Video : %s\n", SDL_GetError()); else { // Set up the format for the pixels of the OpenGL drawing surface SetupPixelFormat(); // Create our window, we pass caption for the window, // the width, height and video flags required CreateMyWindow("www.GameTutorials.com - First OpenGL Program", SCREEN_WIDTH, SCREEN_HEIGHT, VideoFlags); // Initializes our OpenGL drawing surface Game.Init(); // Run our message loop Game.MainLoop(); } } ////////////////////////////// QUIT \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ////// ////// This will shutdown SDL and quit the program ////// ////////////////////////////// QUIT \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* internal Quit(ret_val : int) : void { SDL_Quit(); // shuts down SDL stuff System.Environment.Exit(ret_val); // quit the program } }