PDA

View Full Version : How to get the seams out of tiled zooming?



WILL
31-08-2007, 02:09 AM
In one of my latest projects, I'm having some difficulty trying to get large tiles to zoom in and out, yet stay next to one another without a noticeable seam.

At first I thought it had to do with lack of precision, then I noticed some 'glitchy-ness' with the floating point values I was using. Then I got curious and I tried to flat out overlap them on purpose. What I found out blew my mind. :o There was a black seam even though it should have in theory just be the texture overlapped over the other.

So what am I doing or not doing wrong here?

I am using OpenGL in Ortho mode, and here is my function that I use to do this...

procedure DrawMap_Seamless(X, Y, SrcWidth, SrcHeight, DestWidth, DestHeight: Real; Texture: TTexture);
var
texX, texY : Real;
begin
glEnable(GL_TEXTURE_2D); //Enable Texturing
glBindTexture(GL_TEXTURE_2D, Texture.ID); //Bind Texture

glDisable(GL_BLEND);
glEnable(GL_ALPHA_TEST); //Enable Alpha (Transparency) (Uses Texture Alpha channel)
glAlphaFunc(GL_GREATER, 0.1); //Set Alpha settings

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

BeginOrtho; //Switch to 2D mode
glPushMatrix; //Save Current Matrix
glTranslatef(X, Y, 0); //Move to Objects location

texX := SrcWidth / Texture.Width;
texY := SrcHeight / Texture.Height;

glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2f(0, 0);

glTexCoord2f(texX, 0);
glVertex2f(DestWidth, 0);

glTexCoord2f(texX, texY);
glVertex2f(DestWidth, DestHeight);

glTexCoord2f(0, texY);
glVertex2f(0, DestHeight);
glEnd;

glPopMatrix; //Reload Old Matrix
EndOrtho;
end;


Now here is the code I use to draw my wonderful (2048x2048 32-bit png imported texture) map...

// Draw Map
if (ScreenX <ScreenWidth> MapWidth - ScreenWidth / MapZoom / 2) then
DrawMap_Seamless((-ScreenX * MapZoom + (MapTex[0].Width * MapZoom) + (MapTex[1].Width * MapZoom)) + ScreenWidth / 2,
(-ScreenY * MapZoom) + ScreenHeight / 2,
MapTex[0].Width, MapTex[0].Height,
(MapTex[0].Width * MapZoom),
(MapTex[0].Height * MapZoom),
MapTex[0]);

ScreenX, ScreenY are the map scroll position offsets.
MapTex[] is obviously my array of TTexture objects (just the 2; 0 and 1) TTexture.ID is where I store the GL texture number.
MapZoom is obviously the current zoom size that I'm currently using.

So how do I get rid of this annoying black seam?

I can post a demo if it's needed.

vgo
31-08-2007, 05:09 AM
Try playing with the glTexEnv settings, try adjusting the clamping to GL_CLAMP, GL_CLAMP_TO_EDGE and GL_REPEAT to see if it makes any difference. Also if you clamp the edges try using instead of 0.0 and 1.0 something like 0.001 and 0.999 as texture coordinates, this way the edges should be filtered smoothly.

WILL
31-08-2007, 02:14 PM
Looking up more about GL_CLAMP and GL_CLAMP_TO_EDGE, I have come across this glTexParameter (http://www.khronos.org/opengles/documentation/opengles1_0/html/glTexParameter.html). It doesn't seem to do a very good job of explaining the difference between GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T.

What is the difference, why would I use one or the other and what would you recommend?

Please note that this is the first time playing around with textures in this manner so some added explanations would help greatly, thanks! :)

WILL
31-08-2007, 02:27 PM
It seems that my 2nd block of code is missing the 3 other function calls. :?


...the missing code...
DrawMap_Seamless((-ScreenX * MapZoom) + ScreenWidth / 2,
(-ScreenY * MapZoom) + ScreenHeight / 2,
MapTex[0].Width, MapTex[0].Height,
(MapTex[0].Width * MapZoom),
(MapTex[0].Height * MapZoom),
MapTex[0]);
DrawMap_Seamless((-ScreenX * MapZoom + (MapTex[0].Width * MapZoom)) + ScreenWidth / 2,
(-ScreenY * MapZoom) + ScreenHeight / 2,
MapTex[1].Width, MapTex[1].Height,
(MapTex[1].Width * MapZoom),
(MapTex[1].Height * MapZoom),
MapTex[1]);
if (ScreenX > MapWidth - ScreenWidth / MapZoom / 2) then
DrawMap_Seamless((-ScreenX * MapZoom + (MapTex[0].Width * MapZoom) + (MapTex[1].Width * MapZoom)) + ScreenWidth / 2,
(-ScreenY * MapZoom) + ScreenHeight / 2,
MapTex[0].Width, MapTex[0].Height,
(MapTex[0].Width * MapZoom),
(MapTex[0].Height * MapZoom),
MapTex[0]);

vgo
31-08-2007, 07:11 PM
About glTexParameteri/f...

