fract() and mipmapping. How would I solve this?

I’m having the same problem as this dude here: https://www.opengl.org/discussion_boards/showthread.php/163796-Texture-wrapping-in-shader-mipmapping

However, none of the solutions make much, if any sense to me.

I’m making a shader for a game engine that isn’t my own, so changes on the C++ side of things, such as using GL_REPEAT isn’t possible, not to mention I need to make this scroll effect work on things that don’t have GL_REPEAT.

My shader code:


vec4 Process(vec4 color)
{
	vec2 basetex = gl_TexCoord[0].st;
	vec2 fliptex = gl_TexCoord[0].st*-1; // Flip
	
	// Scroll
	basetex.y -= timer * 0.25;
	fliptex.y += timer * 0.50;
	
	basetex.y = fract(basetex.y);
	fliptex.y = fract(fliptex.y);
	
	// This is how I'd normally do things
	// but due to the fract() above, it creates
	// a seam thanks to mipmapping... ugh.
	vec4 baseTexel = getTexel(basetex);
	vec4 flipTexel = getTexel(fliptex);
	
	// Workaround for mipmap seam by using the base LOD.
	//vec4 baseTexel = texture2DLod(tex, basetex, 0);
	//vec4 flipTexel = texture2DLod(tex, fliptex, 0);
	
	// Darken layers.
	baseTexel.rgb *= 0.75;
	flipTexel.rgb *= 0.25;
	
	vec4 finalTexel = baseTexel+flipTexel;
	
	return finalTexel;
}

Also, this engine uses #version 120, so my options are limited. Anyhow, in short, I need to make this shader mipmap-friendly, at the very least, find some way to hide that 1px seam, with an example that makes sense. (I learn by actually being shown things, so preferably in the form of a modified version of my shader there… Not just “hey, use this function, but I’m not going to explain how to use it logically in your situation.”)

If you know that basetex.y and fliptex.y will be in the range [0,1], then:


        // these are guaranteed to be in the range [0,2]
        // the fractional part will match the original version
	basetex.y += fract(-timer * 0.25);
	fliptex.y += fract(timer * 0.50);

        // for the range [0,1]
	vec4 baseTexel1 = getTexel(basetex);
	vec4 flipTexel1 = getTexel(fliptex);
        // for the range [1,2]
	vec4 baseTexel2 = getTexel(basetex-vec2(0,1));
	vec4 flipTexel2 = getTexel(fliptex-vec2(0,1));

	vec4 baseTexel = basetex.y<1 ? baseTex1 : baseTex2;
	vec4 flipTexel = fliptex.y<1 ? flipTex1 : flipTex2;

Ok, I’ve tried that, it works mostly… but I have a new problem. The 2nd layer (fliptex), seems to scroll, and then visibly snap back every so often.

Found a solution:


vec4 getTexelWrapped(vec2 coord)
{
	if(coord.y < 0) return getTexel(coord+vec2(0,1));
	else if(coord.y < 1) return getTexel(coord);
	else return getTexel(coord-vec2(0,1));
}

vec4 Process(vec4 color)
{
	vec2 basetex = gl_TexCoord[0].st;
	vec2 fliptex = gl_TexCoord[0].st*-1; // Flip
	
	// Scroll
	basetex.y -= fract(timer * 0.25);
	fliptex.y += fract(timer * 0.50);
	
	// Get result
	vec4 baseTexel = getTexelWrapped(basetex);
	vec4 flipTexel = getTexelWrapped(fliptex);
	
	// Darken layers.
	baseTexel.rgb *= 0.75;
	flipTexel.rgb *= 0.25;
	
	vec4 finalTexel = baseTexel+flipTexel;
	finalTexel.a = baseTexel.a * color.a;
	
	return finalTexel;
}

Ah, right; fliptex.y is in the range [-1,0], not [0,1]. I suggest changing its initialisation to:


vec2 fliptex = vec2(1,1)-gl_TexCoord[0].st;

[QUOTE=StrikerTHF;1291703]Found a solution:


