Advertise Mobile SDKs Books Events Forum News Social Networking Support Us
Follow @iphonedevsdk on Twitter

Interface 2, Advanced iOS
Mockup & Code Gen
($9.99)

Make your own iPhone apps
and run them live!
(free)

Pic Frame Dynamo: Photo Editing
($0.99)

Abiliator
($1.99)

Want your application or service advertised on iPhone Dev SDK?

Go Back   iPhone Dev SDK Forum > iPhone SDK Development Forums > iPhone SDK Game Development

Reply
 
LinkBack Thread Tools Display Modes
Old 12-11-2009, 08:57 AM   #1 (permalink)
Registered Member
 
darth_steff's Avatar
 
Join Date: Nov 2009
Location: Germany
Posts: 14
darth_steff is on a distinguished road
Default OpenGL Tiled Map - Texturing Problems

Dear fellow developers,

I have an issue with a tiled map I'm displaying. The map consists of 18x9 tiles, each 256x256 pixels in size. There's one unique PVRTC-compressed texture for each tile (no atlases or shared textures among tiles). The textures contain mipmap levels and were created using Apple's texturetool. Everying looks smooth until I start pinch zooming into my opengl view (pinch has been implemented manually using the touchesMoved method). Suddenly there's a distortion at nearly each tile's edge. It looks as though the texture sort of loops (i.e.: the right most texel is assigned to the left most pixel of the polygon). I use these obvious texture cords:

const GLfloat squareTexCoords[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
};

and these parameters:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

Is there some common thing I'm missing? This is the very first project with opengl involvement for me.

Here's a screenshot:

What's the problem with these distortions?! on Twitpic


Any help is greatly appreciated!

Steff
darth_steff is offline   Reply With Quote
Old 12-11-2009, 09:51 AM   #2 (permalink)
Registered Member
 
Join Date: Nov 2008
Posts: 129
gonk is on a distinguished road
Default

Edit... sorry, didn't look closely enough at your code.

It looks like your texture clamping isn't working. All I can think of is that you didn't have your texture "bound" when you specified your clamping. (You need to specify the clamping parameter for every texture you create.)

Last edited by gonk; 12-11-2009 at 10:05 AM.
gonk is offline   Reply With Quote
Old 12-11-2009, 11:29 AM   #3 (permalink)
Maker of Games
 
Mr Jack's Avatar
 
Join Date: Nov 2009
Location: Coventry, UK
Posts: 395
Mr Jack is on a distinguished road
Default

How are you drawing your textures? Are they all one triangle strip? Or multiple primitives?

Try removing all the other tiles, so there's just one, and looking at it so you can see more clearly what's happening.
__________________


Visit Mr Jack Games for my blog and more about my games
Mr Jack is offline   Reply With Quote
Old 12-12-2009, 05:46 AM   #4 (permalink)
Registered Member
 
darth_steff's Avatar
 
Join Date: Nov 2009
Location: Germany
Posts: 14
darth_steff is on a distinguished road
Default

Quote:
Originally Posted by gonk View Post
Edit... sorry, didn't look closely enough at your code.

It looks like your texture clamping isn't working. All I can think of is that you didn't have your texture "bound" when you specified your clamping. (You need to specify the clamping parameter for every texture you create.)
I'm using the code from the PVRTexture example to create the textures. I've also added these calls prior to glGenTexture and glBindTexture:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

..which didn't change anything.

All tiles are separate triangle strips (i.e. two triangles per tile). Reducing the tiles to one still shows the distortions but less noticable.
If I change the texture filtering to GL_NEAREST the effect is still there but also less noticable.

Thank you guys for responding so fast!

Steff
darth_steff is offline   Reply With Quote
Old 12-12-2009, 11:02 AM   #5 (permalink)
Registered Member
iPhone Dev SDK Supporter
 
smasher's Avatar
 
Join Date: Jul 2008
Location: San Mateo, CA (San Fran)
Posts: 3,858
smasher will become famous soon enough
Default

I think gonk had it - you need to set the clamping for each texture after you've bound it and before you bind the next texture. Are you doing that?