It controls the texture coordinate wrapping. In this case the target is always GL_TEXTURE_2D and parameters GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T.

WRAP_S is the s coordinate or "x", if you like and WRAP_T is the t coordinate ie. "y" for the textures. The coordinates start from bottom left (s:0, t:0) and go to top right (s:1, t:1).

Setting the WRAP parameters control how the texture edges are handled, GL_CLAMP clamps the coordinates between 0 and 1, but depending on filtering, mip map levels and such there might be visible seams between adjacent polygons.

GL_CLAMP_TO_EDGE is pretty much the same, but it filters the edges better and usually there's no visible seams. Use this for example on skyboxes to avoid seams on the cube faces.

GL_REPEAT repeats the texture coordinates, if you set the coordinates outside the range of (0,1) they are interpolated and this causes the texture to repeat over and over again on the polygon. This is useful for walls, floors and such that has repeating pattern.

Try experimenting with these parameters and use different values for texture coordinates to see what they do. Just draw a single quad on the screen and see what happens.

You should definitely have a look at Red Book (http://fly.cc.fer.hr/~unreal/theredbook/), it's a bit outdated but it covers the basic stuff and is still a good reference for beginners.

WILL
01-09-2007, 02:41 AM
Ok I figured out the specifics thanks you your help. However it seems that it doesn't quite help much with regards to the problem. :?


So... I've uploaded a copy of the executable and source so you can see and point out what stupid thing I'm doing. :P

EDIT: I had to re-upload it... doing that now... Done! Here it is. (http://www.pascalgamedevelopment.com/will/Invaded_problemchild.rar) :)

I've left the GraphicsUnit.pas function with the debugging code in so you can see what I'm trying to do to see why I'm not able to 'overlap' these dame textures to eliminate the black line.

I've tried clamping to get rid of any artifact that might be showing on the edge and I've also tried to overlap the two textures to prevent a small gap by messy floating point values. Neither seem to be working. :roll:

Look for the function 'DrawMap_Seamless' and the function calls I make in the main project source file. I'm either calculating something wrong or I'm not setting something properly.

GL_CLAMP_TO_EDGE does not exist in the gl.pas version that is included with the version of JEDI-SDL I'm using btw! :doh:

vgo
01-09-2007, 06:59 AM
So... I've uploaded a copy of the executable and source so you can see and point out what stupid thing I'm doing. :P


I'm having a bit of problems on getting Lazarus working and compiling your program... :)



Look for the function 'DrawMap_Seamless' and the function calls I make in the main project source file. I'm either calculating something wrong or I'm not setting something properly.


Couldn't spot anything obviously wrong in there...



GL_CLAMP_TO_EDGE does not exist in the gl.pas version that is included with the version of JEDI-SDL I'm using btw! :doh:

In SDL you have to load the OpenGL_1_2 extension, like this: glext_LoadExtension('GL_version_1_2') in your OpenGL initialization.

I did a quick test in my planet renderer with the two different wrap settings. Ignore the black triangles, they're caused by a bug in my edge splitting code. :P

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
http://www.projectminiverse.com/images/screenshots/Planets18.jpg

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
http://www.projectminiverse.com/images/screenshots/Planets19.jpg

As you can see there's quite a difference on how the edges are filtered. I think that GL_CLAMP_TO_EDGE will fix your problem. :)

WILL
02-09-2007, 05:28 AM
Ok makes sense. I found where GL_CLAMP_TO_EDGE is located in the JEDI-SDL source. However there is another problem with the damn thing. Namely the glext.pas unit supplied with JEDI-SDL.


In SDL you have to load the OpenGL_1_2 extension, like this: glext_LoadExtension('GL_version_1_2') in your OpenGL initialization.

This is the part that I'm stuck on. For some reason glext.pas freaks out on line 3790 giving me a SIGSEGV (memory) error.

Is there some kind of library file I need to pull this off?

Here is the function that dies on me...
function Load_GL_version_1_2: Boolean;
{var
extstring : PChar;}
begin

Result := FALSE;
//extstring := glGetString( GL_EXTENSIONS );

@glCopyTexSubImage3D := SDL_GL_GetProcAddress('glCopyTexSubImage3D'); // <-- This is the line!
if not Assigned(glCopyTexSubImage3D) then Exit;
@glDrawRangeElements := SDL_GL_GetProcAddress('glDrawRangeElements');
if not Assigned(glDrawRangeElements) then Exit;
@glTexImage3D := SDL_GL_GetProcAddress('glTexImage3D');
if not Assigned(glTexImage3D) then Exit;
@glTexSubImage3D := SDL_GL_GetProcAddress('glTexSubImage3D');
if not Assigned(glTexSubImage3D) then Exit;

Result := TRUE;

end;

I should also note that I'm using JEDI-SDL 1.0 BETA 1. Any ideas? :?

vgo
02-09-2007, 08:05 AM
Sounds like SDL_GL_GetProcAddress has not been initialized and that's why it screws up things. I have no idea why it's not initialized, though.

Your SDL initialization seems to be quite a bit different than mine, maybe that's the problem?

Here's a snippet from my PGD2007 entry's main program initialization:

// Initialize SDL
if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) then
begin
WriteToErrorLog( Format( 'Could not initialize SDL : %s', [SDL_GetError] ), 'Application', 'Main' );
TerminateApplication;
end;

// Fetch the video info
videoInfo := SDL_GetVideoInfo;

if ( videoInfo = nil ) then
begin
WriteToErrorLog( Format( 'Video query failed : %s', [SDL_GetError] ), 'Application', 'Main' );
TerminateApplication;
end;

// the flags to pass to SDL_SetVideoMode
videoFlags := SDL_OPENGL; // Enable OpenGL in SDL
videoFlags := videoFlags or SDL_DOUBLEBUF; // Enable double buffering
videoFlags := videoFlags or SDL_HWPALETTE; // Store the palette in hardware

// This checks to see if surfaces can be stored in memory
if ( videoInfo^.hw_available <> 0 ) then
videoFlags := videoFlags or SDL_HWSURFACE
else
videoFlags := videoFlags or SDL_SWSURFACE;

// This checks if hardware blits can be done * /
if ( videoInfo^.blit_hw <> 0 ) then
videoFlags := videoFlags or SDL_HWACCEL;

// Set the OpenGL Attributes
SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 8 );

