How to use GL_REPEAT to repeat only a selection of a texture atlas? (OpenGL)
Asked Answered
M

7

18

How can I repeat a selection of a texture atlas?

For example, my sprite (selection) is within the texture coordinates:

GLfloat textureCoords[]=
{
    .1f,  .1f,
    .3f,  .1f,
    .1f,  .3f,
    .3f,  .3f
};

Then I want to repeat that sprite N times to a triangle strip (or quad) defined by:

GLfloat vertices[]=
{
   -100.f, -100.f,
    100.f, -100.f,
   -100.f,  100.f,
    100.f,  100.f
};

I know it has something to do with GL_REPEAT and textureCoords going passed the range [0,1]. This however, doesn't work: (trying to repeat N = 10)

GLfloat textureCoords[]=
{
    10.1f,  10.1f,
    10.3f,  10.1f,
    10.1f,  10.3f,
    10.3f,  10.3f
};

We're seeing our full texture atlas repeated...

How would I do this the right way?

Martines answered 19/3, 2009 at 12:59 Comment(0)
M
19

It can't be done the way it's described in the question. OpenGL's texture coordinate modes only apply for the entire texture.

Normally, to repeat a texture, you'd draw a polygon that is "larger" than your texture implies. For instance, if you had a square texture that you wanted to repeat a number of times (say six) over a bigger area, you'd draw a rectangle that's six times as wide as it is tall. Then you'd set the texture coordinates to (0,0)-(6,1), and the texture mode to "repeat". When interpolating across the polygon, the texture coordinate that goes beyond 1 will, due to repeat being enabled, "wrap around" in the texture, causing the texture to be mapped six times across the rectangle.

None of the texture wrap modes support the kind of operation as described in the question, i.e. they all map to the full [0,1] range, not some arbitrary subset. when you're texturing using just a part of the texture, there's no way to specify that larger texture coordinate in a way that makes OpenGL repeat it inside only the sub-rectangle.

You basically have two choices: Either create a new texture that only has the sprite you need from the existing texture or write a GLSL vertex program to map the texture coordinates appropriately.

Martines answered 19/3, 2009 at 15:28 Comment(3)
Thanks to "unwind", "Jimmy J" and "TrayMan" for your contributions. I voted your answers up and compiled this answer from yours which now describes very well what's going on.Martines
>"or write a GLSL vertex program to map the texture coordinates appropriately." There must be fragment program instead.Nona
That depends, Sarge. Using a tessellation technique is more vertex than fragment. And you can usually optimize by not performing texture coordinates calculation in texture shader - specially with tile based deferred rendering.Locust
F
6

I'm not sure you can do that. I think OpenGL's texture coordinate modes only apply for the entire texture. When using an atlas, you're using "sub-textures", so that your texture coordinates never come close to 0 and 1, the normal limits where wrapping and clamping occurs.

There might be extensions to deal with this, I haven't checked.

EDIT: Normally, to repeat a texture, you'd draw a polygon that is "larger" than your texture implies. For instance, if you had a square texture that you wanted to repeat a number of times (say six) over a bigger area, you'd draw a rectangle that's six times as wide as it is tall. Then you'd set the texture coordinates to (0,0)-(6,1), and the texture mode to "repeat". When interpolating across the polygon, the texture coordinate that goes beyond 1 will, due to repeat being enabled, "wrap around" in the texture, causing the texture to be mapped six times across the rectangle.

This is a bit crude to explain without images.

Anyway, when you're texturing using just a part of the texture, there's no way to specify that larger texture coordinate in a way that makes OpenGL repeat it inside only the sub-rectangle.

Fluky answered 19/3, 2009 at 13:40 Comment(1)
Hmm, GL_CLAMP for example affects the sub-textures you're describing, leading me to the idea texture coordinate mode do apply to parts of a texture atlas. Or am I in the wrong here? (Of course, seeing that I can't repeat the way I'd like, implies I indeed am in the wrong:))Martines
D
5

None of the texture wrap modes support the kind of operation you are looking for, i.e. they all map to the full [0,1] range, not some arbitrary subset. You basically have two choices: Either create a new texture that only has the sprite you need from the existing texture or write a GLSL pixel program to map the texture coordinates appropriately.

Denbighshire answered 19/3, 2009 at 14:3 Comment(0)
G
1

While this may be an old topic; here's how I ended up doing it:

A workaround would be to create multiple meshes, glued together containing the subset of the Texture UV's.

E.g.: I have a laser texture contained within a larger texture atlas, at U[0.05 - 0.1] & V[0.05-0.1].

I would then construct N meshes, each having U[0.05-0.1] & V[0.05-0.1] coordinates. (N = length / texture.height; height being the dimension of the texture I would like to repeat. Or easier: the amount of times I want to repeat the texture.)

This solution would be more cost effective than having to reload texture after texture. Especially if you batch all render calls (as you should).

(OpenGL ES 1.0,1.1,2.0 - Mobile Hardware 2011)

Goodin answered 9/12, 2011 at 11:48 Comment(0)
B
1

Can be done with modulo of your tex-coords in shader. The mod will repeat your sub range coords.

Bergstein answered 10/3, 2020 at 15:55 Comment(0)
H
0

I was running into your question while working on the same issue - although in HLSL and DirectX. I also needed mip mapping and solve the related texture bleeding too.

I solved it this way:

min16float4 sample_atlas(Texture2D<min16float4> atlasTexture, SamplerState samplerState, float2 uv, AtlasComponent atlasComponent)
{
  //Get LOD
  //Never wrap these as that will cause the LOD value to jump on wrap
  //xy is left-top, zw is width-height of the atlas texture component
  float2 lodCoords = atlasComponent.Extent.xy + uv * atlasComponent.Extent.zw;  
  uint lod = ceil(atlasTexture.CalculateLevelOfDetail(samplerState, lodCoords));

  //Get texture size
  float2 textureSize;
  uint levels;
  atlasTexture.GetDimensions(lod, textureSize.x, textureSize.y, levels);
  
  //Calculate component size and calculate edge thickness - this is to avoid bleeding
  //Note my atlas components are well behaved, that is they are all power of 2 and mostly similar size, they are tightly packed, no gaps
  float2 componentSize = textureSize * atlasComponent.Extent.zw;
  float2 edgeThickness = 0.5 / componentSize;

  //Calculate texture coordinates
  //We only support wrap for now
  float2 wrapCoords = clamp(wrap(uv), edgeThickness, 1 - edgeThickness);
  float2 texCoords = atlasComponent.Extent.xy + wrapCoords * atlasComponent.Extent.zw;
  return atlasTexture.SampleLevel(samplerState, texCoords, lod);
}

Note the limitation is that the mip levels are blended this way, but in our use-case that is completely fine.

Hardunn answered 15/2, 2021 at 20:6 Comment(0)
O
-2

Can't be done...

Oriole answered 19/3, 2009 at 13:58 Comment(1)
That's your fixed pipeline talking.Roulade

© 2022 - 2024 — McMap. All rights reserved.