You might want to post your whole method for loading a texture.
__________________

Free Games!
smasher is offline   Reply With Quote
Old 12-12-2009, 11:40 AM   #6 (permalink)
Registered Member
 
Join Date: Nov 2008
Posts: 129
gonk is on a distinguished road
Default

Quote:
Originally Posted by darth_steff View Post
I've also added these calls prior to glGenTexture and glBindTexture:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
Mipmapping isn't the problem. But, if you want any glTexParameter call to work, it has to come AFTER the bind for that particular texture. Doing things before the bind will, in effect, change the corresponding parameter on the previously bound texture (if there was one.)
gonk is offline   Reply With Quote
Old 12-12-2009, 05:10 PM   #7 (permalink)
Registered Member
 
darth_steff's Avatar
 
Join Date: Nov 2009
Location: Germany
Posts: 14
darth_steff is on a distinguished road
Default

Quote:
Originally Posted by gonk View Post
Mipmapping isn't the problem. But, if you want any glTexParameter call to work, it has to come AFTER the bind for that particular texture. Doing things before the bind will, in effect, change the corresponding parameter on the previously bound texture (if there was one.)
Do I only need to set these params in the process of texture creation or also when I'm rendering?
Here's the code I'm using to load the textures:

initWithContentsOfFile: is passed a string containing the texture's file path

Code:
- (id)initWithContentsOfFile:(NSString *)path
{
	if ((self = [super init]))
	{
		NSData *data = [NSData dataWithContentsOfFile:path];
		
		_imageData = [[NSMutableArray alloc] initWithCapacity:10];
		
		_name = 0;
		_width = _height = 0;
		_internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
		_hasAlpha = FALSE;
		
		if (!data || ![self unpackPVRData:data] || ![self createGLTexture])
		{
			[self release];
			self = nil;
		}
	}
	
	return self;
}

- (BOOL)unpackPVRData:(NSData *)data
{
	BOOL success = FALSE;
	PVRTexHeader *header = NULL;
	uint32_t flags, pvrTag;
	uint32_t dataLength = 0, dataOffset = 0, dataSize = 0;
	uint32_t blockSize = 0, widthBlocks = 0, heightBlocks = 0;
	uint32_t width = 0, height = 0, bpp = 4;
	uint8_t *bytes = NULL;
	uint32_t formatFlags;
	
	header = (PVRTexHeader *)[data bytes];
	
	pvrTag = CFSwapInt32LittleToHost(header->pvrTag);
	
	pvrTag = header->pvrTag;

	if (gPVRTexIdentifier[0] != ((pvrTag >>  0) & 0xff) ||
		gPVRTexIdentifier[1] != ((pvrTag >>  8) & 0xff) ||
		gPVRTexIdentifier[2] != ((pvrTag >> 16) & 0xff) ||
		gPVRTexIdentifier[3] != ((pvrTag >> 24) & 0xff))
	{
		return FALSE;
	}
	
	flags = CFSwapInt32LittleToHost(header->flags);
	formatFlags = flags & PVR_TEXTURE_FLAG_TYPE_MASK;
	
	if (formatFlags == kPVRTextureFlagTypePVRTC_4 || formatFlags == kPVRTextureFlagTypePVRTC_2)
	{
		[_imageData removeAllObjects];
		
		if (formatFlags == kPVRTextureFlagTypePVRTC_4)
			_internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
		else if (formatFlags == kPVRTextureFlagTypePVRTC_2)
			_internalFormat = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
	
		_width = width = CFSwapInt32LittleToHost(header->width);
		_height = height = CFSwapInt32LittleToHost(header->height);
		
		if (CFSwapInt32LittleToHost(header->bitmaskAlpha))
			_hasAlpha = TRUE;
		else
			_hasAlpha = FALSE;
		
		dataLength = CFSwapInt32LittleToHost(header->dataLength);
		
		bytes = ((uint8_t *)[data bytes]) + sizeof(PVRTexHeader);
		
		// Calculate the data size for each texture level and respect the minimum number of blocks
		while (dataOffset < dataLength)
		{
			if (formatFlags == kPVRTextureFlagTypePVRTC_4)
			{
				blockSize = 4 * 4; // Pixel by pixel block size for 4bpp
				widthBlocks = width / 4;
				heightBlocks = height / 4;
				bpp = 4;
			}
			else
			{
				blockSize = 8 * 4; // Pixel by pixel block size for 2bpp
				widthBlocks = width / 8;
				heightBlocks = height / 4;
				bpp = 2;
			}
			
			// Clamp to minimum number of blocks
			if (widthBlocks < 2)
				widthBlocks = 2;
			if (heightBlocks < 2)
				heightBlocks = 2;

			dataSize = widthBlocks * heightBlocks * ((blockSize  * bpp) / 8);
			
			[_imageData addObject:[NSData dataWithBytes:bytes+dataOffset length:dataSize]];
			
			dataOffset += dataSize;
			
			width = MAX(width >> 1, 1);
			height = MAX(height >> 1, 1);
		}
				  
		success = TRUE;
	}
	return success;
}


