website articles
texture repetition

Problem



One of the most typical problems with texture mapping of large surfaces is the visible repetition of the texture. While things like GL_ARB_texture_mirrored_repeat can help alleviate the problem a bit by making the period of repetition twice bigger, the hardware cannot solve the problem on its own. However, if we are okey with paying the cost of more than a single texture fetch per sample, then there are pretty decent ways to prevent texture repetition.



The texture to be tiled

Regular texture tiling with GL_REPEAT

The proposed solutions with "Technique 1"



Technique 1



One way to prevent the visual repetition of the texture is to assign a random offset and orientation to each tile of the repetition. We can do that by determining in which tile we are, creating a series of four pseudo-random values for the tile, and then using these to offset and re-orient the texture. Re-orientation can be something as simple as mirroring in x or y or both. This produces a non repeating pattern over the whole surface.

The technique just described comes with some caveats that needs to be solved: First, the pattern will show seams across the tile boundaries, since the differently offseted texture tiles won't match at the tile borders. Secondly, because of the discontinuity introduced on the final texture fetch coordinates themselves, the derivatives will have huge jumps at the tile borders and mipmapping will break apart, creating line artifcats.

One solution to solve both problems is to sample the texture with the offset and orientation mentioned above at four texture tiles, and blend between them when sufficiently close to the border of the current tile (in the possitive U and V directions for example). While this will introduced some amount of blurring in the certain areas of the tile, it is acceptable in most cases, as shown in the image at the beginning of the article.

Of course, for this to work we must use custom texture gradients of course, which must come from the original repeating UV mapping.

The code is pretty simple, and you can find it live in Shadertoy: https://www.shadertoy.com/view/lt2GDd

vec4 textureNoTile( sampler2D samp, in vec2 uv )
{
    ivec2 iuv = ivec2( floor( uv ) );
     vec2 fuv = fract( uv );

    // generate per-tile transform
    vec4 ofa = hash4( iuv + ivec2(0,0) );
    vec4 ofb = hash4( iuv + ivec2(1,0) );
    vec4 ofc = hash4( iuv + ivec2(0,1) );
    vec4 ofd = hash4( iuv + ivec2(1,1) );
    
    vec2 ddx = dFdx( uv );
    vec2 ddy = dFdy( uv );

    // transform per-tile uvs
    ofa.zw = sign( ofa.zw-0.5 );
    ofb.zw = sign( ofb.zw-0.5 );
    ofc.zw = sign( ofc.zw-0.5 );
    ofd.zw = sign( ofd.zw-0.5 );
    
    // uv's, and derivatives (for correct mipmapping)
    vec2 uva = uv*ofa.zw + ofa.xy, ddxa = ddx*ofa.zw, ddya = ddy*ofa.zw;
    vec2 uvb = uv*ofb.zw + ofb.xy, ddxb = ddx*ofb.zw, ddyb = ddy*ofb.zw;
    vec2 uvc = uv*ofc.zw + ofc.xy, ddxc = ddx*ofc.zw, ddyc = ddy*ofc.zw;
    vec2 uvd = uv*ofd.zw + ofd.xy, ddxd = ddx*ofd.zw, ddyd = ddy*ofd.zw;
        
    // fetch and blend
    vec2 b = smoothstep( 0.25,0.75, fuv );
    
    return mix( mix( textureGrad( samp, uva, ddxa, ddya ), 
                     textureGrad( samp, uvb, ddxb, ddyb ), b.x ), 
                mix( textureGrad( samp, uvc, ddxc, ddyc ),
                     textureGrad( samp, uvd, ddxd, ddyd ), b.x), b.y );
}
Note that the code propagates the orientation mirror transformation to the derivatives. Since the underlaying hardware is probably taking the absolute value of these, you can pretty savely optimize those away and simply pass ddx and ddy to the textureGrad() function.

The only remaining caveat with this technique is that the per-tile hash function might by alias at high minification factors. For example, if this technique is used to texture a huge terrain, depending on the way this texturing method is used, aliasing might occur in the horizon or distant parts of the terrain.



Technique 2


Another way to get even more organic looking texture un-tile-fication (just invented a word there) is to bomb the whole surface with randomly scaled, offseted and rotated copies of the original texture which get blended together, with the blending weight factor dependant on the distance to the center of each of these copies. This can be accomplished with a smooth voronoi patter for example. Blending weights proportional to a gaussian fallof for each feature point in the voronoi pattern works fine. Just remember to renormalize the final color to the total contribution of each feature point, otherwise texture brightness range will be lost.

Live code in Shadertoy can be reached here: https://www.shadertoy.com/view/4tsGzf
vec4 textureNoTile( sampler2D samp, in vec2 uv )
{
    vec2 p = floor( uv );
    vec2 f = fract( uv );
	
    // derivatives (for correct mipmapping)
    vec2 ddx = dFdx( uv );
    vec2 ddy = dFdy( uv );
    
    // voronoi contribution
    vec4 va = vec4( 0.0 );
    float wt = 0.0;
    for( int j=-1; j<=1; j++ )
    for( int i=-1; i<=1; i++ )
    {
        vec2 g = vec2( float(i), float(j) );
        vec4 o = hash4( p + g );
        vec2 r = g - f + o.xy;
        float d = dot(r,r);
        float w = exp(-5.0*d );
        vec4 c = textureGrad( samp, uv + o.zw, ddx, ddy );
        va += w*c;
        wt += w;
    }
	
    // normalization
    return va/wt;
}

Of course, the drawback is the algorith samples the texture 9 times, which might stress the memory bus too much. But in the other hand, it really help with high quality imagery or situation where just simply can affort it.



Regular texture tiling with GL_REPEAT

Smooth Voronoi based tiling