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

Question about Frac() in GLSL shader

Started by Ruggostein May 6, 2015 at 2:38 PM 7 replies 13.2k views
Original Post
Ruggostein
Ruggostein

When doing frac(X) it returns the fractional part of X, as we all know.

And this is a value in the range 0 to 0.99999999...., never 1.0, is this correct?

I'm trying to find a way to calculate frac(x) that can be used for texture coordinates, where the input will be any UV from 0 to a large number, which I want to to convert to 0..1 range.

Because if we have for example a quad with uvs from 0 to 1, doing frac() on them would turn all UVs into zero.

Of course, I could use a if like this

dest_u = frac(src_u);

if (dest_u <=0.0) {

dest_u = 1.0;

}

But I need this for mobile phones, including old 2.3 Android devices (openGL ES), so I'm trying to find a math solution that does not require ifs.

I know that in some situations it is possible to use min() and max() to replace some ifs, but in this case I'm not finding how to get there, anyone have an idea?

AlexMekhed
AlexMekhed

To convert 0..N to 0..1 you have to divide every UV by N. What you're describing is periodic sawtooth wave. In case you need sawtooth wave, why dont you convert your UVs outside of the shader? If you cant, you can do something like this:


float epsilon = 0.0000001; // some small value
dest_u = frac(max(src_u - epsilon, 0));

It's approximate solution, but it will solve the problem with 0..1 quad

Ruggostein
Ruggostein

The problem is that I dont have have a specific N value. I'm trying to implement a scheme to allow virtualization of textures, meaning the models will come with UVs with a generic range, some will have 0..1 UVs, others use larger values for wrapping textures.

What I'm trying to do is automatically pack tons of small textures into a huge texture, so I can draw everything in a single draw.

So in the vertex shader I will convert the UVs from the original range to the UV that corresponds to the sub rect in the generated texture atlas.

In this case the texture atlas will be updated with some frequency, as I'm trying to do some texture streaming, so I can't just generate the atlas once and update the uvs in the cpu.

Your suggestion could work, but I'm afraid it will introduce texture sampling errors.

AlexMekhed
AlexMekhed

Try this:


dest_u = floor(src_u) - ceil(src_u) + min(ceil(src_u),1) + frac(src_u);
Ruggostein
Ruggostein

Just ran that formula through a couple values in my head, seems to work, yes, thanks, I'll confirm it later when I'm home :)

Krypt0n
Krypt0n

you worry too much about it.

textures never reach 1.0, take for example point filtering (nearest sampling) and a texture of 256 texel. to sample from it you pass UVs from the shader to the texture units, the texture unit scales the value by the texture size, so in our case


uv*=256;

now it indexes into our texture


color = texture[ (int)uv.x];

as our texture is 256 in size, valid values are 0-255 (inclusive),

if uv.x is originally 1.f, it will result in 256.f and casting to int won't change it, of course. we would have an overflow, thus the texture unit actually does


color = texture[  ((int)uv.x) % 256];

and it makes sense. imagine you address your first texel using 0.0f and you have uv wrapping enabled, if 1.0 would be the right most corner of the texture, then wrapping around would actually mean that the next time you reach the first texel is 1.0000001f, that would make the first wrap 1.f in size (1.f-0.f) and the second would be (2.0f-1.00001f) 0.99999f

so, to wrap textures around, you simply need to use


dest_u = frac(src_u);

but it might be not correct to do it in vertexshader, if a triangle crosses the border, you'd need to frac per pixel.

if triangles don't cross border, then you could just as good pre-frac the values on CPU side.

Ruggostein
Ruggostein

I'm not sure I understand what you're trying to say, you're talking about sampling the textures, and the calculation of the UVs comes before that.

If I pass a quad to OpenGL with top left corner with UVs (0, 0) and bottom right (1.0, 1.0) those are the exact values that reach the fragment shader.

Doing just frac() on those UVs will return 0.0 to all of them and only then I sample the textures using tex2D(), so the values will be all wrong.

Krypt0n
Krypt0n

If I pass a quad to OpenGL with top left corner with UVs (0, 0) and bottom right (1.0, 1.0) those are the exact values that reach the fragment shader.

that's so much to explain, but I will give it another try smile.png

imagine you want to simulate texture wrapping by drawing 4 tiles/quads; (I'll do it in 1D to not obfuscate it with nested loops)
for(float x=0;x<4;x++)
{
  glBeing(GL_QUAD);
    glTexCoord2f(x    ,0.f);    glVertex2f(x/4.f,      0.f)
    glTexCoord2f(x    ,1.f);    glVertex2f(x/4.f,      1.f)
    glTexCoord2f(x+1.f,1.f);    glVertex2f((x+1.f)/4.f,1.f)
    glTexCoord2f(x+1.f,0.f);    glVertex2f((x+1.f)/4.f,0.f)
  glEnd();
}
lets focus only on the texture coordinates
(I assume we agree that's how you'd do manual wrapping, right?)

manually unrolled we would get those UV sets for the quads
(0.0, 0.0) (1.0, 1.0)//quad 0
(1.0, 0.0) (2.0, 1.0)//quad 1
(2.0, 0.0) (3.0, 1.0)//quad 2
(3.0, 0.0) (4.0, 1.0)//quad 3
now, what would 1.0 mean? if we follow your assumption, it's the right most corner of the texture. would the 2nd quad then start with the right most corner of the texture too?
and if that would be the case, would that mean that the right most texel column would be drawn twice?

how would a quad like that be mapped:
(-1.0, 0.0) (0.0, 1.0)//quad -1
would 0 be the left most row es in quad 0? or would it be the right most row?


Doing just frac() on those UVs will return 0.0 to all of them and only then I sample the textures using tex2D(), so the values will be all wrong.

that's why I've said it's not correct to do that per vertex. you've said you will get all kind of UVs and you cannot predict the range, so what would happen if you get
(0.5, 0.0) (1.5, 1.0)
?
after a frac per vertex you will get
(0.5, 0.0) (0.5, 1.0)
and all the UV values you pass to the fragment program will be 0.5, isn't that the exact same problem? (Unless I really miss something).

but if that is the problem, you really need to do the frac in the pixelshader.
21st Century Moose
21st Century Moose

With older GL versions using frac is conformant with the GL specification; for example, 3.8.7 in the GL 1.5 specification states:

Wrap mode REPEAT ignores the integer part of texture coordinates, using only the fractional part.

Newer GL versions specify it slightly differently; for example 4.4 core (section 8.14.2) specifies it as:

REPEAT: coord mod size

Similar conventions apply to other modes (such as CLAMP_TO_EDGE) and you can refer to the specifications (same sections) for those.

So either way you'll see that a conformant OpenGL implementation never actually uses texcoords in a 0..1 range; the upper-bound of the range is always less than 1, and it's totally correct that if you feed it a texcoord of 1 that it will use 0 instead.

So if you wish to be consistent with the way OpenGL handles it, you should do the same. In other words: it's perfectly OK to use frac here, and you've nothing to worry about.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls. 

Topic Locked

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

Sign in to reply to this topic.