- (BOOL)createGLTexture
{
	int width = _width;
	int height = _height;
	NSData *data;
	GLenum err;
	
	if ([_imageData count] > 0)
	{
		if (_name != 0)
			glDeleteTextures(1, &_name);
		glGenTextures(1, &_name);
		glBindTexture(GL_TEXTURE_2D, _name);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}
	
	for (int i=0; i < [_imageData count]; i++)
	{
		data = [_imageData objectAtIndex:i];
		glCompressedTexImage2D(GL_TEXTURE_2D, i, _internalFormat, width, height, 0, [data length], [data bytes]);
		
		err = glGetError();
		if (err != GL_NO_ERROR)
		{
			NSLog(@"Error uploading compressed texture level: %d. glError: 0x%04X", i, err);
			return FALSE;
		}
		
		width = MAX(width >> 1, 1);
		height = MAX(height >> 1, 1);
	}
	
	[_imageData removeAllObjects];
	
	return TRUE;
}
Here's how I setup a tile:

Code:
- (id)initWithIndex:(NSInteger)index forMapSize:(CGSize)size {
	// find the row and column
	GLshort row = (NSInteger)(index / size.width);
	GLshort col = (NSInteger)(index % (NSInteger)size.width);
	
	GLshort x = col << 8;
	GLshort y = row << 8;
	
	coords[0] = x;
	coords[1] = y + 256;
	coords[2] = x + 256;
	coords[3] = y + 256;
	coords[4] = x;
	coords[5] = y;
	coords[6] = x + 256;
	coords[7] = y;

	NSString *txName = [NSString stringWithFormat:@"tile%d", index];
	texture = [[PVRTexture alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:txName ofType:@"pvr"]];
	
	return self;
}
coords is a Glshort-array. Each tile consists of two triangles. Row- and column indexes are multiplied by 256 as this corresponds to the size of the tiles.

Drawing looks like this:

