如何只重复纹理的一部分?
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>
我有图像作为纹理。 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>