// Set the title bar in environments that support it
SDL_WM_SetCaption( APP_NAME + ' [' + VERSION + ']', nil );


videoflags := videoFlags or SDL_RESIZABLE; // Enable window resizing
//videoflags := videoFlags or SDL_FULLSCREEN; // Enable window resizing
if ReadSettings(ExtractFilePath(ParamStr(0)) + 'engine.ini') then
begin
if DisplayFullscreen then
videoflags := videoFlags or SDL_FULLSCREEN;
surface := SDL_SetVideoMode( DisplayWidth, DisplayHeight, SCREEN_BPP, videoflags );
end
else
begin
DisplayWidth := SCREEN_WIDTH;
DisplayHeight := SCREEN_HEIGHT;
surface := SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, videoflags );
end;
if ( surface = nil ) then
begin
WriteToErrorLog( Format( 'Unable to create OpenGL screen : %s', [SDL_GetError] ), 'Application', 'Main' );
TerminateApplication;
end;

// Initialize game engine
InitEngine;


Through InitEngine:

glClearColor(0.0, 0.0, 0.0, 0.0);
//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
glShadeModel(GL_SMOOTH);
glMatrixMode(GL_MODELVIEW);
glEnable(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

if not glext_LoadExtension('GL_version_1_2') then
s := 'OpenGL version 1.2 not supported!' + #13#10;

// Load extensions

for i := 0 to MAX_EXTENSION do
if glext_ExtensionSupported(PChar(Extensions[i]), #0) then
glext_LoadExtension(Extensions[i])
else
s := Extensions[i] + #13#10;
if s <> '' then
raise EOpenGLError('All needed OpenGL extensions are not supported! Unsupported extensions: ' + #13#10 + s);


You could try adding those extra flags and set the attributes in SDL initialization, maybe that'll help?

I haven't used SDL that much, so I can't really help if that doesn't work... :(

WILL
02-09-2007, 09:24 AM
Oh man! :doh: Thats why...

Ok, I was expecting that I had to load the extension BEFORE I initialized video. Now that I think about it, thats kinda daft. How else would SDL know about the GL loaded extensions. :lol:

Ok well it's working and MAN! does it look pretty now. ;)


Thanks a ton for the help. Do you have maybe a better explanation as to why exactly the black border around the textures is removed? And whats the deal with GL_CLAMP anyhow if it doesn't apparently do much...?

vgo
02-09-2007, 10:26 AM
I'm glad that you got it working. :)

If I remember correctly GL_CLAMP_TO_EDGE leaves a small border for filtering and GL_CLAMP actually clamps the coordinates to the edges leaving no border, this however may cause problems when using filtering. If you disable filtering then GL_CLAMP should look just fine. I think. :)

From the OpenGL specs:


GL_TEXTURE_WRAP_S

Sets the wrap parameter for texture coordinate s to either GL_CLAMP, GL_CLAMP_TO_EDGE, or GL_REPEAT. GL_CLAMP causes s coordinates to be clamped to the range &#91;0, 1&#93; and is useful for preventing wrapping artifacts when mapping a single image onto an object. GL_CLAMP_TO_EDGE causes s coordinates to be clamped to the range &#91; 1/2N , 1 - 1/2N &#93; , where N is the size of the texture in the direction of clamping. GL_REPEAT causes the integer part of the s coordinate to be ignored; the GL uses only the fractional part, thereby creating a repeating pattern. Border texture elements are accessed only if wrapping is set to GL_CLAMP. Initially, GL_TEXTURE_WRAP_S is set to GL_REPEAT.


EDIT: If you still get visible seams even with GL_CLAMP_TO_EDGE then you could try setting your texture coordinates to the range [0.001 - 0.999] or something.