8. Bump Mapping in GLSL Introduction
Bump mapping is essential in todays computer games, and computer graphics in general. Would you like to know the best thing about it? It is extremely simple to implement. Bump mapping works...
Swiftless GLSL Shader Developer Swiftless GLSL Shader Developer
Version 0.1a
Currently Swiftless GLSL Shader Developer is in it's first public release, and is currently in alpha status, meaning it is not complete and may contain...
Wordpress Optimization Wordpress Website Optimizations
Introduction
Wordpress itself is a fairly wonderful tool. Since switching to it, I find it is a lot quicker to make changes to my website and it is also quicker to get...
36. OpenGL Framebuffers Introduction
Frame buffers are one of those mythical things that we have all heard of, but many beginner OpenGL developers avoid because there is not much information about them, and they can be confusing...
1. Terrain Class Terrain is one of those things that so far, hasn't been perfectly recreated in computer graphics. But it is almost there! Looking over a beautiful landscape can be one of the most amazing feelings in the...
Because OpenGL 3.x and up is extremely shader oriented, I am going to show you how to integrate our shader code from the GLSL section of the website. I am not going to go into GLSL details here, as I have done that, and will continue to do that in the GLSL portion of the site. This tutorial is a quick guide on how to get shaders working in our current framework. So why don’t we get to it, so we can move on to using matrices in the new OpenGL pipeline. Keep in mind that I am only working with Vertex and Fragment shaders still, I will introduce Geometry shaders in a later tutorial.
Coding
The code for this tutorial is going to be fairly minimal, and I may have updates to the shader.cpp and shader.h files that are in the GLSL portion of the site. If you have any queries, feel free to ask. The first thing we are going to do, is copy our shader.h and shader.cpp files from the GLSL section of the site, and add them into our new Visual Studio project. Then go ahead and open opengl_3.h.
Opengl_3.h
Inside of opengl_3.h where we have our opengl context class structure, jump up the top and make sure you include the shader.h file into your source code.
#include "shader.h"
Once we have done this, go down into our private variables for our context class, and add an instance of a shader variable. If you are working with several shaders, you can make this into an array, or create separate shader variables. Personally I would create an array of shaders.
class OpenGLContext {
...
private:
int windowWidth; // Store the width of our window
int windowHeight; // Store the height of our window
Shader *shader; // Our GLSL shader
...
};
Opengl_3.cpp
Once we have a shader variable declared that we can play with, open up opengl_3.cpp and jump down to the setupScene method. Inside of here we are going to set our shader to a new shader object, based on shader.vert and shader.frag. Both of which are included in the project.
void OpenGLContext::setupScene(void) {
glClearColor(0.4f, 0.6f, 0.9f, 0.0f); // Set the clear color based on Microsofts CornflowerBlue (default in XNA)
shader = new Shader("shader.vert", "shader.frag"); // Create our shader by loading our vertex and fragment shader
}
And then all we have left to do is jump to our renderScene method and bind and unbind our shader as required. You will notice that we don’t have any geometry to draw yet, so you won’t see anything yet, but don’t worry, we are getting there
void OpenGLContext::renderScene(void) {
glViewport(0, 0, windowWidth, windowHeight); // Set the viewport size to fill the window
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Clear required buffers
shader->bind(); // Bind our shader
shader->unbind(); // Unbind our shader
SwapBuffers(hdc); // Swap buffers so we can see our rendering
}
Shader.cpp
I mentioned at the start that these are pretty much the same shader.cpp and shader.h files that are in the GLSL section of the website. Here I am going to list one major change that I have made. I am binding two variables to our shader. These variables are constants and are called in_Position and in_Color and will be used to send positions and colors to our shader without binding and unbinding them every frame. These lines are added in our init method for our shader:
void Shader::init(const char *vsFile, const char *fsFile) {
...
glBindAttribLocation(shader_id, 0, "in_Position"); // Bind a constant attribute location for positions of vertices
glBindAttribLocation(shader_id, 1, "in_Color"); // Bind another constant attribute location, this time for color
...
}
Shader.vert
Because this tutorial has been so short, I am going to give a quick talk about the changes done to vertex and fragment shaders in GLSL 1.50. To declare which version of GLSL you are using, you can use a line similar to the following, which sets our GLSL version to 1.50:
#version 150 core
This can be changed for any valid version of GLSL. In GLSL 1.50, we no longer have gl_ModelViewMatrix, gl_ProjectionMatrix, gl_Vertex, etc, etc. Because we now have to manage and send all of this information to the shaders ourselves. In the next tutorial I will introduce self manipulated model, view and projection matrices, but for now I will stick with the basics. Because I created the two variables in_Position and in_Color to bind to our shader, we have to create these. They will both be vec3 variables, and because we are using GLSL 1.5, we also need to declare if the variable is going “in” or “out” of the current shader. There are no more uniform or varying variables to deal with. I am also going to create one “out” variable called pass_Color which is also a vec3 variable. This will send any outgoing color from the vertex shader to the fragment shader. Luckily setting the final position requires some similar code, which is setting the gl_Position variable.
#version 150 core
in vec3 in_Position;
in vec3 in_Color;
out vec3 pass_Color;
void main(void)
{
gl_Position = vec4(in_Position, 1.0);
pass_Color = in_Color;
}
Shader.frag
In our fragment shader, we are also going to declare the version of GLSL that we are working with and because we had an “out” variable in our shader.vert file, then we need an “in” variable of the same name in our fragment shader. So we are going to create an “in” variable, of type vec3 called pass_Color. Now fragment shaders no longer have gl_FragColor, instead we need to manually declare an “out” variable of type vec4, which will give our color to our GPU. I am going to call this “out” variable out_Color and I am going to set it to the pass_Color, which is the variable we sent in to our shader.
#version 150 core
in vec3 pass_Color;
out vec4 out_Color;
void main(void)
{
out_Color = vec4(pass_Color, 1.0);
}
If all went well, you should now be set up to use shaders in your application and your application should run without any errors. Stick with me for the next two tutorials, and by the end of the fourth tutorial, we will now be rendering things to the screen. OpenGL 3.x and 4.x takes a bit more preparation, but it does make a difference in the end, especially when it comes to speed as without the immediate mode, our rendering should be extremely more efficient.
If you have any questions you can comment below or email me at swiftless@gmail.com
VN:F [1.9.3_1094]
Please rate so I know where to improve the site. 1 means needs a lot of improvement, 10 means perfect. If you leave a low rating, please state why. I don't want people just coming to bash the site.
OpenGL has undergone some major changes recently from version 3 and onwards. OpenGL 3 introduced a new way of programming in OpenGL that slightly raises the learning curve, but also does away with the entire Immediate Mode by deprecating a lot of the functions. Now I know I have called this part of the site “OpenGL 4 Tutorials”, and all of a sudden I am talking about OpenGL 3, this is because OpenGL 3 and OpenGL 4 are extremely similar, with OpenGL 4 bringing in some new extensions, and because my graphics card only supports OpenGL 3.x. If you would like to see some OpenGL 4 extensions in use, you can always donate and help me get a new graphics card. In this tutorial, I am going to be simply creating a window with an OpenGL 3.2 context attached, nothing more, nothing less. These tutorials are going to be for Windows only, but if you are keen to get them working on a different OS (My Macbook doesn’t support OpenGL 3.x), please send me the ports and I will post them up.
OpenGL 3+ Key Differences
An OpenGL 3+ and 4 context requires you to create a standard OpenGL context (version 1.x or 2.x) and then get your application to opt-in to using an OpenGL 4 context. Without the Immediate Mode, OpenGL has gone for an almost entirely shader driven architecture, which gives us control over everything from the word go, and also greatly improves performance. When it comes to rendering shapes, instead of setting the state and then calling the vertices one by one, OpenGL 3 and upwards wants us to use Vertex Buffer Objects and Vertex Buffer Arrays straight out. Vertex Buffer Objects were available in OpenGL 1.5 and up as an extension, but they are now a part of the core of OpenGL. Matrices are one of the major things affected by OpenGL 3. To put it simply, OpenGL does no matrix operations for us such as setting the Projection Matrix, setting the Model Matrix and setting the View Matrix. This means calls such as glRotate and glTranslate are not deprecated and not recommended. OpenGL now likes us to handle our own matrices. I will talk about this in a later tutorial, but there is a library called GLM, designed for use with OpenGL and GLSL, which will help us with our matrix operations.
Coding
*note* You can jump straight to the bottom of this page to grab the PDF version of this tutorial, or the project files. *note* Let’s finally get into this tutorial, because it is going to be a long one. I’m also going to be aiming for a more Object Oriented approach to programming in these newer tutorials. This tutorial is going to be very similar in some ways to most Win32 OpenGL tutorials available, the only difference being that it has an OpenGL 3.2 context instead of a standard context. So open up Visual Studio and create a new Windows Console Application in C++. Make sure it is empty to begin with, and then create 4 files. I am calling them: “main.cpp, main.h, opengl_3.cpp, opengl_3.h”. I am going to go through these files one by one, starting with the header files, so that we don’t get mixed up shuffling between them.
Main.h
Main.h is going to contain all of our header files for the time being. So open it up and paste the following code. I won’t explain it, as it is pretty straight forward; we are including some header files and linking some libraries, the only tricky part may be “#pragma once”, which isn’t standard but is widely supported, and means to only include the file once during compilation.
#pragma once
// Include Windows functions
#include
// Include input and output operations
#include
// OpenGL and GLEW Header Files and Libraries
#include
#include
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "opengl32.lib")
As you can see from the above code, we are once again using GLEW. GLEW is up to date with the latest OpenGL 3.x and possibly 4.0 extensions. I highly recommend the use of it as it makes coding in OpenGL so much easier without having to worry about declaring extensions by yourself.
Opengl_3.h
The first thing we want to include here is our main.h file. So add the line:
#include "main.h"
Once we have included our main header file which contains access to the GLEW and OpenGL commands, we are going to want to create a new class. I am going to call this class OpenGLContext and it is going to be responsible for creating a context, handling setting up of our scene, resizing of our scene, rendering of our scene, and finally, cleaning up of our scene. Start off with an empty class like so:
/**
OpenGLContext is a class designed to store all of your OpenGL functions and keep them
out of the way of your application logic. Here we have the ability to create an OpenGL
context on a given window and then render to that window.
*/
class OpenGLContext {
public:
private:
protected:
};
Next up, we are going to come up with some protected variables that will keep track of our window. When working with the Win32 API, each Window you see has its own HWND which is the identifier for that window. We also have two different contexts we need to work with, the first is the HDC, which is the device context, and the second is the HGLRC which is an OpenGL rendering context. If we create a variable to handle each of these, we get:
Once we have the information we need for a window and a context, we need to keep track of the width and the height of our window. If we don’t keep track of these, then whenever we resize the window, we will find that parts of it are not rendered to, or we render more than we have to, resulting in some of our image being cut off. We are going to add two private variables to keep track of this for us:
class OpenGLContext {
...
private:
int windowWidth; // Store the width of our window
int windowHeight; // Store the height of our window
...
};
Finally we need to come up with some method stubs for controlling our OpenGLContext object. I am going to work with two different constructors and a destructor for some standard methods. Followed by four other methods for working with our context which are going to be called “create30Context(HWND), setupScene(void), reshapeWindow(int, int), renderScene(void)”.
class OpenGLContext {
public:
OpenGLContext(void); // Default constructor
OpenGLContext(HWND hwnd); // Constructor for creating our context given a hwnd
~OpenGLContext(void); // Destructor for cleaning up our application
bool create30Context(HWND hwnd); // Creation of our OpenGL 3.x context
void setupScene(void); // All scene information can be setup here
void reshapeWindow(int w, int h); // Method to get our window width and height on resize
void renderScene(void); // Render scene (display method from previous OpenGL tutorials)
...
};
This is all we are going to need for our opengl_3.h header file for now. So let’s go and start on opengl_3.cpp.
Opengl_3.cpp
To give use access to our class methods, you are going to want to include opengl_3.h of course, so that will be line number one for this file.
#include "opengl_3.h"
Now let’s get to building us some methods for our class. I am going to start off with the two constructors and the destructor. Our first constructor, the default one, at the moment does nothing, so you can just create an empty constructor. But the second one will make a call to our create30Context method.
/**
Default constructor for the OpenGLContext class. At this stage it does nothing
but you can put anything you want here.
*/
OpenGLContext::OpenGLContext(void) {
}
/**
Constructor for the OpenGLContext class which will create a context given a windows HWND.
*/
OpenGLContext::OpenGLContext(HWND hwnd) {
create30Context(hwnd); // Create a context given a HWND
}
When we get to our destructor, it will handle the removal of our rendering context and our device context. For this, we make use of wglMakeCurrent, wglDeleteContext and ReleaseDC.
/**
Destructor for our OpenGLContext class which will clean up our rendering context
and release the device context from the current window.
*/
OpenGLContext::~OpenGLContext(void) {
wglMakeCurrent(hdc, 0); // Remove the rendering context from our device context
wglDeleteContext(hrc); // Delete our rendering context
ReleaseDC(hwnd, hdc); // Release the device context from our window
}
Let’s get onto our create30Context method now. This method will take in a HWND value to identify a window, and will create a valid rendering and device context on top of it. Create an empty method for this, and then we will set the HWND for our object, and get a valid HDC. I’ve made this method a Boolean value, so we can return a false value if we are unable to successfully create a context of any kind. However by default, we will fall back to and OpenGL 2.1 context if unable to create an OpenGL 3.x context.
/**
create30Context creates an OpenGL context and attaches it to the window provided by
the HWND. This method currently creates an OpenGL 3.2 context by default, but will default
to an OpenGL 2.1 capable context if the OpenGL 3.2 context cannot be created.
*/
bool OpenGLContext::create30Context(HWND hwnd) {
this->hwnd = hwnd; // Set the HWND for our window
hdc = GetDC(hwnd); // Get the device context for our window
return true; // We have successfully created a context, return true
}
The next thing we need to do, is create a variable called a PIXELFORMATDESCRIPTOR which determines how many bits of colour and depth we get, along with giving us double buffering and support for OpenGL. To do this, we first need to create a new variable, I am going to call it pfd and then we need to allocate some empty memory for it. Here, 32 bits of colour, gives us R, G, B and A channels, while 24 bits gives us only R, G and B (each channel takes 8 bits). After we have set aside some memory for this variable, we need to set the size of it, which will always be sizeof(PIXELFORMATDESCRIPTOR). Next, we will set the flags which allow double buffering, opengl support and drawing to a window. Next up, we set the iPixelType to allow RGBA pixels, before we set how many bits we want in our color and depth buffers. Finally, we need to set the iLayerType. We are going to set this to the Main_Plane for our window.
bool OpenGLContext::create30Context(HWND hwnd) {
this->hwnd = hwnd; // Set the HWND for our window
hdc = GetDC(hwnd); // Get the device context for our window
PIXELFORMATDESCRIPTOR pfd; // Create a new PIXELFORMATDESCRIPTOR (PFD)
memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); // Clear our PFD
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); // Set the size of the PFD to the size of the class
pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW; // Enable double buffering, opengl support and drawing to a window
pfd.iPixelType = PFD_TYPE_RGBA; // Set our application to use RGBA pixels
pfd.cColorBits = 32; // Give us 32 bits of color information (the higher, the more colors)
pfd.cDepthBits = 32; // Give us 32 bits of depth information (the higher, the more depth levels)
pfd.iLayerType = PFD_MAIN_PLANE; // Set the layer of the PFD
return true; // We have successfully created a context, return true
}
Next up, we need to choose a pixel format for our device context, based on our pixel format descriptor. To do this, we make a call to ChoosePixelFormat and pass through our hdc and pfd variables, in return we get an integer value that represents our pixel format, which we can then use to set our pixel format for our device context using SetPixelFormat as seen below.
bool OpenGLContext::create30Context(HWND hwnd) {
this->hwnd = hwnd; // Set the HWND for our window
hdc = GetDC(hwnd); // Get the device context for our window
...
int nPixelFormat = ChoosePixelFormat(hdc, &pfd); // Check if our PFD is valid and get a pixel format back
if (nPixelFormat == 0) // If it fails
return false;
bool bResult = SetPixelFormat(hdc, nPixelFormat, &pfd); // Try and set the pixel format based on our PFD
if (!bResult) // If it fails
return false;
return true; // We have successfully created a context, return true
}
Once we have our pixel format all set up. We need to go ahead and create our OpenGL context. To do this, we need to first create an OpenGL 2.1 compatible context, and then call a new OpenGL 3.x extension called wglCreateContextAttribsARB to create an OpenGL 3.x compatible context. So let’s create our OpenGL 2.1 context as per a standard Win32 OpenGL application.
HGLRC tempOpenGLContext = wglCreateContext(hdc); // Create an OpenGL 2.1 context for our device context
wglMakeCurrent(hdc, tempOpenGLContext); // Make the OpenGL 2.1 context current and active
And now we will go ahead and enable GLEW so that we can use our new OpenGL 3.x context creation extension.
GLenum error = glewInit(); // Enable GLEW
if (error != GLEW_OK) // If GLEW fails
return false;
To create our context correctly, we need to set some attributes. These include the major and minor version of the OpenGL version we wish to use, followed by a flag to set this as a forward compatible context. When I say major and minor version, OpenGL versions come in this naming convention “major.minor”, so in OpenGL 3.2, the major version is 3 and the minor version is 2. This can be change to give OpenGL 3.2, 3.1, 3.0 or even 4.0 contexts.
int attributes[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 3, // Set the MAJOR version of OpenGL to 3
WGL_CONTEXT_MINOR_VERSION_ARB, 2, // Set the MINOR version of OpenGL to 2
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, // Set our OpenGL context to be forward compatible
0
};
Now that our attributes are all sorted out, we are going to do a check to see if our OpenGL 3.x context creation extension is available. If it is, we are going to create our OpenGL 3.2 context, if it isn’t, then we are going to stick with the OpenGL 2.1 context we created above. To switch to the new context after it has been created, we need to make sure that no context is current on our window, and then delete the old context, before finally making our new OpenGl 3.2 context current.
if (wglewIsSupported("WGL_ARB_create_context") == 1) { // If the OpenGL 3.x context creation extension is available
hrc = wglCreateContextAttribsARB(hdc, NULL, attributes); // Create and OpenGL 3.x context based on the given attributes
wglMakeCurrent(NULL, NULL); // Remove the temporary context from being active
wglDeleteContext(tempOpenGLContext); // Delete the temporary OpenGL 2.1 context
wglMakeCurrent(hdc, hrc); // Make our OpenGL 3.0 context current
}
else {
hrc = tempOpenGLContext; // If we didn't have support for OpenGL 3.x and up, use the OpenGL 2.1 context
}
Finally, to check that we have created our context correctly, we are just going to pull out the MAJOR and MINOR version numbers for OpenGL and output them to a console.
int glVersion[2] = {-1, -1}; // Set some default values for the version
glGetIntegerv(GL_MAJOR_VERSION, &glVersion[0]); // Get back the OpenGL MAJOR version we are using
glGetIntegerv(GL_MINOR_VERSION, &glVersion[1]); // Get back the OpenGL MAJOR version we are using
std::cout << "Using OpenGL: " << glVersion[0] << "." << glVersion[1] << std::endl; // Output which version of OpenGL we are using On Windows, you won’t get a console for a Win32 Application, but a nifty trick to get console output, is to open up Command Prompt, navigate to your directory with your executable file, and use something like: “program.exe > temp.txt”
This will run your application, and save all output to the file temp.txt. Linux programmers may already be familiar with calls like this. Now we still have three more methods we need to create here before we are done, but luckily for us, they are extremely short. The first I will look at is setupScene, which takes no parameters, and will only set up our OpenGL Clear Color value. I am going to set it to the nice CornFlowerBlue you get when working with XNA. I grew fond of this colour, and it looks nicer than a standard black or white.
/**
setupScene will contain anything we need to setup before we render
*/
void OpenGLContext::setupScene(void) {
glClearColor(0.4f, 0.6f, 0.9f, 0.0f); // Set the clear color based on Microsofts CornflowerBlue (default in XNA)
}
Moving on, we now have our reshapeWindow method. This method will take in two integers, one for the width of our window and the other for the height of our window. It will then set our OpenGLContext objects width and height to these values.
/**
reshapeWindow is called every time our window is resized, and it sets our windowWidth and windowHeight
so that we can set our viewport size.
*/
void OpenGLContext::reshapeWindow(int w, int h) {
windowWidth = w; // Set the window width
windowHeight = h; // Set the window height
}
And the last method for this file, is our renderScene method, which once again takes no arguments. The first thing we need to do, is set the size of our viewport to the size of our window, which is done with a call to glViewport. Next we will clear our color, depth and stencil buffers, before finally swapping our front and back buffers to draw to the screen.
/**
renderScene will contain all our rendering code.
The first thing we are going to do is set our viewport size to fill the entire window.
Next up we are going to clear our COLOR, DEPTH and STENCIL buffers to avoid overlapping
of renders.
Any of your other rendering code will go here.
Finally we are going to swap buffers.
*/
void OpenGLContext::renderScene(void) {
glViewport(0, 0, windowWidth, windowHeight); // Set the viewport size to fill the window
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Clear required buffers
SwapBuffers(hdc); // Swap buffers so we can see our rendering
}
Now we have everything in place for our basic OpenGL 3.2 context, so let’s go and finish off this tutorial with the main.cpp file, which will handle all of our Window creation.
Main.cpp
For the purpose of these tutorials, I am going to assume that we are working with single window applications and am not going to go for a more OO approach to window creation. So let’s get on to creating our window. The first thing we need to do here is to include our common header files, main.h and opengl_3.h. Followed by declaring some variables. We are going to need an instance of an OpenGLContext object, a boolean value to say as to whether or not we are still running our application and a standard HINSTANCE variable for Win32. We are also going to give a method declaration for WndProc which is also required for a Windows application.
#include "main.h"
#include "opengl_3.h"
OpenGLContext openglContext; // Our OpenGL Context class
bool running = true; // Whether or not the application is currently running
HINSTANCE hInstance; // The HINSTANCE of this application
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Standard window callback
The rest of this tutorial, is made up of three methods. The first one here, is required in any Windows application and is responsible for our windows callbacks. We are going to look for calls to resize the window, and for calls to destroy the window. When we find a call to resize the window, we are going to call our openglContext.reshapeWindow method. Lets take a look at the definition for this method.
/**
WndProc is a standard method used in Win32 programming for handling Window messages. Here we
handle our window resizing and tell our OpenGLContext the new window size.
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
return DefWindowProc(hWnd, message, wParam, lParam);
}
In order to look for different window calls, we turn to the message parameter in the method definition. This is a UINT variable, which can be tied to any of the WM_* variables. We are going to need WM_SIZE for window resizing, and WM_DESTROY for calls to destroy the window. I am going to use a simple switch statement to check for these like so:
switch (message) {
case WM_SIZE: // If our window is resizing
{
}
case WM_DESTROY:
{
}
}
When it comes to our window resizing, we are going to need to update our OpenGL context so that it knows the new size of the window. To do this, we can pull out the width and height of the window using calls to LOWORD(lParam) and HIWORD(lParam), which makes things fairly simple for us. Our method now becomes:
/**
WndProc is a standard method used in Win32 programming for handling Window messages. Here we
handle our window resizing and tell our OpenGLContext the new window size.
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_SIZE: // If our window is resizing
{
openglContext.reshapeWindow(LOWORD(lParam), HIWORD(lParam)); // Send the new window size to our OpenGLContext
break;
}
case WM_DESTROY:
{
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
The only thing left to check for now, is when a window is destroyed. Then we need to send a call to PostQuitMessage so that we can exit our application.
/**
WndProc is a standard method used in Win32 programming for handling Window messages. Here we
handle our window resizing and tell our OpenGLContext the new window size.
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_SIZE: // If our window is resizing
{
openglContext.reshapeWindow(LOWORD(lParam), HIWORD(lParam)); // Send the new window size to our OpenGLContext
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
While we now have a method which will handle all of the call backs for our window, we need to actually go ahead and create a window. So I am going to create a new method called createWindow, which will return a boolean value which determines whether or not the window was created successfully. It’s parameters are an LPCWSTR for the title of the window, and two integers, one for the width and one for the height of the window.
bool createWindow(LPCWSTR title, int width, int height) {
return true;
}
Once we have our method skeleton in place, we need to setup some variables for our window. We will need a WNDCLASS, HWND and a DWORD. The WNDCLASS, which I will call windowClass sets all the basic information for a window, such as the icon, cursor, type of redraw, the instance of the owner application, etc. The HWND variable which I will just call hwnd, will hold the unique identifier for the window which we create, and finally the DWORD variable, which I am going to call dwExStyle and that just sets the type of window and that it has an edge. I’m not going to go into detail about all the settings for this, as it’s not an OpenGL topic, its a Win32 topic, so here’s just the code. I have included a quick error check to make sure the windowClass variable can be registered:
Next up, we are going to create our window using the above window class, and then we are going to create our OpenGL context on the window before showing the window and making a quick call to update it.
bool createWindow(LPCWSTR title, int width, int height) {
...
hWnd = CreateWindowEx(dwExStyle, title, title, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, width, height, NULL, NULL, hInstance, NULL);
openglContext.create30Context(hWnd); // Create our OpenGL context on the given window we just created
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
return true;
}
This should now give us a window, with an OpenGL 3 context, and it should also be visible. The last thing we need to do is create a WinMain method which will be used to launch our application. Lets create an empty WinMain method, and then we will go ahead and fill this in. Note that I have left in the MSG variable called msg, just because we use it for our return value.
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow) {
MSG msg;
return (int) msg.wParam;
}
Before we fill this in, I am going to give a brief rundown of what to expect. The first few lines are going to be used to convert the title of the window from a char array to an LPCWSTR, they are just an annoying necessity because the CreateWindowEx method doesn’t support char arrays. The next two lines after this will create our window, and setup our OpenGL context, and we will end with a loop that will continue running for as long as the application does, and will be responsible for rendering and checking for window messages. Lets take a look at the first few lines, which convert our char array.
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow) {
MSG msg;
/**
The following 6 lines of code do conversion between char arrays and LPCWSTR variables
which are used in the Windows API.
*/
char *orig = "OpenGL 3 Project"; // Our windows title
size_t origsize = strlen(orig) + 1;
const size_t newsize = 100;
size_t convertedChars = 0;
wchar_t wcstring[newsize];
mbstowcs_s(&convertedChars, wcstring, origsize, orig, _TRUNCATE);
return (int) msg.wParam;
}
Now let’s make the calls to create our window and setup our OpenGL scene.
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow) {
...
createWindow(wcstring, 500, 500); // Create our OpenGL window
openglContext.setupScene(); // Setup our OpenGL scene
return (int) msg.wParam;
}
And finally we can set up our infinite loop. We will use the Win32 call to PeekMessage to check for calls to our window. If we get a WM_QUIT message we are going to set our running variable to false. Otherwise we are going to translate and dispatch the message, which is done in our callback. And if we don’t have any messages to process, we are going to call our openglContext.renderScene method before returning.
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow) {
...
/**
This is our main loop, it continues for as long as running is true
*/
while (running)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // If we have a message to process, process it
if (msg.message == WM_QUIT) {
running = false; // Set running to false if we have a message to quit
}
else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else { // If we don't have a message to process
openglContext.renderScene(); // Render our scene (which also handles swapping of buffers)
}
}
return (int) msg.wParam;
}
And wow, if you made it this far, give yourself a well deserved pat on the back! It took me a few days to write this tutorial, which is an initial setup on top of which all our other tutorials will build, so once you get it down you are ready to move onto the next. I am also providing Visual Studio 2010 projects for these tutorials, and PDF versions for download. For those of you who have been waiting, I’m sorry it has taken so long, but this is now a 13 page, 4,500+ word introduction to setting up an OpenGL compatible context on a window.
If you have any questions you can comment below or email me at swiftless@gmail.com
VN:F [1.9.3_1094]
Please rate so I know where to improve the site. 1 means needs a lot of improvement, 10 means perfect. If you leave a low rating, please state why. I don't want people just coming to bash the site.
Bump mapping is essential in todays computer games, and computer graphics in general. Would you like to know the best thing about it? It is extremely simple to implement. Bump mapping works by taking an image which stores surface normals on a per-pixel basis. This means that we can apply standard lighting techniques that require a normal value on a per-pixel basis instead of a per-vertex or per-surface basis. This gives our applications a greatly added sense of realism.
How does bump mapping work in OpenGL with GLSL though? GLSL texturing works on a per-texel basis, where a texel can be thought of as a scaled pixel, either larger or smaller, and we get access to a texel at any fragment on an objects surface when we are texturing in GLSL. We already covered texturing in GLSL in a previous tutorial, so I won’t go over it again.
When you look at a normal map, it is generally a blue and purple color. To get your head around normal mapping, all you need to understand is that each pixel in this image file, can be mapped to a normal value. Once you can understand that, normal mapping is extremely straight forward.
Don’t get this tutorial on bump mapping confused with tangent-space bump mapping though. There aren’t many tutorials around that explain the different, but regular bump mapping only cares about the normal maps relation to lights. Tangent space bump mapping takes it a step further and takes into account the objects surfaces in relation-to the light as well as the normal maps relation.
Coding
Enough theory, how can we get it into OpenGL and GLSL. The first thing we will want to do is load in our normal map, and apply it to our object in OpenGL. This requires multi-texturing in OpenGL, which I don’t believe I have covered in GLSL, so I will include that in this tutorial. This code is going to be based on the GLSL texturing tutorial, but you may notice some additions to make it cross-platform between OSX and Windows correctly.
Main.cpp
We are only going to need one new variable for the main.cpp file which will be used to store our normal map texture. This is stored and used just like a normal texture. I am going to call this normal_texture, and define it with our texture variable at the top of our file.
GLuint normal_texture; // Our normal map texture
Then go down to your init method, and remove the current line that creates a texture, and replace it with the following two lines, which will create both of our new
Inside of our init method you may also notice the following code, this is for cross-platform compatibility and simply checks if we are on OSX or not. If not, we initialize glew.
The rest of our code for the main source code file, goes into our cube method, which I have now modified to draw a square instead.
You should already have code to set the first texture, and we are going to adjust this slightly by adding the following line after we set our active texture:
glEnable(GL_TEXTURE_2D);
Our code to set up our first texture should now look like:
Now we need to set up our second texture. This is done by activating the next texture, GL_TEXTURE1 and modifying the other calls to point to our normal map texture. When we do this, we need to make sure that our glUniform1i for our normal map location in the shader, has the value 1. When multi-texturing in GLSL, each texture used will have an incremented value in the shader. Our final code will look like:
I won’t explain the code for drawing the square, as I have done this in previous tutorials, so I will jump to after our square. We will need to do some cleaning up, as we don’t want our texture states to mess up when we render other objects later on. All I am doing to clean up, is setting the active texture, binding it to nothing, and then disabling texturing on this texture. Note that I am doing this in reverse to how I have set the textures. You can do this in any order, but I like this as I find it easier to follow (count up on initialization, and then count down on de-initialization).
Our vertex shader requires absolutely no extra calls, so just use the one provided in the previous texturing tutorial.
Shader.frag
Shader.frag is going to contain all of our bump mapping calculations. To get this going, we need to add a new uniform variable, which will load in our normal mapping texture:
uniform sampler2D normal_texture;
Now that we have access to our normal map, lets load it in. For a normal, we only require a 3 value vector, or vec3 in GLSL, and we are going to normalize this to make sure the final normal vector is of unit length. In other words, it has a length of 1.0. But in this line take note of the (* 2.0 – 1.0). When we read in a texture value, we get a number between 0.0 and 1.0 as color cannot have a negative value. This then multiplies the value by 2.0, giving us values between 0.0 and 2.0, and then subtracts 1.0 so we get our final values in the range of -1.0 to 1.0. We have to do this manipulation as a normal vector is 3D and can be pointing in any direction. It’s a nifty little trick which allows us to convert a normal map texture file to a normal map.
// Extract the normal from the normal map
vec3 normal = normalize(texture2D(normal_texture, gl_TexCoord[0].st).rgb * 2.0 - 1.0);
Since we now have the normal value for the current pixel, I am going to set up a quick light, located at the top right of the scene, and also a little towards us. Usually you would base this on actual lights in your scene, and to do so, take a look at my GLSL lighting tutorial.
// Determine where the light is positioned (this can be set however you like)
vec3 light_pos = normalize(vec3(1.0, 1.0, 1.5));
Finally we can get to some familiar code. We are going to calculate the diffuse value for the current pixel, based on our normal extracted from the normal map. Then we are going to multiply the diffuse value against our color texture, and assign the output to our gl_FragColor variable.
// Calculate the lighting diffuse value
float diffuse = max(dot(normal, light_pos), 0.0);
vec3 color = diffuse * texture2D(color_texture, gl_TexCoord[0].st).rgb;
// Set the output color of our current pixel
gl_FragColor = vec4(color, 1.0);
And now you should have a perfectly working bump mapping shader. If you have any questions, feel free to email me at swiftless@gmail.com or leave a comment below.
#if ( (defined(__MACH__)) && (defined(__APPLE__)) )
#include <stdlib.h>
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#include <OpenGL/glext.h>
#else
#include <stdlib.h>
#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include <GL/glext.h>
#endif
#include "shader.h"
Shader shader;
GLuint texture;
GLuint normal_texture; // Our normal map texture
GLuint LoadTexture( const char * filename, int width, int height )
{
GLuint texture;
unsigned char * data;
FILE * file;
//The following code will read in our RAW file
file = fopen( filename, "rb" );
if ( file == NULL ) return 0;
data = (unsigned char *)malloc( width * height * 3 );
fread( data, width * height * 3, 1, file );
fclose( file );
glGenTextures( 1, &texture ); //generate the texture with the loaded data
glBindTexture( GL_TEXTURE_2D, texture ); //bind the texture to it’s array
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); //set texture environment parameters
//And if you go and use extensions, you can use Anisotropic filtering textures which are of an
//even better quality, but this will do for now.
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//Here we are setting the parameter to repeat the texture instead of clamping the texture
//to the edge of our shape.
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
//Generate the texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
free( data ); //free the texture
return texture; //return whether it was successfull
}
void FreeTexture( GLuint texture )
{
glDeleteTextures( 1, &texture );
}
void init(void) {
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
#if ( (defined(__MACH__)) && (defined(__APPLE__)) )
#else
glewInit();
#endif
shader.init("shader.vert", "shader.frag");
texture = LoadTexture("colour_map.raw", 256, 256);
normal_texture = LoadTexture("normal_map.raw", 256, 256);
}
void cube (void) {
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
int texture_location = glGetUniformLocation(shader.id(), "color_texture");
glUniform1i(texture_location, 0);
glBindTexture(GL_TEXTURE_2D, texture);
glActiveTexture(GL_TEXTURE1);
glEnable(GL_TEXTURE_2D);
int normal_location = glGetUniformLocation(shader.id(), "normal_texture");
glUniform1i(normal_location, 1);
glBindTexture(GL_TEXTURE_2D, normal_texture);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex3f(-1.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd();
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
}
void display (void) {
glClearColor (0.0,0.0,0.0,1.0);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
gluLookAt (0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
shader.bind();
cube();
shader.unbind();
glutSwapBuffers();
}
void reshape (int w, int h) {
glViewport (0, 0, (GLsizei)w, (GLsizei)h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (60, (GLfloat)w / (GLfloat)h, 1.0, 100.0);
glMatrixMode (GL_MODELVIEW);
}
int main (int argc, char **argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); //set up the double buffering
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("A basic OpenGL Window");
init();
glutDisplayFunc(display);
glutIdleFunc(display);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}
Shader.vert
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
// Set the position of the current vertex
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
Shader.frag
uniform sampler2D color_texture;
uniform sampler2D normal_texture;
void main() {
// Extract the normal from the normal map
vec3 normal = normalize(texture2D(normal_texture, gl_TexCoord[0].st).rgb * 2.0 - 1.0);
// Determine where the light is positioned (this can be set however you like)
vec3 light_pos = normalize(vec3(1.0, 1.0, 1.5));
// Calculate the lighting diffuse value
float diffuse = max(dot(normal, light_pos), 0.0);
vec3 color = diffuse * texture2D(color_texture, gl_TexCoord[0].st).rgb;
// Set the output color of our current pixel
gl_FragColor = vec4(color, 1.0);
}
VN:F [1.9.3_1094]
Please rate so I know where to improve the site. 1 means needs a lot of improvement, 10 means perfect. If you leave a low rating, please state why. I don't want people just coming to bash the site.
Currently Swiftless GLSL Shader Developer is in it’s first public release, and is currently in alpha status, meaning it is not complete and may contain errors. By downloading this software, you are taking full responsibility for any harm it may cause to your computer.
I have included an example project with the application, which shows how to use the geometry shaders, post processing and performs a simple blur effect. I will have more projects to put up as I progress with the application, you can also email me any projects you would like to share and I will put them up on this page, and may even put them in with a release of the application.
If you find any errors or have any suggestions for this software, please email me at swiftless@gmail.com
* Support for OpenGL 2.1 (As supported by JOGL) * Fragment Shader, Vertex Shader and Geometry Shader abilities * Post processing capabilities Access to first render as either texture or depth texture * Uses framebuffers for speed * Syntax highlighting * Project support * Real-time shader compilation * Real-time error reporting and shader validation * Support for all 8 lights in the basic OpenGL pipeline * Multiple texture support
Future Features:
* Multiple rendering passes * Backcolor settings
Known Bugs:
* Appears to run slow after several minutes * Custom uniform variables are not yet accessible * Cube shape doesn’t appear * Sphere doesn’t render correctly
Screenshots:
Swiftless GLSL Shader Developer User Interface
Swiftless GLSL Shader Developer Error Reporting
Swiftless GLSL Shader Developer Projects
Swiftless GLSL Shader Developer Features
VN:F [1.9.3_1094]
Please rate so I know where to improve the site. 1 means needs a lot of improvement, 10 means perfect. If you leave a low rating, please state why. I don't want people just coming to bash the site.
Frame buffers are one of those mythical things that we have all heard of, but many beginner OpenGL developers avoid because there is not much information about them, and they can be confusing at first. A frame buffer can be thought of as another window that we render to, but we don’t end up seeing.
Now you might be thinking, if we draw something, but we don’t end up seeing it, then what is the point? Have you ever heard of off-screen rendering? I might ask you this instead, have you ever seen a reflection in a game of a live scene? Well, to get this reflection, we typically have to render the scene from the angle of the reflection first, and then we turn that render into a texture and bind it to whatever shape has the reflection attached to it. This initial rendering of the reflection is typically done in a frame buffer for speed, as we can bind a frame buffer straight to a texture.
If you have ever heard of pixel buffers, don’t get them confused with frame buffers, they are different, but can be used together. I won’t go into pixel buffers here, I might write a tutorial on them later on, but they are highly useful for asynchronous texture reads and writes. I once had a project, and part of it required I write to two frame buffers and read back the data, and then perform operations on this data and push it back to the GPU as a texture as quickly as possible. Using a combination of frame buffers and pixel buffers, I went from approximately 5 frames per second doing this, to 30 frames per second, and if the demand arises, I will post a tutorial on how to do this. It was a case where offloading the information to the GPU was not possible.
You should now have an idea of what frame buffers are, in short, they are a rendering context that can be bound to a texture, so let’s start coding them and see what we come up with. I am going to aim for a quad, with a texture of a rotating cube on it. It is a simple example, but highly useful, as you can imagine, the entire scene can be rendered into a frame buffer, and then placed onto a quad for some post processing.
Code
Frame buffer objects are stored like textures. OpenGL will store the information on the graphics card, and it will return an ID associated with that texture for us to place inside an unsigned integer. Also, as seen with textures, the frame buffer ID of 0 (zero) is reserved by OpenGL and is used to unbind the current frame buffer. Also, frame buffers can have several buffers attached. The default buffer is the colour buffer, but you can also bind depth buffers, stencil buffers, etc. So let’s create some variables to hold our frame buffer, frame buffers depth buffer and the texture we are going to store our frame buffer in.
unsigned int fbo; // The frame buffer object
unsigned int fbo_depth; // The depth buffer for the frame buffer object
unsigned int fbo_texture; // The texture object to write our frame buffer object to
After we have all the variables required to create and use our frame buffers (yes, we only need three variables), we are going to add some extra variables. First, we need two integer values for the width and height of our GLUT window, this is because when you create a frame buffer, you need to specify the width and the height of the texture, and when you use the frame buffer, you need to change the viewport size to match the texture size. Because frame buffers are independent of the size of the GLUT window, you can use them to create higher or lower resolution textures than your regular scene. Typically you would use a lower resolution texture for effects such as reflections so that it renders quicker. The last extra variable we are going to use will just hold how much our teapot in our frame buffer scene will rotate.
int window_width = 500; // The width of our window
int window_height = 500; // The height of our window
float rotation_degree = 0.0f; // The angle of rotation in degrees for our teapot
The next step we have to do is to create/initialize our frame buffer, the associated depth buffer, and the texture we are going to render to. Typically it is done in the following order:
Create the Depth Buffer that we are going to use with our frame buffer.
Create the Texture that we are going to bind our frame buffer to.
Create the actual Frame Buffer.
Bind the texture to the frame buffer.
Bind the depth buffer to the frame buffer.
Check for errors.
You can check for errors along the way if you wish, but I will just be using one check to make sure our final frame buffer is complete.
Frame Buffer – Depth Buffer
Let’s create the depth buffer we are going to use. To begin with, create a method call initFrameBufferDepthBuffer. This method is going to take no parameters and is not going to return anything. This method is going to contain all code related to the depth buffer we are going to use.
void initFrameBufferDepthBuffer(void) {
}
Inside of this method to create the depth buffer, the first call we need to make will create a render buffer for OpenGL to use, and is very similar to the code used to create textures.
glGenRenderbuffersEXT(1, &fbo_depth); // Generate one render buffer and store the ID in fbo_depth
This will create a render buffer, and store the ID in our fbo_depth variable for us to access. Once the render buffer is created for the depth buffer, we then need to bind the buffer so that we can play with it and once we are finished with it, we need to bind the null render buffer, which takes the value of 0. When we are binding a frame buffer, it is once again very similar to texturing, so let’s place our binding code inside our method to fill between.
void initFrameBufferDepthBuffer(void) {
glGenRenderbuffersEXT(1, &fbo_depth); // Generate one render buffer and store the ID in fbo_depth
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo_depth); // Bind the fbo_depth render buffer
...
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); // Unbind the render buffer
}
Next, we need to fill in the … between the binding of the render buffers. These next two lines are the lines that tell OpenGL that this render buffer will be used for depth. The first line tells OpenGL that we are going to be storing the depth component, and we are going to monitor the entire size of the window. The next line then says that we are going to use fbo_depth to render the depth buffer/attachment.
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, window_width, window_height); // Set the render buffer storage to be a depth component, with a width and height of the window
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo_depth); // Set the render buffer of this buffer to the depth buffer
Frame Buffer – Texture
Recapping on what we have done, we have created a render buffer that will store the depth component of the buffer that it is attached to. Now we need to create the texture that we want to store our frame buffer in. To do this, let’s create another method similar to the one we just created, but I am going to call this one initFrameBufferTexture. Because we are only creating a texture here, I am not really going to explain this code, the code comments should outline what is going on if you don’t know about texturing in OpenGL (in which case, you are diving in fairly deep straight out).
void initFrameBufferTexture(void) {
glGenTextures(1, &fbo_texture); // Generate one texture
glBindTexture(GL_TEXTURE_2D, fbo_texture); // Bind the texture fbo_texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_width, window_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); // Create a standard texture with the width and height of our window
// Setup the basic texture parameters
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Unbind the texture
glBindTexture(GL_TEXTURE_2D, 0);
}
Frame Buffer – Initialization
Finally, we need one more method; this one will be called initFrameBuffer and will make calls to the above methods we have created, as well as creating the frame buffer and attaching the texture and the depth buffer.
After making the calls to our above methods, we need to create the frame buffer, and bind it. This is extremely similar to how we created the render buffer for the depth component, and how we create textures, so the following should look vaguely familiar.
glGenFramebuffersEXT(1, &fbo); // Generate one frame buffer and store the ID in fbo
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); // Bind our frame buffer
…
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // Unbind our frame buffer
This code will generate one frame buffer, and then bind it so we can modify it, and then finally it will unbind it. This is fairly straight forward, and the next two calls will attach the texture and render buffer to our frame buffer. First off, let’s attach the texture to the frame buffer.
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, fbo_texture, 0); // Attach the texture fbo_texture to the color buffer in our frame buffer
Now that we have a texture, theoretically we can use this straight out, but you won’t get any depth information. So we need to go ahead and attach the depth render buffer we created called fbo_depth, to our frame buffer.
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo_depth); // Attach the depth buffer fbo_depth to our frame buffer
And now we have a frame buffer, which outputs to a texture and also has a depth buffer, which we can use. Well, we think we do, so first let’s check if our frame buffer is complete and there were no problems. To do this, we can make a call to glCheckFramebufferStatusEXT while our frame buffer is bound, and this will give us back a GLenum with the status of our frame buffer. For our frame buffer to be complete, we need to check that the status is equal to GL_FRAMEBUFFER_COMPLETE_EXT. We can do this with a simple if statement. Note that if you want the output to the console, you need to include <iostream> into your file.
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); // Check that status of our generated frame buffer
if (status != GL_FRAMEBUFFER_COMPLETE_EXT) // If the frame buffer does not report back as complete
{
std::cout << "Couldn't create frame buffer" << std::endl; // Make sure you include <iostream>
exit(0); // Exit the application
}
General Initialization
Because in this example, we want to use the frame buffer constantly, I am going to create an init method which will call the initFrameBuffer method, and init will be called straight after we initialize GLEW, which is straight after we call glutCreateWindow. So here is our init method, which in this example, enables texturing, enables depth testing, and then initializes our frame buffer.
void init(void) {
glEnable(GL_TEXTURE_2D); // Enable texturing so we can bind our frame buffer texture
glEnable(GL_DEPTH_TEST); // Enable depth testing
initFrameBuffer(); // Create our frame buffer object
}
Next up, here is an example of my main method, which calls glewInit and calls our own init method.
int main (int argc, char **argv) {
…
glutCreateWindow ("You’re first OpenGL Window"); // Set the title for the window
if (GLEW_OK != glewInit()) {
std::cout << "Couldn't initialize GLEW" << std::endl;
exit(0);
}
init();
…
}
Frame Buffer – Usage
To use our frame buffer, I am first going to give you the display method, and whilst you could put all your code in here, I am going to make a call to a method to render our teapot scene. This display method builds upon the tutorial for creating a square.
So it looks something like the following code, which will create a square with texture coordinates, and I have placed it back 2 units so that it fills up most of the screen. Keep in mind, that I am not doing anything with the frame buffer at the moment; this is just a standard display method.
void display (void) {
keyOperations();
glClearColor(1.0f, 0.0f, 0.0f, 1.0f); // Clear the background of our window to red
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //Clear the colour buffer (more buffers later on)
glLoadIdentity(); // Load the Identity Matrix to reset our drawing locations
glTranslatef(0.0f, 0.0f, -2.0f);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex3f(-1.0f, -1.0f, 0.0f); // The bottom left corner
glTexCoord2f(0.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, 0.0f); // The top left corner
glTexCoord2f(1.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 0.0f); // The top right corner
glTexCoord2f(1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f); // The bottom right corner
glEnd();
glutSwapBuffers();
}
Now we will go ahead and bind our frame buffer texture to the quad. This is done just like binding a regular texture, because it is just a regular texture. The only difference between the texture we use for our frame buffer, and a standard texture we load in from a file, is that this texture is filled in on the GPU by OpenGL.
So right before your quad begins, bind your frame buffer texture, and then after you finish drawing your quad, unbind it.
You should be able to run this now, but unfortunately because we haven’t rendered anything into the frame buffer, nothing will appear on our quad, it will be as if we never bound a texture. So let’s create a method where we are going to render our frame buffer. I am going to call this renderTeapotScene, because I am using it to render a teapot.
void renderTeapotScene(void) {
}
renderTeapotScene is going to be called from inside our display method, right after our keyOperations and before we do any actual rendering.
Inside of our renderTeapotScene method, we need to first bind our frame buffer so that we can render to it, and then unbind it when we are finished rendering. I am also going to add in some extra code at the very end of this method, just to update the amount in which we are going to rotate our teapot.
So far, everything should be fine, except when you bind a frame buffer, you need to set the size of the viewport that the frame buffer will render to. This is done with a call to glViewport, but first I am going to do some more management orientated activities, and store the current state of our GL_ENABLE_BIT and our GL_VIEWPORT_BIT. This is so that when we finish, we can put these back to how they were and not having any glEnable or glViewport calls modify our regular rendering. It is also wise to store information such as GL_LIGHTING_BIT if you are using lighting, and any other states you might need.
void renderTeapotScene(void) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); // Bind our frame buffer for rendering
glPushAttrib(GL_VIEWPORT_BIT | GL_ENABLE_BIT); // Push our glEnable and glViewport states
glPopAttrib(); // Restore our glEnable and glViewport states
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // Unbind our texture
rotation_degree += 0.5f;
if (rotation_degree > 360.0f)
rotation_degree = 0.0f;
}
It is our calls to glPushAttrib and glPopAttrib which will save and restore our states. The next step as I said is to set the viewport size for rendering, and then I am going to clear the colour buffer, depth buffer and reset the model view matrix, just like you would in your regular display method.
void renderTeapotScene(void) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); // Bind our frame buffer for rendering
glPushAttrib(GL_VIEWPORT_BIT | GL_ENABLE_BIT); // Push our glEnable and glViewport states
glViewport(0, 0, window_width, window_height); // Set the size of the frame buffer view port
glClearColor (0.0f, 0.0f, 1.0f, 1.0f); // Set the clear colour
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the depth and colour buffers
glLoadIdentity(); // Reset the modelview matrix
glPopAttrib(); // Restore our glEnable and glViewport states
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // Unbind our texture
rotation_degree += 0.5f;
if (rotation_degree > 360.0f)
rotation_degree = 0.0f;
}
This should be starting to look a little familiar inside of our frame buffer and push attributes calls, and you are probably right, everything you do from now on is just like rendering a normal scene, only you don’t need to call glFlush or glutSwapBuffers like you would with a regular render as we are not pushing the output to the screen. So now all we need to do is add the code in for our teapot rendering. I am going to translate it back five units, and then rotate it along the x and the y axis.
void renderTeapotScene(void) {
…
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the depth and colour buffers
glLoadIdentity(); // Reset the modelview matrix
glTranslatef(0.0f, 0.0f, -5.0f); // Translate back 5 units
glRotatef(rotation_degree, 1.0f, 1.0f, 0.0f); // Rotate according to our rotation_degree value
glutSolidTeapot(1.0f); // Render a teapot
glPopAttrib(); // Restore our glEnable and glViewport states
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // Unbind our texture
…
}
If you followed everything as I have said correctly, then you should be able to compile this and run it. You should then see a quad with a red background, with a texture of a spinning teapot with a blue background attached to the quad.
Well done if you managed that, and now have your head around frame buffers!
#include <GL/glew.h> // Include the GLEW header file
#include <GL/glut.h> // Include the GLUT header file
#include <iostream> // Allow us to print to the console
bool* keyStates = new bool[256]; // Create an array of boolean values of length 256 (0-255)
unsigned int fbo; // The frame buffer object
unsigned int fbo_depth; // The depth buffer for the frame buffer object
unsigned int fbo_texture; // The texture object to write our frame buffer object to
int window_width = 500; // The width of our window
int window_height = 500; // The height of our window
float rotation_degree = 0.0f; // The angle of rotation in degrees for our teapot
void initFrameBufferDepthBuffer(void) {
glGenRenderbuffersEXT(1, &fbo_depth); // Generate one render buffer and store the ID in fbo_depth
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo_depth); // Bind the fbo_depth render buffer
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, window_width, window_height); // Set the render buffer storage to be a depth component, with a width and height of the window
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo_depth); // Set the render buffer of this buffer to the depth buffer
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); // Unbind the render buffer
}
void initFrameBufferTexture(void) {
glGenTextures(1, &fbo_texture); // Generate one texture
glBindTexture(GL_TEXTURE_2D, fbo_texture); // Bind the texture fbo_texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_width, window_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); // Create a standard texture with the width and height of our window
// Setup the basic texture parameters
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Unbind the texture
glBindTexture(GL_TEXTURE_2D, 0);
}
void initFrameBuffer(void) {
initFrameBufferDepthBuffer(); // Initialize our frame buffer depth buffer
initFrameBufferTexture(); // Initialize our frame buffer texture
glGenFramebuffersEXT(1, &fbo); // Generate one frame buffer and store the ID in fbo
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); // Bind our frame buffer
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, fbo_texture, 0); // Attach the texture fbo_texture to the color buffer in our frame buffer
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo_depth); // Attach the depth buffer fbo_depth to our frame buffer
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); // Check that status of our generated frame buffer
if (status != GL_FRAMEBUFFER_COMPLETE_EXT) // If the frame buffer does not report back as complete
{
std::cout << "Couldn't create frame buffer" << std::endl; // Output an error to the console
exit(0); // Exit the application
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // Unbind our frame buffer
}
void init(void) {
glEnable(GL_TEXTURE_2D); // Enable texturing so we can bind our frame buffer texture
glEnable(GL_DEPTH_TEST); // Enable depth testing
initFrameBuffer(); // Create our frame buffer object
}
void keyOperations (void) {
if (keyStates['a']) { // If the a key has been pressed
// Perform 'a' key operations
}
}
void renderTeapotScene(void) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); // Bind our frame buffer for rendering
glPushAttrib(GL_VIEWPORT_BIT | GL_ENABLE_BIT); // Push our glEnable and glViewport states
glViewport(0, 0, window_width, window_height); // Set the size of the frame buffer view port
glClearColor (0.0f, 0.0f, 1.0f, 1.0f); // Set the clear colour
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the depth and colour buffers
glLoadIdentity(); // Reset the modelview matrix
glTranslatef(0.0f, 0.0f, -5.0f); // Translate back 5 units
glRotatef(rotation_degree, 1.0f, 1.0f, 0.0f); // Rotate according to our rotation_degree value
glutSolidTeapot(1.0f); // Render a teapot
glPopAttrib(); // Restore our glEnable and glViewport states
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // Unbind our texture
rotation_degree += 0.5f;
if (rotation_degree > 360.0f)
rotation_degree = 0.0f;
}
void display (void) {
keyOperations(); // Perform any key presses
renderTeapotScene(); // Render our teapot scene into our frame buffer
glClearColor(1.0f, 0.0f, 0.0f, 1.0f); // Clear the background of our window to red
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //Clear the colour buffer (more buffers later on)
glLoadIdentity(); // Load the Identity Matrix to reset our drawing locations
glTranslatef(0.0f, 0.0f, -2.0f);
glBindTexture(GL_TEXTURE_2D, fbo_texture); // Bind our frame buffer texture
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex3f(-1.0f, -1.0f, 0.0f); // The bottom left corner
glTexCoord2f(0.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, 0.0f); // The top left corner
glTexCoord2f(1.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 0.0f); // The top right corner
glTexCoord2f(1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f); // The bottom right corner
glEnd();
glBindTexture(GL_TEXTURE_2D, 0); // Unbind any textures
glutSwapBuffers();
}
void reshape (int width, int height) {
glViewport(0, 0, (GLsizei)width, (GLsizei)height); // Set our viewport to the size of our window
glMatrixMode(GL_PROJECTION); // Switch to the projection matrix so that we can manipulate how our scene is viewed
glLoadIdentity(); // Reset the projection matrix to the identity matrix so that we don't get any artifacts (cleaning up)
gluPerspective(60, (GLfloat)width / (GLfloat)height, 1.0, 100.0); // Set the Field of view angle (in degrees), the aspect ratio of our window, and the new and far planes
glMatrixMode(GL_MODELVIEW); // Switch back to the model view matrix, so that we can start drawing shapes correctly
}
void keyPressed (unsigned char key, int x, int y) {
keyStates[key] = true; // Set the state of the current key to pressed
}
void keyUp (unsigned char key, int x, int y) {
keyStates[key] = false; // Set the state of the current key to not pressed
}
int main (int argc, char **argv) {
glutInit(&argc, argv); // Initialize GLUT
glutInitDisplayMode (GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); // Set up a basic display buffer (only single buffered for now)
glutInitWindowSize (500, 500); // Set the width and height of the window
glutInitWindowPosition (100, 100); // Set the position of the window
glutCreateWindow ("You’re first OpenGL Window"); // Set the title for the window
if (GLEW_OK != glewInit()) {
std::cout << "Couldn't initialize GLEW" << std::endl;
exit(0);
}
init();
glutDisplayFunc(display); // Tell GLUT to use the method "display" for rendering
glutIdleFunc(display); // Tell GLUT to use the method "display" for rendering
glutReshapeFunc(reshape); // Tell GLUT to use the method "reshape" for reshaping
glutKeyboardFunc(keyPressed); // Tell GLUT to use the method "keyPressed" for key presses
glutKeyboardUpFunc(keyUp); // Tell GLUT to use the method "keyUp" for key up events
glutMainLoop(); // Enter GLUT's main loop
}
VN:F [1.9.3_1094]
Please rate so I know where to improve the site. 1 means needs a lot of improvement, 10 means perfect. If you leave a low rating, please state why. I don't want people just coming to bash the site.
This is a really simple change I am going to implement, which will give us a crisper looking terrain, while also giving us a constant 60 frames per second I am going to add a variable here which will be called hLOD:
// int hLOD;
This is going to be a public variable which we are going to set before we create our terrain. It is going to be a number which is a power of 2. Eg: 2, 4, 8, 16. I am going to use it to draw every nth vertex, where n is the hLOD variable. So we are going to draw every hLOD vertex if that makes sense. The reason this speeds up the terrain rendering should be obvious, the less we have to draw, the faster it will go. But it makes it crisper as well, because if you run the previous tutorials and get up close to the terrain, you will find that it is really jagged. This also smooths out the terrain surface for us.
public: bool Create(char*hFileName, const int hWidth, const int hHeight);
void Render(void);
int hLOD;
BYTE hHeightField[1024][1024];
};
Heightfield.CPP File:
In our heightfield.cpp file, all our changes are going to take place in the Create function. First, we need to change the vertex count that we will be using throughout to tell us how many vertices we need, we do this by changing the following line:
The next change we need to make is to the increments in our loops. Instead of incrementing by 1 for every vertex, we need to increment by every hLOD to get every hLOD’th vertex like so:
for (int hMapX = 0; hMapX < hmWidth; hMapX+=hLOD){
for (int hMapZ = 0; hMapZ < hmHeight; hMapZ+=hLOD){
After we have our loop setup, we need to make sure that our polygons span over every hLOD vertex, so instead of incrementing our vertices X and Z positions by 1, we need to increment them by our hLOD value as follows:
We are only going to make 1 change to our main.cpp file in this tutorial. This is the addition of the line:
// hField.hLOD = 8;
In our Init function, right before we call the creation of our heightfield. I chose the number 8 because after testing different numbers, I found 8 to give good frame rates, and still look really nice.
And that is it for now. Check out the next tutorial on adding a bump map texture to the heightfield for dynamic lighting on the terrain here *coming soon*.
Please rate so I know where to improve the site. 1 means needs a lot of improvement, 10 means perfect. If you leave a low rating, please state why. I don't want people just coming to bash the site.
VBO’s. Well well, this means a total change to our Render function, a new Init function and a few new classes and a few new variables. This is going to be a major change to our code, but in this case, with change comes progress, and wouldn’t you love to go from the current 5 frames per second, to say, 30 or 60? I know I would So lets get to it.
First off, we are going to create a couple of extra classes. The first we are going to call Vert and will hold all our vertices for our terrain, and the second is going to be called TexCoord and guess what?! It will hold all of our texture coordinates for our terrain Lets take a look at the code we need to add:
First our Vert class, which needs to hold the x, y and z coordinates for our terrain vertices: // class Vert{ // public: // float x; // float y;
// float z; // };
And second we have our TexCoord class which holds the u and v texture coordinates for our terrain: // class TexCoord{ // public: // float u;
// float v; // };
Now I don’t know about you, but basic C++ knowledge explains the above simply enough. I’m going to assume you have at least some idea of what classes are. So lets go take a look at the changes to our SwiftHeightField class, starting off with our new variables. All our variables are going to be private to only our class functions, and will all be used for the creation and use of our Vertex Buffer Objects. So first off we have vhVertexCount. This will hold how many vertices our terrain has and is calculated by the width * height of our heightfield.raw file. Next up is vhVertices, which attached to our new Vert class will hold all our vertices, and finally we have vhTexCoords which used with our TexCoord class will hold all of the texture coordinates we are going to need:
// int vhVertexCount; // Vert *vhVertices; // TexCoord *vhTexCoords;
Now lets look at the last two variables we are going to need to add to our heightfield.h file:
// unsigned int vhVBOVertices; // unsigned int vhVBOTexCoords;
These are going to be the buffers used for the creation of our Vertex Buffer Object. Please see the ‘Vertex Buffer Object’ tutorial (coming soon) for a possibly more indepth tutorial on how these work. As for now, lets look at the new function we are going to add. This is going to be called Init and guess what?! It is going to Initialize our Vertex Buffer Object for us
// bool Init(void);
This will be in the private section of our class and will return true if our program is able to create our Vertex Buffer Object
1. 2. 3. 4. 5. 6. 7.
8. 9. 10. 11. 12. 13. 14. 15. 16.
17. 18. 19. 20. 21. 22. 23. 24. 25.
26. 27. 28. 29. 30. 31. 32. 33. 34.
35. 36. 37. 38. 39.
#include<windows.h>
class Vert{
public:
float x;
float y;
float z; };
class TexCoord{
public:
float u;
float v;
};
class SwiftHeightField {
private:
int hmHeight;
int hmWidth;
int vhVertexCount;
Vert *vhVertices;
TexCoord *vhTexCoords;
unsignedint vhVBOVertices;
unsignedint vhVBOTexCoords;
unsignedint tID[2];
bool Init(void);
public:
bool Create(char*hFileName, const int hWidth, const int hHeight);
void Render(void);
BYTE hHeightField[1024][1024];
};
Heightfield.CPP File:
Who’s ready for a major code overhaul?! I know I am So without further ado:
Starting off, we are going to add a couple of new header files, these are the glew.h and glext.h files, used for the incorporation of OpenGL extensions within our program. They just let us call the extensions without us having to link them to OpenGL manually. So we have the new lines:
// #include <GL/glew.h> // #include <GL/glext.h>
Simple enough so far? Good, now lets look at our new Init function. The following code is the outline of our function. Pretty bland isn’t it?
// bool SwiftHeightField::Init(void){
// return true; // }
Now lets look at generating a Vertex Buffer Object for our vertices First off, we need to call glGenBuffersARB to tell OpenGL that we want to generate a buffer for our Vertex Buffer Object:
// glGenBuffersARB(1, &vhVBOVertices);
Then we need to bind that buffer as an array for OpenGL to fill with data:
The last line here is telling OpenGL to fill our current open buffer vhVBOVertices, with our vertices vhVertices, the size of our vertex count vhVertexCount, multiplied by 3 (for our x, y and z coordinates), then multiplied by the size of a float variable. We set our Vertex Buffer Object to GL_STATIC_DRAW_ARB, because we don’t plan on updating/changing the vertices inside our Vertex Buffer Object as a terrain is generally static (unless you want to get into destructible terrain).
After we have generated, bound and filled our buffer, we are going to want to just clean up after ourselves and delete all the information in vhVertices, and set it to nothing to save system resources:
// delete [] vhVertices; // vhVertices = NULL;
Did you get all that? If not, you can email me, but for now lets continue the tutorial After you have done this for your vertices, you are going to want to do the same for our texture coordinates. This is done the exact same way, just changing which variables we are using for what:
Just make sure here, that you change the 3 from the previous code, to a 2. This is because last time with our vertices, we were working with x, y and z floats for our coordinates, now we are only working with u and v floats for our texture coordinates. Vertices have an extra third dimension to work with, while textures only work in two dimensions. And also remember to clean up after with this as well:
// delete [] vhTexCoords;
// vhTexCoords = NULL;
Now let us move onto our Create function. This is where the next major change occurs. Not in the loading of the terrain, but the loading of the terrain data into our vhVertices and vhTexCoords variables for use in our Init function.
The first one we are going to look at is the preperation of our vhVertices and vhTexCoords variables. First off we are going to declare just how many vertices our Vertex Buffer Object will be using. This is done by multiplying the height by the width by 6 of the terrain. We need to multiply by 6 because we are using triangles, are two triangles make up a ‘patch’ or polygon and there are 3 vertices in each triangle. The extra code division of (1 * 1) will be used later on when I add some REALLY basic Level Of Detail optimization to just lower how many polygons are drawn. At this stage, we are drawing every polygon at once, so we divide it by (1 * 1) = 1 to get our initial number of vertices. Next up we set our vhVertices variable to an array the size of the number of vertices that we have. And we do the same for our texture coordinates.
After this, we have a few temporary variables, these are nIndex, flX and flZ. These are going to hold which vertex we are up to and which vertex we are working on.
// int nIndex = 0; // float flX; // float flZ;
Now we are going to enter a couple of loops that will go through our terrain from left to right, and front to back (entirely open to interpretation).
// for (int hMapX = 0; hMapX < hmWidth; hMapX++){
// for (int hMapZ = 0; hMapZ < hmHeight; hMapZ++){
Inside our loops we are going to add another third loop, which will go through each vertex of the current triangle we are up to.
// for (int nTri = 0; nTri < 6; nTri++){
Now while we are looping through everything, here is where we calculate everything we need. First off we are going to temporarily work out which X and Z coordinate we are up to for our vertex. The following two lines of code read (in pseudocode):
if nTri = 1 or 2 or 5{ hMapX = hMapX + 1; } else { hMapX = hMapX;
}
And the same goes for the Z value we are currently up to, only now using triangles 2, 4 and 5.
These next three lines, are simply setting the x, y and z values of our current vertex in vhVertices to what it would be if we were just plain out using our render code from the previous tutorials, only difference is I have condensed it with the above lines.
And the final line of nIndex++ just increments which vertex we are actually up to.
// nIndex++; // } // } // }
Now while we are still in our Create function, we need to call our Init function now that we have everything read in and ready to transfer to our Vertex Buffer Objects. To do this, I just call the line:
// Init();
At some stage after all of the above code Now let us move onwards to our Render function!
Now to start off, we are going to just delete EVERYTHING from inside our Render function and start from scratch Hehe. Sounds like alot of fun now don’t it? Of course it is! We LOVE rewriting functions don’t we?! Anyways…. Now that you are prepared, it’s actually not that tricky. Because we are working with Vertex Buffer Objects, we also start working with Client States. These work in exactly the same way (syntax wise) as glEnable and glDisable. Only now we have glEnableClientState and glDisableClientState. So first off we are going to enable our texture coordinate array client state, enable texturing and then bind our texture like so:
Next off, we need to bind which vertex buffer object goes with this texture coordinate client state. This is our vhVBOTexCoords vertex buffer object that we created in our Init function. After we have bound which buffer object we are going to use, we need to call glTexCoordPointer to tell OpenGL that this is a texture coordinate pointer.
The next three lines are similar, only this time we are working with our vertex buffer, and not our texture coordinate buffer. So we first need to enable the client state we are going to be using:
// glEnableClientState(GL_VERTEX_ARRAY);
Then we have to bind our buffer vhVBOVertices and call glVertexPointer. Now that we have everything set up, it is time to draw our Vertex Buffer Object.
We do this with the following line. Yes, it is only one line of code to actually draw it, everything else is setting up to draw it We are drawing our vertex arrays as a set of triangles, starting at position 0 in our Vertex Buffer Object and ending at the size of vhVertexCount.
// glDrawArrays(GL_TRIANGLES, 0, vhVertexCount);
Now that we have setup and drawn our Vertex Buffer Object, we need to disable our client states and texturing with the following lines:
Our major changes to this file are going to take place in the form of initializing our extensions that we are going to use. To do this, I am first going to create a function called initExtensions which is where we are going to initialize all our extensions:
// void initExtensions(void){ // }
Now that we have a nice neat place to initialize our extensions, lets initialize them:
The above extensions are everything we need to create and use our Vertex Buffer Objects. As we now have our extensions initialized and ready to use, we need to call the initExtensions function. I am doing this inside our Init function *before* we create our heightfield. This is because the extensions are needed for the creation of our Vertex Buffer Objects, not just in drawing them. So our Init function now looks like:
Please rate so I know where to improve the site. 1 means needs a lot of improvement, 10 means perfect. If you leave a low rating, please state why. I don't want people just coming to bash the site.
Another nice little adjustment to our heightfield.h file. We just need to assign a variable to hold our textures I am calling this tID and at this point, is an array with just 2 positions, for now we will just be working with tID[0], but later on when we add normal mapping, we will be working with tID[1]:
// unsigned int tID[2];
Now lets move onto our heightfield.cpp file
1. 2. 3. 4.
5. 6. 7. 8. 9. 10. 11. 12. 13.
14. 15. 16.
#include<windows.h>
class SwiftHeightField {
private:
int hmHeight;
int hmWidth;
unsignedint tID[2];
public:
bool Create(char*hFileName, const int hWidth, const int hHeight);
void Render(void);
BYTE hHeightField[1024][1024];
};
Heightfield.CPP File:
The first change here that you will need to pay attention to, is the incorporation of jpeg.h. Just like in the ‘Loading a JPEG texture’ tutorial (coming soon), we will be using the IJG jpeg library to load our textures. So after we add the line:
// include "jpeg.h"
We need to load in our texture. I am doing this after we load in our heightfield, in our Create function. So I am adding the line:
// SwiftTextureJpeg(tID, "texture.jpg", 0);
Where texture.jpg is the texture that will stretch over our terrain. Next is where our texturing comes into play. Just like texturing any other primitive shape in OpenGL, we need to enable texturing and bind our texture. So in our Render function, right before anything else, I am adding the lines:
// glEnable(GL_TEXTURE_2D);
// glBindTexture(GL_TEXTURE_2D, tID[0]);
Which will enable texturing and then bind are texture respectively. To finish off this, we need to disable texturing when we are done with our terrain, so we call:
// glDisable(GL_TEXTURE_2D);
At the end of our Render function. Now we need to take a look at our texture coordinates. At the moment, we have no texture coordinates, so in this tutorial we are going to dynamically compute them on the fly. This sounds really technical but it isn’t really. At the X = 0 coordinate on the map, our U coordinate for our texture is 0, and at the X = Width coordinate, our U coordinate for our texture is X / Width.
So at: X = 0, U = 0;
Z = 0, V = 0;
X = 1024, U = 1; (X / Width = 1024 / 1024 = 1) Z = 1024, V = 1; (Z / Height = 1024 / 1024 = 1)
Taking this into practice, our rendering code will now look like this:
Please rate so I know where to improve the site. 1 means needs a lot of improvement, 10 means perfect. If you leave a low rating, please state why. I don't want people just coming to bash the site.
Our heightfield.h file in this tutorial is going to be left untouched. Just move onto our heightfield.cpp file
1. 2. 3. 4. 5. 6. 7.
8. 9. 10. 11. 12. 13. 14.
#include<windows.h>
class SwiftHeightField {
private:
int hmHeight;
int hmWidth;
public:
bool Create(char*hFileName, const int hWidth, const int hHeight);
void Render(void);
BYTE hHeightField[1024][1024];
};
Heightfield.CPP File:
All the changes in this tutorial, pretty much take place in this file and only really affect our Render function. We are moving from glBegin(GL_POINTS) to glBegin(GL_TRIANGLE_STRIP). This fills in the gaps between our vertices, creating polygons instead of just points where are vertices were. So looking at our code, where we had our GL_POINTS calls, we now have:
It is simple enough to follow, we set the vertices to our triangle strips from our current vertex in the loop, then the vertices surrounding it creating four corners. This tutorial is built with the impression that basic OpenGL knowledge is already known so I will not explain this code
1. 2. 3. 4. 5. 6.
7. 8. 9. 10. 11. 12. 13. 14. 15.
16. 17. 18. 19. 20. 21. 22. 23. 24.
25. 26. 27. 28. 29. 30. 31. 32. 33.
34. 35. 36. 37.
#include<stdio.h> #include<gl\gl.h>
#include“heightfield.h”
bool SwiftHeightField::Create(char*hFileName, const int hWidth, const int hHeight){
Please rate so I know where to improve the site. 1 means needs a lot of improvement, 10 means perfect. If you leave a low rating, please state why. I don't want people just coming to bash the site.
In our heightfield.h file, we are going to declare some private variables inside our class, these are going to hold the height and the width of our heightfield, globally within the class:
// private:
// int hmHeight; // int hmWidth;
1. 2. 3. 4.
5. 6. 7. 8. 9. 10. 11. 12. 13.
14.
#include<windows.h>
class SwiftHeightField {
private:
int hmHeight;
int hmWidth;
public:
bool Create(char*hFileName, const int hWidth, const int hHeight);
void Render(void);
BYTE hHeightField[1024][1024];
};
Heightfield.CPP File:
To render our terrain, we are going to need to call OpenGL commands from within this source file, so we need include gl.h:
// #include <gl/gl.h>
To get the height and width of our heightfield, we then copy the height and width inside the Create function, to our global variables:
// hmHeight = hHeight; // hmWidth = hWidth;
Now we move onto our Render function. Yay, we are about to start seeing some results
To start off, I am going to just render our terrain as a bunch of points in space. To do this, we need our glBegin(GL_POINTS) and glEnd() Inside of thse calls, I am placing a loop that will create a vertex at every point along the X and the Z axis at their respective Y positions. So our code goes from nothing, to this:
Upon running this program, you should find that a window is displayed with a black background, and a bunch of white spots in space. These spots are the vertices of our terrain and are the building blocks of everything to come
1.
2. 3. 4. 5. 6. 7. 8. 9. 10.
11. 12. 13. 14. 15. 16. 17. 18. 19.
20. 21. 22. 23. 24. 25. 26. 27. 28.
29. 30.
#include<stdio.h> #include<gl\gl.h>
#include“heightfield.h”
bool SwiftHeightField::Create(char*hFileName, const int hWidth, const int hHeight){
Please rate so I know where to improve the site. 1 means needs a lot of improvement, 10 means perfect. If you leave a low rating, please state why. I don't want people just coming to bash the site.