WebGL - 动画精灵 + 动画坐标
WebGL - animated sprites + animated coordinates
我需要 运行 精灵动画和动画坐标。
也就是说,动画中的某个特定精灵在 [0, 1] 中的纹理坐标,然后由另一个坐标平移。
翻译可能导致坐标在 [0, 1] 之外,这是重复所需要的。
问题是这样的 - 我将精灵作为纹理图集提供。
因此,选择一个精灵就是在[0, 1]中得到一个子矩形。
因为这个精灵在其他精灵之间,所以没有办法重复 - 毕竟,如果纹理坐标移动到精灵矩形之外,其他精灵将被采样。
精灵是必要的在纹理图集中给出 - 我正在使用实例化渲染,其中每个实例都可以使用动画中的任何精灵,据我所知,实现它的唯一方法是使用纹理图集(或 OpenGL 中的纹理数组等)。
tl;dr - 有没有办法在 WebGL 中同时实现纹理重复和精灵动画?
如果您知道精灵在图集中的位置,那么您就不能在片段着色器中计算一个纹理坐标模吗?
vec2 animatedUV; // animation value
vec2 spriteStartUV; // corner uv coord for sprite in atlas
vec2 spriteEndVU; // opposite corner uv coord for sprite in atlas
vec2 spriteRange = (spriteEndUV - spriteStartUV);
vec2 uv = spriteStartUV + fract(texcoord + animatedUV) * spriteRange;
vec4 color = texture2D(someTexture, uv);
我不知道这是否适用于你的特定情况,但也许它会给你一些想法。
工作示例:
const vs = `
void main() {
// using a point sprite because it's easy but the concept
// is the same.
gl_Position = vec4(0, 0, 0, 1);
gl_PointSize = 40.0;
}
`;
const fs = `
precision mediump float;
// I'm passing these in as uniforms but you can pass them in as varyings
// from buffers if that fits your needs better
uniform vec2 animatedUV; // animation value
uniform vec2 spriteStartUV; // corner uv coord for sprite in atlas
uniform vec2 spriteEndUV; // opposite corner uv coord for sprite in atlas
uniform sampler2D someTexture;
void main() {
// this would normally come from a varying but lazy so using point sprite
vec2 texcoord = gl_PointCoord.xy;
vec2 spriteRange = (spriteEndUV - spriteStartUV);
vec2 uv = spriteStartUV + fract(texcoord + animatedUV) * spriteRange;
vec4 color = texture2D(someTexture, uv);
gl_FragColor = color;
}
`;
// use the canvas to make a texture atlas with one sprite
const ctx = document.querySelector("#atlas").getContext("2d");
const w = ctx.canvas.width;
const h = ctx.canvas.height
const sx = 30;
const sy = 40;
const sw = 50;
const sh = 60;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "blue";
ctx.fillRect(sx, sy, sw, sh);
ctx.fillStyle = "yellow";
ctx.font = "45px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("G", sx + sw / 2, sy + sh / 2);
// compute texcoods for sprite
const spriteStartUV = [ sx / w, sy / h ];
const spriteEndUV = [ (sx + sw) / w, (sy + sh) / h ];
const gl = document.querySelector("#webgl").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const tex = twgl.createTexture(gl, {
src: ctx.canvas,
});
function render(time) {
time *= 0.001; // seconds
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {
animatedUV: [time, time * 1.1],
spriteStartUV: spriteStartUV,
spriteEndUV: spriteEndUV,
someTexture: tex,
});
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; margin: 2px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas id="atlas"></canvas>
<canvas id="webgl"></canvas>
如果你想让它重复更多,那么增加你的texcoords或添加一个multplier
const vs = `
void main() {
// using a point sprite because it's easy but the concept
// is the same.
gl_Position = vec4(0, 0, 0, 1);
gl_PointSize = 40.0;
}
`;
const fs = `
precision mediump float;
// I'm passing these in as uniforms but you can pass them in as varyings
// from buffers if that fits your needs better
uniform vec2 animatedUV; // animation value
uniform vec2 spriteStartUV; // corner uv coord for sprite in atlas
uniform vec2 spriteEndUV; // opposite corner uv coord for sprite in atlas
uniform sampler2D someTexture;
void main() {
// this would normally come from a varying but lazy so using point sprite
vec2 texcoord = gl_PointCoord.xy * 3.; // this * 3 could already be
// in your texcoords
vec2 spriteRange = (spriteEndUV - spriteStartUV);
vec2 uv = spriteStartUV + fract(texcoord + animatedUV) * spriteRange;
vec4 color = texture2D(someTexture, uv);
gl_FragColor = color;
}
`;
// create texture atlas with one sprite
const ctx = document.querySelector("#atlas").getContext("2d");
const w = ctx.canvas.width;
const h = ctx.canvas.height
const sx = 30;
const sy = 40;
const sw = 50;
const sh = 60;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "blue";
ctx.fillRect(sx, sy, sw, sh);
ctx.fillStyle = "yellow";
ctx.font = "45px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("G", sx + sw / 2, sy + sh / 2);
// compute texture coords for sprite in atlas
const spriteStartUV = [ sx / w, sy / h ];
const spriteEndUV = [ (sx + sw) / w, (sy + sh) / h ];
const gl = document.querySelector("#webgl").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const tex = twgl.createTexture(gl, {
src: ctx.canvas,
});
function render(time) {
time *= 0.001; // seconds
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {
animatedUV: [time, time * 1.1],
spriteStartUV: spriteStartUV,
spriteEndUV: spriteEndUV,
someTexture: tex,
});
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; margin: 2px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas id="atlas"></canvas>
<canvas id="webgl"></canvas>
请注意,上面的示例使用了制服,但您可以同样轻松地使用每个顶点的 spriteStartUV、spriteEndUV 和任何其他使用属性的数据,并将这些数据添加到您的缓冲区中。
更新
使用更多 sprite 以使其更清晰的示例,它正在使用纹理图集
const vs = `
uniform vec4 u_position;
void main() {
// using a point sprite because it's easy but the concept
// is the same.
gl_Position = u_position;
gl_PointSize = 40.0;
}
`;
const fs = `
precision mediump float;
// I'm passing these in as uniforms but you can pass them in as varyings
// from buffers if that fits your needs better
uniform vec2 animatedUV; // animation value
uniform vec2 spriteStartUV; // corner uv coord for sprite in atlas
uniform vec2 spriteEndUV; // opposite corner uv coord for sprite in atlas
uniform sampler2D someTexture;
void main() {
// this would normally come from a varying but lazy so using point sprite
vec2 texcoord = gl_PointCoord.xy * 3.; // this * 3 could already be
// in your texcoords
vec2 spriteRange = (spriteEndUV - spriteStartUV);
vec2 uv = spriteStartUV + fract(texcoord + animatedUV) * spriteRange;
vec4 color = texture2D(someTexture, uv);
gl_FragColor = color;
}
`;
// create texture atlas with 36 sprites
const ctx = document.querySelector("#atlas").getContext("2d");
const w = ctx.canvas.width;
const h = ctx.canvas.height;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, w, h);
const sw = 16;
const sh = 16;
const spritesAcross = w / sw | 0;
const spriteData = [];
const backgroundColors = [
"#884", "#848", "#488", "#448", "#484", "#488", "#222",
];
"ABCDEFGHIIJKLMNOPQRSTUVWXYZ0123456789".split('').forEach((letter, ndx) => {
const sx = ndx % spritesAcross * sw;
const sy = (ndx / spritesAcross | 0) * sh;
ctx.fillStyle = backgroundColors[ndx % backgroundColors.length];
ctx.fillRect(sx, sy, sw, sh);
ctx.fillStyle = "yellow";
ctx.font = "16px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(letter, sx + sw / 2, sy + sh / 2);
spriteData.push({
spriteStartUV: [ sx / w, sy / h ],
spriteEndUV: [ (sx + sw) / w, (sy + sh) / h ],
});
});
// compute texture coords for sprite in atlas
const gl = document.querySelector("#webgl").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const tex = twgl.createTexture(gl, {
src: ctx.canvas,
});
function render(time) {
time *= 0.001; // seconds
gl.useProgram(programInfo.program);
for (let i = 0; i < 100; ++i) {
const spriteInfo = spriteData[i % spriteData.length];
const t = time + i;
twgl.setUniforms(programInfo, {
u_position: [Math.sin(t * 1.2), Math.sin(t * 1.3), 0, 1],
animatedUV: [t, t * 1.1],
spriteStartUV: spriteInfo.spriteStartUV,
spriteEndUV: spriteInfo.spriteEndUV,
someTexture: tex,
});
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; margin: 2px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas id="atlas"></canvas>
<canvas id="webgl"></canvas>
我需要 运行 精灵动画和动画坐标。
也就是说,动画中的某个特定精灵在 [0, 1] 中的纹理坐标,然后由另一个坐标平移。
翻译可能导致坐标在 [0, 1] 之外,这是重复所需要的。
问题是这样的 - 我将精灵作为纹理图集提供。 因此,选择一个精灵就是在[0, 1]中得到一个子矩形。 因为这个精灵在其他精灵之间,所以没有办法重复 - 毕竟,如果纹理坐标移动到精灵矩形之外,其他精灵将被采样。
精灵是必要的在纹理图集中给出 - 我正在使用实例化渲染,其中每个实例都可以使用动画中的任何精灵,据我所知,实现它的唯一方法是使用纹理图集(或 OpenGL 中的纹理数组等)。
tl;dr - 有没有办法在 WebGL 中同时实现纹理重复和精灵动画?
如果您知道精灵在图集中的位置,那么您就不能在片段着色器中计算一个纹理坐标模吗?
vec2 animatedUV; // animation value
vec2 spriteStartUV; // corner uv coord for sprite in atlas
vec2 spriteEndVU; // opposite corner uv coord for sprite in atlas
vec2 spriteRange = (spriteEndUV - spriteStartUV);
vec2 uv = spriteStartUV + fract(texcoord + animatedUV) * spriteRange;
vec4 color = texture2D(someTexture, uv);
我不知道这是否适用于你的特定情况,但也许它会给你一些想法。
工作示例:
const vs = `
void main() {
// using a point sprite because it's easy but the concept
// is the same.
gl_Position = vec4(0, 0, 0, 1);
gl_PointSize = 40.0;
}
`;
const fs = `
precision mediump float;
// I'm passing these in as uniforms but you can pass them in as varyings
// from buffers if that fits your needs better
uniform vec2 animatedUV; // animation value
uniform vec2 spriteStartUV; // corner uv coord for sprite in atlas
uniform vec2 spriteEndUV; // opposite corner uv coord for sprite in atlas
uniform sampler2D someTexture;
void main() {
// this would normally come from a varying but lazy so using point sprite
vec2 texcoord = gl_PointCoord.xy;
vec2 spriteRange = (spriteEndUV - spriteStartUV);
vec2 uv = spriteStartUV + fract(texcoord + animatedUV) * spriteRange;
vec4 color = texture2D(someTexture, uv);
gl_FragColor = color;
}
`;
// use the canvas to make a texture atlas with one sprite
const ctx = document.querySelector("#atlas").getContext("2d");
const w = ctx.canvas.width;
const h = ctx.canvas.height
const sx = 30;
const sy = 40;
const sw = 50;
const sh = 60;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "blue";
ctx.fillRect(sx, sy, sw, sh);
ctx.fillStyle = "yellow";
ctx.font = "45px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("G", sx + sw / 2, sy + sh / 2);
// compute texcoods for sprite
const spriteStartUV = [ sx / w, sy / h ];
const spriteEndUV = [ (sx + sw) / w, (sy + sh) / h ];
const gl = document.querySelector("#webgl").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const tex = twgl.createTexture(gl, {
src: ctx.canvas,
});
function render(time) {
time *= 0.001; // seconds
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {
animatedUV: [time, time * 1.1],
spriteStartUV: spriteStartUV,
spriteEndUV: spriteEndUV,
someTexture: tex,
});
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; margin: 2px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas id="atlas"></canvas>
<canvas id="webgl"></canvas>
如果你想让它重复更多,那么增加你的texcoords或添加一个multplier
const vs = `
void main() {
// using a point sprite because it's easy but the concept
// is the same.
gl_Position = vec4(0, 0, 0, 1);
gl_PointSize = 40.0;
}
`;
const fs = `
precision mediump float;
// I'm passing these in as uniforms but you can pass them in as varyings
// from buffers if that fits your needs better
uniform vec2 animatedUV; // animation value
uniform vec2 spriteStartUV; // corner uv coord for sprite in atlas
uniform vec2 spriteEndUV; // opposite corner uv coord for sprite in atlas
uniform sampler2D someTexture;
void main() {
// this would normally come from a varying but lazy so using point sprite
vec2 texcoord = gl_PointCoord.xy * 3.; // this * 3 could already be
// in your texcoords
vec2 spriteRange = (spriteEndUV - spriteStartUV);
vec2 uv = spriteStartUV + fract(texcoord + animatedUV) * spriteRange;
vec4 color = texture2D(someTexture, uv);
gl_FragColor = color;
}
`;
// create texture atlas with one sprite
const ctx = document.querySelector("#atlas").getContext("2d");
const w = ctx.canvas.width;
const h = ctx.canvas.height
const sx = 30;
const sy = 40;
const sw = 50;
const sh = 60;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "blue";
ctx.fillRect(sx, sy, sw, sh);
ctx.fillStyle = "yellow";
ctx.font = "45px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("G", sx + sw / 2, sy + sh / 2);
// compute texture coords for sprite in atlas
const spriteStartUV = [ sx / w, sy / h ];
const spriteEndUV = [ (sx + sw) / w, (sy + sh) / h ];
const gl = document.querySelector("#webgl").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const tex = twgl.createTexture(gl, {
src: ctx.canvas,
});
function render(time) {
time *= 0.001; // seconds
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {
animatedUV: [time, time * 1.1],
spriteStartUV: spriteStartUV,
spriteEndUV: spriteEndUV,
someTexture: tex,
});
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; margin: 2px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas id="atlas"></canvas>
<canvas id="webgl"></canvas>
请注意,上面的示例使用了制服,但您可以同样轻松地使用每个顶点的 spriteStartUV、spriteEndUV 和任何其他使用属性的数据,并将这些数据添加到您的缓冲区中。
更新
使用更多 sprite 以使其更清晰的示例,它正在使用纹理图集
const vs = `
uniform vec4 u_position;
void main() {
// using a point sprite because it's easy but the concept
// is the same.
gl_Position = u_position;
gl_PointSize = 40.0;
}
`;
const fs = `
precision mediump float;
// I'm passing these in as uniforms but you can pass them in as varyings
// from buffers if that fits your needs better
uniform vec2 animatedUV; // animation value
uniform vec2 spriteStartUV; // corner uv coord for sprite in atlas
uniform vec2 spriteEndUV; // opposite corner uv coord for sprite in atlas
uniform sampler2D someTexture;
void main() {
// this would normally come from a varying but lazy so using point sprite
vec2 texcoord = gl_PointCoord.xy * 3.; // this * 3 could already be
// in your texcoords
vec2 spriteRange = (spriteEndUV - spriteStartUV);
vec2 uv = spriteStartUV + fract(texcoord + animatedUV) * spriteRange;
vec4 color = texture2D(someTexture, uv);
gl_FragColor = color;
}
`;
// create texture atlas with 36 sprites
const ctx = document.querySelector("#atlas").getContext("2d");
const w = ctx.canvas.width;
const h = ctx.canvas.height;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, w, h);
const sw = 16;
const sh = 16;
const spritesAcross = w / sw | 0;
const spriteData = [];
const backgroundColors = [
"#884", "#848", "#488", "#448", "#484", "#488", "#222",
];
"ABCDEFGHIIJKLMNOPQRSTUVWXYZ0123456789".split('').forEach((letter, ndx) => {
const sx = ndx % spritesAcross * sw;
const sy = (ndx / spritesAcross | 0) * sh;
ctx.fillStyle = backgroundColors[ndx % backgroundColors.length];
ctx.fillRect(sx, sy, sw, sh);
ctx.fillStyle = "yellow";
ctx.font = "16px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(letter, sx + sw / 2, sy + sh / 2);
spriteData.push({
spriteStartUV: [ sx / w, sy / h ],
spriteEndUV: [ (sx + sw) / w, (sy + sh) / h ],
});
});
// compute texture coords for sprite in atlas
const gl = document.querySelector("#webgl").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const tex = twgl.createTexture(gl, {
src: ctx.canvas,
});
function render(time) {
time *= 0.001; // seconds
gl.useProgram(programInfo.program);
for (let i = 0; i < 100; ++i) {
const spriteInfo = spriteData[i % spriteData.length];
const t = time + i;
twgl.setUniforms(programInfo, {
u_position: [Math.sin(t * 1.2), Math.sin(t * 1.3), 0, 1],
animatedUV: [t, t * 1.1],
spriteStartUV: spriteInfo.spriteStartUV,
spriteEndUV: spriteInfo.spriteEndUV,
someTexture: tex,
});
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; margin: 2px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas id="atlas"></canvas>
<canvas id="webgl"></canvas>