Around two years ago I made a tutorial for XNA in which you could render 2D games scaled to the current window resolution with proper letter-boxes or pillar-boxes.
As many know since then I moved to C++ and OpenGL, and ocasionally people ask me “Can you still do that independent resolution thing?”, and yes it’s perfectly possible. I’ve used this on all latest Windows, Mac and iOS, in case you are wondering.
The code is quite straight forward actually. In case you are not familiar with what we are trying to achieve here I recommend my other tutorial first, where I explain this is more detail.
So first we need to set our viewport with proper letterbox or pillar box, if required.
// Let's start by clearing the whole screen with black glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Both these values must be your real window size, so of course these values can't be static int screen_width = 1024; int screen_height = 728; // This is your target virtual resolution for the game, the size you built your game to int virtual_width=1280; int virtual_height=720; float targetAspectRatio = virtual_width/virtual_height; // figure out the largest area that fits in this resolution at the desired aspect ratio int width = screen_width ; int height = (int)(width / targetAspectRatio + 0.5f); if (height > screen_height ) { //It doesn't fit our height, we must switch to pillarbox then height = screen_height ; width = (int)(height * targetAspectRatio + 0.5f); } // set up the new viewport centered in the backbuffer int vp_x = (screen_width / 2) - (width / 2); int vp_y = (screen_height / 2) - (height/ 2); glViewport(vp_x,vp_y,width,height); |
Now that our viewport is set we should set our 2d perspective
// Now we use glOrtho glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); // This function is for Mac and Windows only, if you are using // iOS you should use glOrthof instead glOrtho(0, screen_width, screen_height, 0, -1, 1); /*if on iOS*/ //glOrthof(0, screen_width, screen_height, 0, -1, 1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); |
So now we should push the transformations before actually drawing anything
// Push in scale transformations glMatrixMode(GL_MODELVIEW); glPushMatrix(); //Now to calculate the scale considering the screen size and virtual size float scale_x = (float)((float)(screen_width)) / (float)virtual_width); float scale_y = (float)((float)(screen_height) / (float)virtual_height); glScalef(scale_x, scale_y, 1.0f); |
We can now proceed to drawing everything we want, that’s is really up to you now.
// Place your sprites drawing code here // Example glBegin(GL_TRIANGLES); glColor3f(0.1, 0.2, 0.3); glVertex3f(0, 0, 0); glVertex3f(50, 0, 0); glVertex3f(0, 50, 0); glEnd(); |
I really don’t recommend using glBegin() and glEnd(), this was just for simplicity, you should use glDrawElements or glDrawArrays
After you finish you drawing code we can proceed to the rest
// This pops those matrices for the scale transformations. glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glPopMatrix(); |
//Now to finish we should end our 2D perspective
glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); |
And that’s pretty much it. I have this code on my games and it works fine, at least for what I usually need. Feel free to tweak it around for your needs. Hope this helps to get a picture on how to achieve this effect. Let me know if you find any bug.
Here’s an example of what you might achieve with this
Are you sure it is:
glOrtho(0, screen_width, screen_height, 0, -1, 1);
Not:
glOrtho(0, screen_width, 0, screen_height, -1, 1);
Juris.
Hi Juris.
Yes, according to glOrtho specification it’s
http://www.opengl.org/sdk/docs/man2/xhtml/glOrtho.xml
So in this case it’s glOrtho(0, screen_width, screen_height, 0, -1, 1 );
I seem to have some issues with this code. I have used your XNA example and it worked good. In this case, I have issues with correct scaling and images are up-side-down. Is it possible for you to post a link to VS Project example?
Thanks for your work.
Juris.
As for upside down pictures, don’t forget that OpenGL considers the screen origin as the bottom left corner, so your coordinates are probably flipped. Depending on your code for loading the images to GL they may or not be flipped in the video card memory, I recommend using GDebugger to check that. As for actual vert exes if you want to maintain 0,0 as the top left corner, like XNA then you can do a glScale(1,-1,1) before drawing everything. As for the project I don’t have a clean project of this anymore, sorry, only mixed in with my engine.
Hey!
Just writing to say thanks for this post. I have been able to make it work for me but with a little tweak.
I have looked at games like StarCraft, and some other strategy games and it seems that they always keep the same hight for the virtual viewport and only allow you to see more map on the sides like here:
http://www.progamingtours.net/wp-content/uploads/2013/03/sc2_fov.gif
So I have implemented it the same way.
I wanted to ask, how do you deal with sprite corruption after you scale matrix with glScalef()?
Here is my image Not Scaled: http://s21.postimg.org/tq7qr8ld3/no_scaling.png
Here is my image scaled: http://s21.postimg.org/zfnzbjrjb/scaling.png
Sprite quality suffers after any scaling with glScaleF();
Any tips?
Regards. Juris.
Hi Juris,
Yes quality does suffer an impact, the amount really depends on how much scale you are applying and the type of filter you are using for scaling up/down the images, like Nearest Neighbor or Linear, by using
Scaling images may sometimes cause some artifacts, it really depends on the image itself. Right now I’m working on a Pixel Art game, using a scale of 4x only with GL_NEAREST instead of GL_LINEAR because I want a “blocky” look.
Some 2D games show less visible are, others shrink everything, it really depends. The other day I was playing SkullGirls on PC and the game is in fact scaling, so it’s still a valid technique depending on what you want.