PDA

View Full Version : Calculating Texture Coordinates in OpenGL


Night_Wulfe
04-24-2005, 11:18 PM
Hi there. I'm having an issue with determining texture coordinates in OpenGL, in Windows, when using a sprite sheet. I'm using the following image for my sprite sheet (in this case, simple ground tiles):

http://home.woh.rr.com/nightwulfe/images/tiles.png

Each tile is 32x32 pixels. There are 7 tiles horizontal and 3 tiles vertical. To test my code, I created this image with a border so that I can see how the tiles are being rendered on the screen. This is the result:

http://home.woh.rr.com/nightwulfe/images/output.png

As you can see there are some issues. First, some of the tiles don't show the full border. Some tiles show part of the tile previous to it. There are also a few cases where two pixels of the border, or a previous tile's data, are put into the border of the currently rendered tile.

The following code renders each tile. The render method takes an x, y, and tileId parameter. tileId is a 0 based index into the sprite sheet, ordered left to right, top to bottom:


tiles->render(0, 0, 7);
tiles->render(32, 16, 14);
tiles->render(64, 32, 15);
tiles->render(96, 48, 16);
tiles->render(128, 64, 17);
tiles->render(160, 80, 18);
tiles->render(192, 96, 19);
tiles->render(224, 112, 20);


The following code is used by the render method to calculate the texture coordinates. m_nCellWidth and m_nCellHeight are the height and width of each tile respectively (32x32). m_nWidth and m_nHeight are the image sprite sheet, texture, dimensions. m_nCellsPerRow is calculated based on the width of each tile and the width of the image. In this case, its value is 7:


unsigned int nRow = nCellId / m_nCellsPerRow;
unsigned int nCol = nCellId - (nRow * m_nCellsPerRow);

GLfloat s1 = static_cast<GLfloat>(nCol * m_nCellWidth) / m_nWidth;
GLfloat t1 = static_cast<GLfloat>(nRow * m_nCellHeight) / m_nHeight;
GLfloat s2 = static_cast<GLfloat>((nCol + 1) * m_nCellWidth) / m_nWidth;
GLfloat t2 = static_cast<GLfloat>((nRow + 1) * m_nCellHeight) / m_nHeight;

GLfloat x1 = static_cast<GLfloat>(x);
GLfloat x2 = static_cast<GLfloat>(x + m_nCellWidth);
GLfloat y1 = static_cast<GLfloat>(y);
GLfloat y2 = static_cast<GLfloat>(y + m_nCellHeight);

glBegin(GL_QUADS);
glTexCoord2f(s1, t1); glVertex2f(x1, y1);
glTexCoord2f(s1, t2); glVertex2f(x1, y2);
glTexCoord2f(s2, t2); glVertex2f(x2, y2);
glTexCoord2f(s2, t1); glVertex2f(x2, y1);
glEnd();


And the following is used to initialize OpenGL


glShadeModel(GL_SMOOTH);

glClearDepth(1.0f);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
glEnable(GL_TEXTURE_2D);

glCullFace(GL_BACK);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);



This code is used when the window is resized. The initial client window size is 800x600:


