Skip to main content
GameDev.net gamedev.net
🔒 Locked

[SOLVED] Texture Seams using atlas (with screenshots)

Started by RobM May 7, 2009 at 1:17 PM 0 replies 14.7k views
Original Post
RobM
RobM
I'm having a frustrating problem with some texture seams. I've done a fair amount of research on this but I can't seem to get rid of them. I use a texture atlas (2048x2048) which contains a handful of texture tiles, mostly at 512x512, but one at 128x128. I have a material per vertex and I blend between them using a coloured blend tile. I am using manually calculated mipmapping based on Ysaneya's atlas technique (using ddx/ddy). For the purpose of this problem, I can switch the mipmapping off and still see the issue. With no filtering, and with no 0.5 texel adjustments to my UVs, everything looks fine as in the first screenshot. The problem arises when I use linear filtering on my texture atlas and I'm blending between two tiles of different resolutions. I've attached two screenshots, the first screenshot is the issue with no filtering (but the 0.5 UV adjustments added) - in this you can clearly see the seam and also the texels being offset 'correctly' by 0.5. The second screenshot has LINEAR filtering on and the uv offsets - the seam is clearly visible. Just to show that this only happens on blends with texture tiles of different sizes, here's a screenshot of an area with same-size tiles used - no seams. Here is my pixel shader:

float mipmapLevel(float2 coords, float2 texSize)
{
	float2 dx = ddx(coords * texSize);
	float2 dy = ddy(coords * texSize);
	float d = max(dot(dx, dx), dot(dy, dy));
	return 0.4 * log2(d);
}
	
float4 GetAtlasTexture(float id, float2 coords)
{
	// the TextureAtlasLUT is an 8-bit 16x32 texture which contains information about each tile in
	// the texture atlas
	
	// the LUT format is as follows (per tile in the atlas)
	// index 0 - xOffset
	// index 1 - yOffset
	// index 2 - power of 2 value for size of tile (e.g. 9 = 512, 10 = 1024)
	// index 3-15 unused
	// index 16 - xOffset
	// index 17 - yOffset
	// etc...
	
	// material ids are 0-based in the atlas but 1-based in the blendmap
	// the LUT
	// 0.03125 = 1/32 = each row in the LUT
	id = (id-1) * 0.03125;
	
	// get the xy offset of the tile
	// 0.0625 = 1/16 = each column in the LUT
	float4 xStart = tex2D(TextureAtlasLUTSampler, float2(0, id));
	float4 yStart = tex2D(TextureAtlasLUTSampler, float2(0.0625, id));
	
	// convert to 1/256 fraction of the overal atlas
	xStart.b = (xStart.b * 255) / 256;
	yStart.b = (yStart.b * 255) / 256;
	
	// calculate the tileSize by using the power of 2 value
	float4 pwr = tex2D(TextureAtlasLUTSampler, float2(0.0625 * 2, id)) * 255;	
	float tileSize = pow(2.0, pwr.b);
	
	// retrieve the mipmap level for this pixel clamped by the power of 2 value
	float lod = clamp(mipmapLevel(coords, float2(tileSize, tileSize)), 0, pwr.b);
	
	// get the width/height of the mip surface we've decided on
	float size = pow(2.0, pwr.b - lod);
	
	// and compute the fraction size for the tile
	float lodSizeX = size / (tileSize / 2048);
	float lodSizeY = size / (tileSize / 2048);
	
	// tile the coords
	coords = frac(coords);

	// compute the new coordinates
	coords.x = coords.x * ((lodSizeX * (tileSize/2048) - 1.0) / lodSizeX) + (0.5 / lodSizeX) + (xStart.b);
	coords.y = coords.y * ((lodSizeY * (tileSize/2048) - 1.0) / lodSizeY) + (0.5 / lodSizeY) + (yStart.b);
				
	//return the pixel from the correct mip surface of the atlas
	return tex2Dlod(TextureAtlasSampler, float4(coords, 0, lod));
}


The code that calls this method I think is probably irrelevant, in this instance of the issue, it'll get called twice with the same UV coords but a different material ID. The UV coords passed in will be between the range of 0-32, 0-64, 0-128 or 0-256 - depending on the size of the patch being drawn. The frac call brings it into line with the terrain quad size. The issues I've been seeing are all when coords between 0-32 are passed in but I don't think this really matters too much. I'm fairly certain it has to do with the fact that one texture tile in the atlas is smaller than the other one but I just can't figure it out. I'd really appreciate any help on this. Thanks in advance [Edited by - RobMaddison on May 9, 2009 4:45:50 AM]
RobM
RobM
I've made a bit of progress on this problem - I've at least whittled it down to the ddx/ddy calls.

I can get rid of the seams in a very fudged way (but this won't suffice as a solution).

What I've effectively done is moved the ddx/ddy calls outside of some branching I have in my pixel shader.

To 'fix' it, at the very start of my pixel shader, I calculate the two mipmap levels based on the two texture sizes, 512 and 128, pass these values into the GetAtlasTexture method and use the appropriate one based on tile size:

	float2 dx = ddx(coords * 128); // was ddx(coords * texSize);	float2 dy = ddy(coords * 128); // was ddx(coords * texSize);	float d = max(dot(dx, dx), dot(dy, dy));	mipMap128 = 0.4 * log2(d);	dx = ddx(coords * 512); // was ddx(coords * texSize);	dy = ddy(coords * 512); // was ddx(coords * texSize);	d = max(dot(dx, dx), dot(dy, dy));	mipMap512 = 0.4 * log2(d);


Previously, I was calling the mipmapLevel() method with whatever the texture tile size was (in this case either 512 or 128) but GetAtlasTexture() was called upto 4 times and all of this was within some branching (not dynamic branching). Now that it's outside the branching, I don't get any seams at all. It's not really a solution because I obviously don't want to have to calculate all possible mipmap levels for all texture tile sizes (when I'm probably only using 1 or 2 - or in extreme cases, 4).

So my 'new' question, which I hope is easier to answer, is how can calling ddx/ddy (inside a branch) to retrieve mip levels give rise to coordinate issues (or seams)?

Thanks

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.