How to repeat only the part of texture?

431 views Asked by At

I have the Image as texture. example

And I want to repeat only the part of this texture. For example the third rectangle in first row from [0.5,0] to [0.75,0.25]. (the brown one)

Is it any way to do it in Webgl 2 ?

ps. maybe it could be done using textureOffset and something else...

Thank you!

1

There are 1 answers

0
Blindman67 On BEST ANSWER

To repeat part of a texture you can do that in the shader by setting some uniforms that define the section of the texture you wish to repeat.

// uniform that defines the x, y (top left) and width and height of repeat
uniform vec4 repeat; // x, y, w, h  

You can then repeat the texture as follows

gl_FragColor = vec4(texture2D(tex, mod(uv, vec2(1)) * repeat.zw + repeat.xy));

There is one issue when you use a texture that is not set with NEAREST as the interpolation will case pixels at the edge to bleed in. This will cause unwanted visible seams where the texture repeats.

The easiest way to fix is the reduce the repeating pattern size by a pixel and the pattern start position in by half a pixel.

// example for 256 texture size

const pixel = 1 / 256;
const repeat = [0.5 + pixel / 2, 0.0 + pixel / 2 ,0.25 - pixel, 0.25 - pixel];

Example

Example creates a texture (image on right) and then renders a random part of that text in the canvas on the left. The repeat set to a random amount each new render

const shaders = {
vs: `
attribute vec2 vert;
varying vec2 uv;
void main() {
    uv = vert;
    gl_Position = vec4(vert, 0.0, 1.0);
}`, 
fs: `precision mediump float;
uniform sampler2D tex;
varying vec2 uv;  
uniform vec4 repeat;
uniform vec2 tiles;
void main(){
    gl_FragColor = vec4(texture2D(tex, mod(uv * tiles, vec2(1)) * repeat.zw + repeat.xy));
}`
};
const colors = "#ff0000,#ff8800,#ffff00,#88ff00,#00ff00,#00ff88,#00f0f0,#0088ff,#0000ff,#8800ff,#ff00ff,#ff0088".split(",");
const randCol = (cols = colors) => cols[Math.random() * cols.length | 0];
const F32A = a => new Float32Array(a), UI16A = a => new Uint16Array(a);
const GLBuffer = (data, type = gl.ARRAY_BUFFER, use = gl.STATIC_DRAW, buf) => (gl.bindBuffer(type, buf = gl.createBuffer()), gl.bufferData(type, data, use), buf);
const GLLocs = (shr, type, ...names) => names.reduce((o,name) => (o[name] = (gl[`get${type}Location`])(shr, name), o), {});
const GLShader = (prg, source, type = gl.FRAGMENT_SHADER, shr) => {
    gl.shaderSource(shr = gl.createShader(type), source);
    gl.compileShader(shr);
    gl.attachShader(prg, shr);
}
function texture(gl, image, {min = "LINEAR", mag = "LINEAR"} = {}) {
    const texture = gl.createTexture();
    target = gl.TEXTURE_2D;
    gl.bindTexture(target, texture);
    gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl[min]);
    gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, gl[mag]);
    gl.texImage2D(target, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    return texture;
}
const bindTexture = (texture, unit = 0) => { gl.activeTexture(gl.TEXTURE0 + unit); gl.bindTexture(gl.TEXTURE_2D, texture) }
const createTag = (tag, props = {}) => Object.assign(document.createElement(tag), props);
const appendEl = (par, ...sibs) => sibs.reduce((p,sib) => (p.appendChild(sib), p),par);

function createTexture(width = 256, height = 256) {
    const tex = createTag("canvas", {width, height, className: "texture"});
    appendEl(document.body, tex);
    const ctx = tex.getContext("2d");
    var x = 4, y = 4, count = 0;
    const xStep = width / x, yStep = height / y;
    ctx.font = (yStep * 0.95 | 0) + "px arial";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    while (y--) {
        x = 4;
        while (x--) {
          ctx.fillStyle = randCol();
          ctx.fillRect(x * xStep, y * yStep, xStep, yStep);
          ctx.fillStyle = "#000";
          ctx.fillText((count++).toString(16).toUpperCase(), (x + 0.5) * xStep, (y + 0.5) * yStep);
       }
    }
    ctx.setTransform(1,0,0,-1,0,height);
    ctx.globalCompositeOperation = "copy";
    ctx.drawImage(tex,0,0);
    bindTexture(texture(gl, tex));       
    ctx.drawImage(tex,0,0);
}

var W;    
const gl = canvas.getContext("webgl");
requestAnimationFrame(renderRandom);
addEventListener("resize", renderRandom);
const prog = gl.createProgram();
GLShader(prog, shaders.vs, gl.VERTEX_SHADER);
GLShader(prog, shaders.fs);
gl.linkProgram(prog);
gl.useProgram(prog);
const locs = GLLocs(prog, "Uniform", "repeat", "tiles");
const attIdxs = GLLocs(prog, "Attrib", "vert");
GLBuffer(F32A([-1,-1, 1,-1, 1,1, -1,1]));
GLBuffer(UI16A([1,2,3, 0,1,3]), gl.ELEMENT_ARRAY_BUFFER);
gl.enableVertexAttribArray(attIdxs.vert);
gl.vertexAttribPointer(attIdxs.vert, 2, gl.FLOAT, false, 0, 0); 
createTexture();

function renderRandom() {
    gl.viewport(0, 0, W = canvas.width = Math.min(innerWidth,innerHeight), canvas.height = W);
    const textPxSize = 1/256;
    const x = (Math.random() * 4 | 0) / 4 + textPxSize / 2;
    const y = (Math.random() * 4 | 0) / 4 + textPxSize / 2;
    const tiles = Math.random() * 8 + 1 | 0;
    gl.uniform4fv(locs.repeat, F32A([x,y,0.25 - textPxSize, 0.25 - textPxSize ]));
    gl.uniform2fv(locs.tiles, F32A([tiles, tiles]));
    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
    setTimeout(renderRandom, 4000);
}
    canvas {
        border: 2px solid black;
    }
    .texture { width: 128px; height: 128px;}
<canvas id="canvas"></canvas>