glViewport(0, 0, nWidth, nHeight);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glOrtho(0.0f, nWidth, nHeight, 0.0f, -1.0f, 1.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();


The following code is used to setup the OpenGL texture when the image file is loaded:


glGenTextures(1, &m_nId);
glBindTexture(GL_TEXTURE_2D, m_nId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);


I tried to give as much info as I could think of, without making the post too long. If I'm missing something, please let me know and I'll answer any questions. I think the problem is somewhere in my calculation of the texture coordinates, but I can't for the life of me figure it out.

Any help would be much appreciated. Thanks.

His Shadow
05-08-2005, 02:43 AM
A bit late...

Maybe you could try the following:
- Change to integer format
... glTexCoord2i() and glVertex2i()

- Set the glMatrixMode(GL_TEXTURE)
... and set scaling to 1/width and 1/height of texture. That
way you don't need the division of m_nWidth and
m_nHeight.

DudeMiester
05-08-2005, 01:19 PM
nevermind.

Destroyer
05-09-2005, 01:31 AM
it seems like everyone here knows how to program.

Blade Nightflame
05-09-2005, 02:06 PM
Why do you think there's a Programming/HTML forum then? Just for giggles? http://forums.3drealms.com/ubbthreads/images/graemlins/wink.gif

Night_Wulfe
05-15-2005, 12:55 PM
His Shadow said:
A bit late...

Maybe you could try the following:
- Change to integer format
... glTexCoord2i() and glVertex2i()

- Set the glMatrixMode(GL_TEXTURE)
... and set scaling to 1/width and 1/height of texture. That
way you don't need the division of m_nWidth and
m_nHeight.



I did some research on what GL_TEXTURE does as a MatrixMode, but I don't understand how it would fix this issue. I've been coding for years, but I'm fairly new to OpenGL. I tried it anyway and all I got was a bunch of dark grey squares.

It seems from all of my experiments, glTexCoord2i() doesn't work with integer values other than 0 and 1, which just specifies the whole range of the texture. Is there anyway to tell OpenGL I want to specify the texture coordinates as pixels?

His Shadow
05-15-2005, 03:44 PM
Sorry. Quite a while since I've been coding OpenGL.

I did the following calculations..

width: 224
height: 96

- pass 1 -----------------------------
cellId: 14

row: 14 / 7 = 2
col: 14 - (2 * 7) = 0

s1: (0 * 32) / 244 = 0.0
t1: (2 * 32) / 96 = 0.66666666666666666666666666666667
s2: (0 + 1) * 32 / 244 = 0.13114754098360655737704918032787
t2: (2 + 1) * 32 / 96 = 1.0


- pass 2 -----------------------------
cellId: 15

row: 15 / 7 = 2.1428571428571428571428571428571 = 2
col: 15 - (2 * 7) = 1

s1: (1 * 32) / 244 = 0.13114754098360655737704918032787
t1: (2 * 32) / 96 = 0.66666666666666666666666666666667
s2: (1 + 1) * 32 / 244 = 0.26229508196721311475409836065574
t2: (2 + 1) * 32 / 96 = 1.0

You see the ending and starting texture coordinates (between two neighbouring ids) match up. I think the ending texture coordinate should be a tad shorter, since you are drawing indepenent subimages.


GLfloat s2 = static_cast<GLfloat>((nCol + 1) * m_nCellWidth - 1) / m_nWidth;
GLfloat t2 = static_cast<GLfloat>((nRow + 1) * m_nCellHeight - 1) / m_nHeight;

Added a (-1) to those two lines. Since you don't want to touch the first pixel on the neighbouring subimage, but instead hit the last pixel in the target subimage.

Please tell me if this actually works http://forums.3drealms.com/ubbthreads/images/graemlins/smile.gif

KillerByte
05-15-2005, 04:50 PM
Night_Wulfe said:
It seems from all of my experiments, glTexCoord2i() doesn't work with integer values other than 0 and 1, which just specifies the whole range of the texture. Is there anyway to tell OpenGL I want to specify the texture coordinates as pixels?



This is correct. Texture coordinates for texture modes such as GL_TEXTURE_2D are clamped between the values of 0 and 1. This means that these are the only two integer values which will be available.

In order to be able to specify texture coordinates in terms of pixels, you could use the GL_NV_TEXTURE_RECTANGLE extension. Not only does this remove the need to specify textures in powers of two but it also requires you to reference texture coordinates in pixels. However, the GL_NV_TEXTURE_RECTANGLE extension does not support mip-mapping of the texture which means they will be subject to aliasing problems.

Your best bet is to simply clamp the pixel coordinates yourself and pass these as values to glTexCoord2f(). All you need to do is divide the pixel coordinate you want cast as a double, by the width of the texture also cast as a double. So, for example, in a 256x256 texture, referencing the point (64, 128) in the texture gives the following clamped values:

64/256 = 0.25
128/256 = 0.5

So, the point in the texture would be (0.25, 0.5).

I hope this clarifies things for you. http://forums.3drealms.com/ubbthreads/images/graemlins/smile.gif

DudeMiester
05-15-2005, 09:44 PM
afaik, all you need to do is correct the rounding error when you are doing integer math. The easy way is to convert it all to float before you do any math.

Night_Wulfe
05-16-2005, 12:25 AM
His Shadow said:
Sorry. Quite a while since I've been coding OpenGL.

I did the following calculations..

width: 224
height: 96

- pass 1 -----------------------------
cellId: 14

row: 14 / 7 = 2
col: 14 - (2 * 7) = 0

s1: (0 * 32) / 244 = 0.0
t1: (2 * 32) / 96 = 0.66666666666666666666666666666667
s2: (0 + 1) * 32 / 244 = 0.13114754098360655737704918032787
t2: (2 + 1) * 32 / 96 = 1.0


- pass 2 -----------------------------
cellId: 15

row: 15 / 7 = 2.1428571428571428571428571428571 = 2
col: 15 - (2 * 7) = 1

s1: (1 * 32) / 244 = 0.13114754098360655737704918032787
t1: (2 * 32) / 96 = 0.66666666666666666666666666666667
s2: (1 + 1) * 32 / 244 = 0.26229508196721311475409836065574
t2: (2 + 1) * 32 / 96 = 1.0

You see the ending and starting texture coordinates (between two neighbouring ids) match up. I think the ending texture coordinate should be a tad shorter, since you are drawing indepenent subimages.


GLfloat s2 = static_cast<GLfloat>((nCol + 1) * m_nCellWidth - 1) / m_nWidth;
GLfloat t2 = static_cast<GLfloat>((nRow + 1) * m_nCellHeight - 1) / m_nHeight;

Added a (-1) to those two lines. Since you don't want to touch the first pixel on the neighbouring subimage, but instead hit the last pixel in the target subimage.

Please tell me if this actually works http://forums.3drealms.com/ubbthreads/images/graemlins/smile.gif



Thanks for your response.
I tried your suggestion, and although the outcome is slightly different, there is still problems with the edges of the textures. What you're saying does make sense, however, so I'll stick with it in the mean time.

Night_Wulfe
05-16-2005, 12:28 AM
KillerByte said:

Night_Wulfe said:
It seems from all of my experiments, glTexCoord2i() doesn't work with integer values other than 0 and 1, which just specifies the whole range of the texture. Is there anyway to tell OpenGL I want to specify the texture coordinates as pixels?



This is correct. Texture coordinates for texture modes such as GL_TEXTURE_2D are clamped between the values of 0 and 1. This means that these are the only two integer values which will be available.




It's annoying that I see a lot of resources all over the net stating that if you use this function, you can specify values from 0 to n where n is the width or height of the texture in pixels. I doubt they're correct, though, as my own code never worked that way and everyone I've talked to says otherwise.


KillerByte said:
In order to be able to specify texture coordinates in terms of pixels, you could use the GL_NV_TEXTURE_RECTANGLE extension. Not only does this remove the need to specify textures in powers of two but it also requires you to reference texture coordinates in pixels. However, the GL_NV_TEXTURE_RECTANGLE extension does not support mip-mapping of the texture which means they will be subject to aliasing problems.




I may have to look into this extension and how to use it then.


KillerByte said:
Your best bet is to simply clamp the pixel coordinates yourself and pass these as values to glTexCoord2f(). All you need to do is divide the pixel coordinate you want cast as a double, by the width of the texture also cast as a double. So, for example, in a 256x256 texture, referencing the point (64, 128) in the texture gives the following clamped values:

64/256 = 0.25
128/256 = 0.5

So, the point in the texture would be (0.25, 0.5).

I hope this clarifies things for you. http://forums.3drealms.com/ubbthreads/images/graemlins/smile.gif



I'm pretty sure I'm already doing these calculations in the code I pasted above. If not, please elaborate because I don't understand how what you're suggesting differs from what I have in my code.

Night_Wulfe
05-16-2005, 12:31 AM
DudeMiester said:
afaik, all you need to do is correct the rounding error when you are doing integer math. The easy way is to convert it all to float before you do any math.



I don't think I have a rounding issue due to wrong types of variables. You don't have to cast all operands to a float in a calculation in C/C++, and the way I have it typed above works fine. I can see the values while stepping through a debugger, and if I type out the calculations in a calculator, following order of operations of course, I get the same answer as my code.

Night_Wulfe
05-16-2005, 12:37 AM
I think I'm going to need to post all my resources and source code. They have changed quite a bit (although the rendering code above is still pretty much what I use), and what I have now demonstrates the problem in a much more clear manner. I'll look into doing this if anyone is still interested in taking a look at it.

If no one, or myself, can figure this out, I may end up switching to just avoiding the use of a 3D API all together and use DirectDraw.

Thanks for all your help so far. You're the first out of two other forums to actually offer any ideas as to what may be wrong.

KillerByte
05-16-2005, 05:20 AM
Night_Wulfe said:
I think I'm going to need to post all my resources and source code. They have changed quite a bit (although the rendering code above is still pretty much what I use), and what I have now demonstrates the problem in a much more clear manner. I'll look into doing this if anyone is still interested in taking a look at it.



Feel free to do this. I don't mind taking a look for you.

Night_Wulfe
05-16-2005, 05:50 PM
Here you are:

http://home.woh.rr.com/nightwulfe/ruger.zip

The files you'll most be interested in are in the Ruger folder. They are Texture.cpp (contains texture calculations and rendering of rectangle), RugerGame.cpp (contains the game main routine), Window.cpp (window creation and event handling) and OpenGL.cpp (OpenGL Resize code, attaching to a window and initial glEnable calls). The rest is pretty much frame work.

There is a compiled executeable in the bin folder. All third party libraries are included just to make things easier if you decide to try to compile the thing. The project files are Visual Studio .NET 2003. If you need to make up a new project for another IDE or compiler, Ruger is the main project and depends on tinyxml and glpng.

When you run the executeable it should create a 640x480 screen (I forget which bit depth, check the log window). In the center of the window you'll see a tiled walk way. A small animation of a man runs across the walk way. All of this is fine. If you watch the walk way, there are glowing cross tiles. If you watch these, the animation is very strange. Basically, it's the same problem as what is in my initial post: The texture calculations are off.

This is mostly visible because of the tile animations, but the other tiles have the same problem. If you were to tile the first tile in the tile set (RugerTILES.png) in your favorite paint program, it looks slightly different from what you see in the game.

Anyone can do whatever they want with this code base, I've pretty much abandoned it. If I can't figure out what's wrong with the rendering stuff, I'll probably just say "Screw cross platform," let someone else deal with that, and switch to DirectDraw with the new code base I have.

Thanks again

Night_Wulfe
05-16-2005, 06:19 PM
\o/


I think I fixed it. If so, today is the best day ever. If someone could verify for me, I would be very grateful. Replace the RugerTILES.png image in the bin/images folder in the zip file linked above with this image:

http://home.woh.rr.com/nightwulfe/RugerTILES.png (Right Click, save as)

Replace the executeable in the bin folder with this one:

http://home.woh.rr.com/nightwulfe/Ruger.exe (Right click, save as)

After replacing, run the executable. The animation problem should have gone away. If so, it's because my texture was not a size that was a power of two (i.e. 128x128 or 256x256)

Thanks!

Edit: Oooopse. The new executeable expects the RugerTILES.png file to be called RugerTILES4.png. When you save the file to the images folder, save it as RugerTILES4.png. Otherwise, you'll just get a guy running across the screen with no floor.

His Shadow
05-17-2005, 04:07 AM
Nice. When can we expect particle effects and post-processing effects to be included? http://forums.3drealms.com/ubbthreads/images/graemlins/smile.gif

I also had a thought it mayby was mip-mapping that caused it. Glad you fixed the problem.

Do you got the latest "OpenGL Programming Guide"? Pretty decent book. They have not released a new version for OpenGL 2.0 yet, though. There's also a book available on the new shader language, "OpenGL Shader Language". But that is probably overkill for your project http://forums.3drealms.com/ubbthreads/images/graemlins/smile.gif

Night_Wulfe
05-17-2005, 06:30 AM
Nice. When can we expect particle effects and post-processing effects to be included?



No idea http://forums.3drealms.com/ubbthreads/images/graemlins/tongue.gif I have a long way yet before this code is up to where it needs to be so that i can make a game out of it.


I also had a thought it mayby was mip-mapping that caused it. Glad you fixed the problem.



That was my first guess. I also thought that maybe floats just weren't precise enough to get the tiles correctly, which of course confused me because no one else had the problem.


Do you got the latest "OpenGL Programming Guide"? Pretty decent book. They have not released a new version for OpenGL 2.0 yet, though.



I have the third edition. I look at it now and then, but most of the stuff I've run across it didn't seem to address. I only realize afterwards that my problem wasn't what I thought it was, this one included. In addition to it being a mipmap or texture coordinate problem, I thought it may have something to do with glOrtho. The book didn't have much on glOrtho, so I shelved it again http://forums.3drealms.com/ubbthreads/images/graemlins/doh.gif


There's also a book available on the new shader language, "OpenGL Shader Language". But that is probably overkill for your project



Yea. The only two games I've ever made was Hang Man in QBasic probably 8 years ago and Minesweeper on a Motorola 68000 emulator for school about 2 years ago. So in the end, I'm trying to keep the end result simple. Beyond what I have now, I think all I plan on adding is some basic lighting effects. I'll probably let OpenGL do this with its own lighting system. If I have to do my own calculations to do vertex lighting for performance reasons, then I won't bother.

I guess I blew the idea of keeping it simple out of the water by writing this as an engine rather than an actual game. I even have a scripting engine which was used in an even older copy of the code. I didn't write it, but it's probably the easiest one I've ever seen to use.

http://www.angelcode.com/angelscript/ for details.

I need to stop rambling so much.

SippyCup
07-26-2005, 02:44 AM
And I was thinking PHP was tedious.

DudeMiester
07-26-2005, 03:11 PM
Hah try DirectX and then talk, better yet try knowing what you're talking about and then talk. The fact is the calculations are absolutely necessary and abolutely not redundant.