Code:
- (void)drawView:(GLView*)view;
{
	const GLshort squareTexCoords[] = {
        0, 1,
        1, 1,
        0, 0,
        1, 0
    };

	glLoadIdentity();
	glScalef(mapScale, mapScale, 1);
	glTranslatef(mapOffset.x, mapOffset.y, 0);
	
	glEnableClientState(GL_VERTEX_ARRAY);	
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_SRC_COLOR);
	
    glClearColor(0.0, 0.0, 0.25, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);	

	glTexCoordPointer(2, GL_SHORT, 0, squareTexCoords);

	for (TexturedTile* tile in tiles) {
		[tile drawTile];
	}
	
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_BLEND);
	
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);	
}
(I'm using Jeff LaMarches OpenGL application template by the way).

TexturedTile::drawTile does simply take it's coords array and draws a triangle strip:

Code:
- (void)drawTile {
	glBindTexture(GL_TEXTURE_2D, [texture name]);	
	glVertexPointer(2, GL_SHORT, 0, coords);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
If I set the filtering within drawTile the application slows down dramatically. Also not that I'm not doing any optimization yet. My first priority is getting this thing to work without 'em texture distortions.

Best regards,
Steff
darth_steff is offline   Reply With Quote
Old 12-12-2009, 05:20 PM   #8 (permalink)
Registered Member
 
Join Date: Nov 2008
Posts: 129
gonk is on a distinguished road
Default

I did not read all of your code. However, I'd say these two lines:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);


should be moved to within the createGLTexture, right AFTER the bind command. You only need to specify if when you create the texture, but you need to do it for every texture you create, right after you "bind" it.
gonk is offline   Reply With Quote
Old 12-12-2009, 05:44 PM   #9 (permalink)
Registered Member
 
darth_steff's Avatar
 
Join Date: Nov 2009
Location: Germany
Posts: 14
darth_steff is on a distinguished road
Default

Quote:
Originally Posted by gonk View Post
I did not read all of your code. However, I'd say these two lines:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);


should be moved to within the createGLTexture, right AFTER the bind command. You only need to specify if when you create the texture, but you need to do it for every texture you create, right after you "bind" it.
Did that. Now the distortions are almost gone. Still they're quite noticable:

almost gone.. on Twitpic
darth_steff is offline   Reply With Quote
Old 12-12-2009, 06:04 PM   #10 (permalink)
Registered Member
 
Join Date: Nov 2008
Posts: 129
gonk is on a distinguished road
Default

Yeah... I seem to remember running in to this as well at one point... the problem is that you need to specify border texels, but opengles doesn't allow that (at least, not version 1.1.) I think what's going on is that you are not getting the clamping you want in the mipmaps. Are you generating the mipmaps yourself? If so, make sure you have the clamping for each texture turned on before you generate the mipmaps.

My solution to this problem (which is probably not the best one) is just to make sure that none of my textures run up flush with the edge of the texture. In the one case where I can't avoid that, I use texels within the texture (6 rows, I think) as my border texels. But in your case that will mean resampling all of your textures to use some of the textures texels as border texels. It will also mean that you're texture coordinates will have to be updated so that they map to the "real" corner of the texture which will now be interior to your texture. That probably isn't the right solution, but it was the one I went with. It was a long time ago that I ran across this problem so my memory is a bit hazy.

Anyone else run in to this problem? I *think* (but I don't know) the problem is that mipmap levels other than level 0 ALWAYS have clamping turned off (that is, they always wrap instead of clamp.) I don't think that is what opengles is supposed to do, but I think that's what's going on. This may only be a problem with pvr textures.
gonk is offline   Reply With Quote
Old 12-12-2009, 06:19 PM   #11 (permalink)
Registered Member
 
darth_steff's Avatar
 
Join Date: Nov 2009
Location: Germany
Posts: 14
darth_steff is on a distinguished road
Default

Quote:
Originally Posted by gonk View Post
Yeah... I seem to remember running in to this as well at one point... the problem is that you need to specify border texels, but opengles doesn't allow that (at least, not version 1.1.) I think what's going on is that you are not getting the clamping you want in the mipmaps. Are you generating the mipmaps yourself? If so, make sure you have the clamping for each texture turned on before you generate the mipmaps.

My solution to this problem (which is probably not the best one) is just to make sure that none of my textures run up flush with the edge of the texture. In the one case where I can't avoid that, I use texels within the texture (6 rows, I think) as my border texels. But in your case that will mean resampling all of your textures to use some of the textures texels as border texels. It will also mean that you're texture coordinates will have to be updated so that they map to the "real" corner of the texture which will now be interior to your texture. That probably isn't the right solution, but it was the one I went with. It was a long time ago that I ran across this problem so my memory is a bit hazy.

Anyone else run in to this problem? I *think* (but I don't know) the problem is that mipmap levels other than level 0 ALWAYS have clamping turned off (that is, they always wrap instead of clamp.) I don't think that is what opengles is supposed to do, but I think that's what's going on. This may only be a problem with pvr textures.
Thanks! I use texturetool to generate the mipmap levels, so I have no influence on the clamping. So you mean if I sort of inset my map tile by adding 6 pixels to each of the edges if would work? Gonna try that... only I don't know how to teach ImageMagick to do this yet :-)
BTW: just tried to emulate what you've suggested by setting the texture coords to 0.1..0.9 and it did work. No distortions anymore. Now I know what to do.

Thanks again! I really appreciate it!

Steff
darth_steff is offline   Reply With Quote
Old 12-12-2009, 06:36 PM   #12 (permalink)
Registered Member
 
Join Date: Nov 2008
Posts: 129
gonk is on a distinguished road
Default

Quote:
Originally Posted by darth_steff View Post
Thanks! I use texturetool to generate the mipmap levels, so I have no influence on the clamping. So you mean if I sort of inset my map tile by adding 6 pixels to each of the edges if would work? Gonna try that... only I don't know how to teach ImageMagick to do this yet :-)
BTW: just tried to emulate what you've suggested by setting the texture coords to 0.1..0.9 and it did work. No distortions anymore. Now I know what to do.

Thanks again! I really appreciate it!

Steff
Actually, you can try just insetting your texture coordinates without resampling your texures first. E.g., if your textures are 256x256, try setting your texcoords to vary from [0.5/255.0 .. 254.5/255.0] or [1.0/255.0 .. 254.0/255.0]. That might be enough for your purposes.
gonk is offline   Reply With Quote
Old 12-12-2009, 08:22 PM   #13 (permalink)
Registered Member
 
darth_steff's Avatar
 
Join Date: Nov 2009
Location: Germany
Posts: 14
darth_steff is on a distinguished road
Default

Quote:
Originally Posted by gonk View Post
Actually, you can try just insetting your texture coordinates without resampling your texures first. E.g., if your textures are 256x256, try setting your texcoords to vary from [0.5/255.0 .. 254.5/255.0] or [1.0/255.0 .. 254.0/255.0]. That might be enough for your purposes.
Whoops, I already resampled my textures - would have had to do that anyway as I wanted to switch to 512x512 textures. Works fine now except one thing: I can't seem to get the tex coords right. My inset is 6px so the coords should be [6.0/512 ... 506/512] right? Still I see black border texels, especially when zooming out.

Best regards,
Steff
darth_steff is offline   Reply With Quote
Old 12-12-2009, 10:45 PM   #14 (permalink)
Registered Member
 
Join Date: Nov 2008
Posts: 129
gonk is on a distinguished road
Default

Quote:
Originally Posted by darth_steff View Post
Whoops, I already resampled my textures - would have had to do that anyway as I wanted to switch to 512x512 textures. Works fine now except one thing: I can't seem to get the tex coords right. My inset is 6px so the coords should be [6.0/512 ... 506/512] right? Still I see black border texels, especially when zooming out.

Best regards,
Steff
Don't fill your border texels with black. They need to be the same color as their corresponding interior texel. So, your texture should like like you drew it in a quad that was slightly too big, and you drew it with clamping turned on. You are trying to simulate the effect of clamping yourself since the iphone isn't doing it right.

And you probably don't need to use 6, I'm just saying that I think that's what I'm using.

As for whether the range should be [6.0/512.0 ... 506.0/512.0] or
[6.5/512.0...] or [5.5/512.0...] I don't know... you'll just have to experiment until it looks seamless.

Again, I have no idea if this is the right way or the best way to do this, I'm just saying it works for me.
gonk is offline   Reply With Quote
Reply

Bookmarks

Tags
mipmapping, opengl, texturing, tiled map

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On



» Advertisements
» Online Users: 402
7 members and 395 guests
13dario13, ChrisYates, fredidf, iOS.Lover, Leslie80, Wikiboo, Yosh_K
Most users ever online was 1,387, 04-10-2012 at 04:21 AM.
» Stats
Members: 175,670
Threads: 94,121
Posts: 402,903
Top Poster: BrianSlick (7,990)
Welcome to our newest member, Yosh_K
Powered by vBadvanced CMPS v3.1.0

All times are GMT -5. The time now is 04:40 AM.
Powered by vBulletin® Version 3.8.0
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Friendly URLs by vBSEO 3.3.0