vec4 getTexelWrapped(vec2 coord)
{
	if(coord.y < 0) return getTexel(coord+vec2(0,1));
	else if(coord.y < 1) return getTexel(coord);
	else return getTexel(coord-vec2(0,1));
}

[/QUOTE]
Don’t do this. Derivatives (including the implicit derivatives used for LoD calculation) are undefined within non-uniform control flow. So texture lookups with implicit derivatives (which basically means all texture lookups in 1.2) shouldn’t occur inside conditionals.

[QUOTE=GClements;1291704]
Don’t do this. Derivatives (including the implicit derivatives used for LoD calculation) are undefined within non-uniform control flow. So texture lookups with implicit derivatives (which basically means all texture lookups in 1.2) shouldn’t occur inside conditionals.[/QUOTE]

In english? (No offense, but this is terminology that only someone advanced would understand. Layman’s terms, please.)

I don’t exactly understand what you’re trying to say, but going on a pure guess, would this be better?


vec4 getTexelWrapped(vec2 coord)
{
	vec4 wrapTex0 = getTexel(coord+vec2(0,1));
	vec4 wrapTex1 = getTexel(coord);
	vec4 wrapTex2 = getTexel(coord-vec2(0,1));
	
	if(coord.y < 0) return wrapTex0;
	else if(coord.y < 1) return wrapTex1;
	else return wrapTex2;
}

Any way to do this for both axis (x and y) at the same time?

Yeah, back to the drawing board… because I found that I need to be able to wrap in both axes.

Tried this, causes the same seam issue as just using fract on the coordinates… not sure why though, it’s doing essentially the exact same thing as the code in my last post.


vec4 getTexelWrapped(vec2 coord)
{	
	if(coord.y < 0) coord.y += 1.0;
	else if(coord.y > 1) coord.y -= 1.0;
	
	if(coord.x < 0) coord.x += 1.0;
	else if(coord.x > 1) coord.x -= 1.0;
	
	return getTexel(coord);
}

Anyone got any solutions?

EDIT: Figured out something myself that suits my needs, I’ll share it for anyone who wants it:


vec4 getTexelWrapped(vec2 coord)
{	
	return texture2DGrad(tex, fract(coord), dFdx(coord), dFdy(coord));
}

vec4 Process(vec4 color)
{
	vec2 basetex = gl_TexCoord[0].st;
	vec2 fliptex = gl_TexCoord[0].st*-1; // Flip
	
	// Scroll
	basetex.y -= timer * 0.25;
	fliptex.y += timer * 0.50;
	
	// Get result
	vec4 baseTexel = getTexelWrapped(basetex);
	vec4 flipTexel = getTexelWrapped(fliptex);
	
	// Darken layers.
	baseTexel.rgb *= 0.75;
	flipTexel.rgb *= 0.25;
	
	vec4 finalTexel = baseTexel+flipTexel;
	finalTexel.a = baseTexel.a * color.a;
	
	return finalTexel;
}

Non-uniform control flow means any expression which may be evaluated for one fragment (or vertex) but not for another. So anything inside if/else or ?:.

Yes.

Sample at 4 points then use two nested conditionals, one for x and one for y.

That’s a typical solution for later versions, but the textureGrad() functions don’t exist in GLSL 1.2 (and the textureLod() functions can’t be used in fragment shaders in 1.2).

[QUOTE=GClements;1291714]
That’s a typical solution for later versions, but the textureGrad() functions don’t exist in GLSL 1.2 (and the textureLod() functions can’t be used in fragment shaders in 1.2).[/QUOTE]
Seems that, from what I’m finding in documentation, texture2DGrad and texture2DLod are valid in 1.2, but were deprecated in 1.3 in exchange for textureGrad and textureLod.

Either way, thank you, I appreciate the help.

The GLSL 1.2 specification doesn’t have textureGrad(). It has textureLod(), but says (§8.7)

The built-ins suffixed with “Lod” are allowed only in a vertex shader.

Odd. Not sure why it works then. Does it make any difference that #extension GL_EXT_gpu_shader4 : enable is defined?

EDIT: Ah, yes, seems it does: https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_gpu_shader4.txt

That must have been what I was looking at before.