PDA

View Full Version : 2D rendering in OpenGL



K4Z
19-10-2006, 02:58 PM
As requested, This is just a simple demonstration on how to perform 2D rendering in OpenGL, (directed at those using (JEDI-)SDL and would like to use HW accel OpenGL for rendering). The code is not that different from standard OGL 3D commands, so it'll help if you understand the basics of OGL. (To cut the length of this demo down, I won't go through setting up the screen, loading textures, etc, etc)

First of all is setting up OGL to behave like 2D, this is done by disabling Depth Test, and switching the PROJECTION mode to ORTHO view (2D view). To simplify things and keep the code clean, it's a good idea to put the code in a couple of functions.



procedure Start2D;
var W, H:Single;
ViewPort : TViewPortArray;
begin
glGetIntegerv(GL_VIEWPORT, @ViewPort[0]);
W := ViewPort[2];
H := ViewPort[3];

glDisable(GL_DEPTH_TEST); //turn off z buffer
glMatrixMode(GL_PROJECTION); //set projection matrix
glPushMatrix; //push it
glLoadIdentity(); //reset
glOrtho(0, W, H, 0, 0, 100); //ortho view
glMatrixMode(GL_MODELVIEW); //return to modelview
glLoadIdentity(); //reset
end;

procedure End2D;
begin
glEnable(GL_DEPTH_TEST); //turn on z buffer
glMatrixMode(GL_PROJECTION); //set projection matrix
glPopMatrix; //restore projection
glMatrixMode(GL_MODELVIEW); //set modelview matrix
glLoadIdentity(); //reset
end;


To breake things down:

We need the width and height of the screen in order to set our whole screen to 2D. So without hard coding it in, we can use a simple OpenGL call like: glGetIntegerv(GL_VIEWPORT, @ViewPort[0]), and you will get an array with lots of data, [2] and [3] contain the data what we want.

glDisable(GL_DEPTH_TEST) disables the Z buffer, telling OGL not to sort polygons, ie, last drawn polys are drawn on top of everything else.

glMatrixMode(GL_PROJECTION) just sets the mode to Projection (I won't go into this here :P).

glOrtho(0, W, H, 0, 0, 100) is the core of the function, setting the Projection mode to 2D.

And glMatrixMode(GL_MODELVIEW) just sets the Mode back to ModelView, so we can actually render things.

(The other reason we use the two functions is in situations where you might want to switch back to 3D, in order to do some cool 3D effects, etc)


Ok, now time for some extreme basic rendering code. If you have used OGL for 3D before, you'll understand the code pretty well, if not, it's pretty easy to understand. As with all 3D libraries, rendering is done using textured 3D triangles (or quads), This is no different when rendering 2D in OGL.




uses gl, glu, sdl;

repeat

glEnable(GL_TEXTURE_2D); //Enable Texturing
glBindTexture(GL_TEXTURE_2D, MyTexture); //Bind Texture

glColor3f(1,1,1); //Make sure color is white (Normal)

Start2D; //Switch to 2D mode
glPushMatrix; //Save Current Matrix

//Draw a Quad
glBegin(GL_QUADS);
glTexCoord2f(0, 0); //Top-Left
glVertex2f(0,0);

glTexCoord2f(100, 0); //Top-Right
glVertex2f(100, 0);

glTexCoord2f(1, 1); //Bottom-Right
glVertex2f(100, 100);

glTexCoord2f(0, 1); //Bottom-Left
glVertex2f(100,100);
glEnd;
glPopMatrix; //Reload Old Matrix
End2D;

SDL_GL_SwapBuffers;
until gmQuit;


Edited by savage to make unit names lower case for cross compilation smoothness :)

And to run through it; You'll need GL.pas, and GLU.pas for OGL stuff, SDL.pas for well, SDL.

First 3 lines are just standard OpenGL texture binding, and resetting color to white.

Next we call our 2D function to disable 3D.

glBegin(GL_QUADS) just tells OpenGL we wanna draw using 4 point triangles.

glTexCoord2f(0, 0) and glVertex2f(0,0) are used to set texture co-ords and plot Vertex's, just like in 3D. The main difference is we don't define a Z axis, and now that we are in 2D mode, X,Y now relate to Screen pixel co-ordinates (starting at Top-Left : 0,0) instead of 3D co-ordinates at 0,0,0.

I use SDL_GL_SwapBuffers to update the screen as it can handle double buffering.

Ok, I guess that's the basics, I could post some working source if it'll help.

grudzio
19-10-2006, 03:43 PM
I think that glTexCoord2f(0.0,0.0) refers to bottom left corner not the top left.



glBegin(GL_QUADS) just tells OpenGL we wanna draw using 4 point triangles.

I am sure you meant polygons :D .

One more thing. If you don't plan on using 3D, I think it is easier to setup 2D view only once. Here is an example.


//w,h - screen width and height
procedure Init2D(w,h : integer);
begin
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION):
glLoadIdentity;
glOrtho(0,w,h,0,0,100); //or gluOrtho2D(0,w,h,0)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
end;



Just my two cents :) .

