如何只重复纹理的一部分?

How to repeat only the part of texture?

我有图像作为纹理。 example

而且我只想重复这个纹理的一部分。 例如第一行的第三个矩形从 [0.5,0] 到 [0.75,0.25]。 (棕色的)

有没有办法在 Webgl 2 中做到这一点?

ps。也许可以使用 textureOffset 和其他东西来完成...

谢谢!

要重复纹理的一部分,您可以在着色器中设置一些制服来定义您希望重复的纹理部分。

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

然后您可以按如下方式重复纹理

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

当您使用未使用 NEAREST 设置的纹理时会出现一个问题,因为插值会导致边缘处的像素渗入。这会导致纹理重复出现不需要的可见接缝。

最简单的修复方法是将重复图案大小减小一个像素,将图案起始位置减小半个像素。

// 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];

例子

示例创建纹理(右图),然后在左侧 canvas 中呈现该文本的随机部分。重复设置为每个新渲染的随机数量

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>