JernejL
19-10-2006, 04:03 PM
This is yet another adaption of the famous code from gamedev.net that i use, but with one difference, that coordinates will match windows, making (0,0) top-left edge of the window, and (W, H) will be bottom right edge.

edit: also, my function does not turn off depth test, but relies on you doing it yourself.



// Dwarf with Axe - GAMEDEV forums: 18 July 2002 6:12:57 PM
//
// There have been thousands of posts along the lines of
// "How do I do 2d in OpenGL" to "Duuuhde, I wunt too maek
// a two dee gaem in ohpun jee el; how do eye set uhp two dee???!?"
//
// I have developed a simple, nice, pretty way for all of you to have your 2D fun.

procedure GlEnable2D;
var
vport: array[0..3] of integer;
begin
glGetIntegerv(GL_VIEWPORT, @vPort);

glMatrixMode(GL_PROJECTION);
glPushMatrix;
glLoadIdentity;
glOrtho(0, vPort[2], 0, -vPort[3], -1, 1);

glMatrixMode(GL_MODELVIEW);
glPushMatrix;
glLoadIdentity;

// flip Y axis
glTranslatef(0, -vPort[3], 0);
end;

procedure GlDisable2D;
begin
glMatrixMode(GL_PROJECTION);
glPopMatrix;
glMatrixMode(GL_MODELVIEW);
glPopMatrix;
end;

tanffn
19-10-2006, 06:09 PM
Thanks a lot for sharing!
Now all I need to do is find some free time to play with it :)

What about scrolling, can I render with a starting pixel of (-10, -50)?

Traveler
19-10-2006, 07:27 PM
Sure. That'd go something like this:



glTranslatef(x, y, -0.1);

glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex2f(0, 0);
glTexCoord2f(0, 1); glVertex2f(0, FHeight);
glTexCoord2f(1, 1); glVertex2f(FWidth, FHeight);
glTexCoord2f(1, 0); glVertex2f(FWidth, 0);
glEnd;


where x and y can have any value you'd like.

grudzio
19-10-2006, 07:54 PM
Just remember that if you call glTranslatef(x,y,0) all other OpenGL operations will be relative to the (x,y). An example

glTranslatef(10,20,0);
glBegin(GL_QUADS);
...
glEnd;

glTranslatef(20,-10,0);
glBegin(GL_QUADS);
...
glEnd;

The second call to glTranslatef will move your position to (30,10) not (20,-10) since it is relative to first call. (Its like Delphi TCanvas methods MoveTo and LineTo). To translate to proper position wrap code with
glPushMatrix and glPopMatrix calls i. e.


glPushMatrix;
glTranslatef(10,20,0);
glBegin(GL_QUADS);
...
glEnd;
glPopMatrix;

glPushMatrix;
glTranslatef(20,-10,0);
glBegin(GL_QUADS);
...
glEnd;
glPopMatrix;

glPushMatrix saves current transformation matrix on a stack and glPopMatrix restores it. Those two are very usefull functions.
Take a look at KAZ Start2D and End2D procedures. He uses glPushMatrix and glPopMatrix to save old projection matrix, turns on 2D and finally restores old projection.

Same applies to glRotatef and glScalef if you want to rotate and scale sprites.

cragwolf
19-10-2006, 09:36 PM
OpenGL is great for 2D. You automatically have zoom and rotation without needing to creating a whole new set of graphics. And transparency is extremely simple to do. Personally, the only reason I use SDL is for the window management.

savage
19-10-2006, 10:28 PM
Personally, the only reason I use SDL is for the window management.

I personally think that SDL is great for window management and also input management. The new classes in v1.0 make it even easier to use both.
Just depends if you want an extra layer for that sort of stuff though. I'll be porting the Grid Crazy and Aliens demos to the new classes, so that others can see how easy it is to use them. I originally created them for the SoAoS project.

K4Z
20-10-2006, 12:54 AM
Thanx for the extra info and fixes guys :wink:


What about scrolling, can I render with a starting pixel of (-10, -50)?

Yep that's perfectly ok in OpenGL.


I personally think that SDL is great for window management and also input management. The new classes in v1.0 make it even easier to use both.

Yep, I use SDL for just about everything in my projects, window management, file loading, input, etc, But I just like to do my own OpenGL for rendering :wink:.

K4Z
20-10-2006, 12:59 AM
glBegin(GL_QUADS) just tells OpenGL we wanna draw using 4 point triangles.

I am sure you meant polygons :D .


Yeah Polygons, I just like to steal the term 4 point triangles from my 3D modeling background (Before the days of quads) :P :P :P

JernejL
20-10-2006, 10:30 AM
Actually, opengl implementations are free to render quads as they like, that includes the most often case: splitting it into 2 triangles, so in most cases, like 99.9% of opengl implentations, your quads will end up as two triangles.

WILL
20-10-2006, 12:25 PM
One thing that I've noticed OpenGL has issues with is drawing line specific to the pixel.

This function should draw a line exactly from X1,Y1 to X2,Y2 this would include the pixels at the two points I specified. Unfortunately this does not happen. I think it may, in part have to do with the last position sent to OpenGL and when it changes states for the next drawing.

procedure DrawLine(X1, Y1, X2, Y2: Integer; ColorR, ColorG, ColorB: GLfloat);
begin
glDisable(GL_TEXTURE_2D); //Disable Textures, as we're drawing lines
glColor3f(ColorR, ColorG, ColorB); //Set the new color!

BeginOrtho; //Switch to 2D mode
glPushMatrix; //Save Current Matrix
glTranslatef(X1, Y1, 0);
glBegin(GL_LINES); //Draw Using Lines
glVertex2f(0, 0);
glVertex2f(X2 - X1, Y2 - Y1);
glEnd;
glPopMatrix; //Reload Old Matrix
EndOrtho;
end;

Yes, it uses the same methods as Kas posted above. (same codebase after all ;))

savage
20-10-2006, 01:03 PM
btw, I don't think you want to be doing BeginOrtho and EndOrtho all over the shop, if your whole game is in 2D. Just do it at start up and leave it be. If I'm wrong I'm sure some OpenGL gurus will correct me.

K4Z
20-10-2006, 01:38 PM
btw, I don't think you want to be doing BeginOrtho and EndOrtho all over the shop, if your whole game is in 2D. Just do it at start up and leave it be. If I'm wrong I'm sure some OpenGL gurus will correct me.

Yep, that's the idea, I tried to keep the demo simple to understand, so I set it up like that.

Though BeginOrtho really isn't that expensive, I call these functions multiple times a frame, for GUIs, HUDs,Billboards, Particles, etc etc in between 3D rendering. So either way it should be fine :P :P.

Traveler
20-10-2006, 02:03 PM
I dont think its a really bad thing to do, as fps games have for instance a 3d scene with a 2d hud. But, if its allround 2d, it's probably better to call it once per loop.

edit: thatt'll teach me to leave new topic windows open and wait another 20 mins for an answer :-)

jdarling
20-10-2006, 02:04 PM
Now if someone only knew how to use SDL surfaces on OpenGL surfaces :). At least every time I've tried it didn't work :(.

K4Z
20-10-2006, 02:32 PM
Now if someone only knew how to use SDL surfaces on OpenGL surfaces :). At least every time I've tried it didn't work :(.

Lol, I don't even think that's possible, is it? and would it be worth it? :P

JernejL
20-10-2006, 03:36 PM
One thing that I've noticed OpenGL has issues with is drawing line specific to the pixel.

This function should draw a line exactly from X1,Y1 to X2,Y2 this would include the pixels at the two points I specified. Unfortunately this does not happen. I think it may, in part have to do with the last position sent to OpenGL and when it changes states for the next drawing.


all 2d switching code above will when sent coordinates set them pixel perfect, the only real problem with 2D mode is, when somebody has set forced anti-aliasing in the gfx driver and you are drawing lines without texture. What you get is NOT what you want in that case, you will for example see serious off by one coordinates on ATI driver platforms...

grudzio
20-10-2006, 04:17 PM
Now if someone only knew how to use SDL surfaces on OpenGL surfaces :). At least every time I've tried it didn't work :(.

This is how I would use SDL surfaces with OpenGL.

I. reading from buffer to SDL_Surface
- create sdl surface 24 or 32 bpp (or convert existing one) depending if you want alpha or not
- call glReadPixels (http://www.mevis.de/opengl/glReadPixels.html)
example:

surface := SDL_CreateRGBSurface(SDL_SWSURFACE,w,h,32,rmask,gm ask,bmask,amask);
glReadPixels(x,y,w,h,GL_RGBA,GL_UNSIGNED_BYTE,surf ace^.pixels);


II. SDL_Surface to OpenGL texture
- Again, load/create surface and convert to 24 or 32 bpp
- create openGL texture from surface's data for example like this

function MakeTexture(surf : PSDL_Surface) : gluint;
var
texID,tex_fmt : gluint;
begin
glGenTextures( 1, @texID );
glBindTexture( GL_TEXTURE_2D, TexID );
//setup some texture parameters
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );

if surf^.format.bitPerPixel = 32 then
tex_fmt := GL_RGBA
else
tex_fmt := GL_RGB;

glTexImage2D( GL_TEXTURE_2D,
0,
tex_fmt,
surf^.w, surf^.h,
0,
tex_fmt,
GL_UNSIGNED_BYTE,
surf^.pixels );
Result := texID;
end;


III. Read OpenGL texture to SDL_Surface
- As always create/convert to proper pixel format
- call glGetTexImage (http://www.mevis.de/opengl/glGetTexImage.html)

IV. Copy SDL surface directly to OpenGL color buffer (very slow)
- Prepare surface
- call glDrawPixels (http://www.mevis.de/opengl/glDrawPixels.html)

I hope it helps.

savage
20-10-2006, 07:30 PM
I can't remember if it was mentioned on this thread or another JEDI-SDL thread, but the SDL_ttf 3D demo uses a PSDL_Surface to render some text over a spinning 3d cube. Have a look at it.