我需要运行精灵动画和动画坐标。
也就是说,动画中某些特定精灵给出了[0,1]中的纹理坐标,然后它被另一个坐标翻译。
平移可能导致坐标超出[0,1],这是重复所需的。
问题是这个 - 我将sprite作为纹理图集提供。 因此,选择精灵意味着在[0,1]中获得一个子矩形。 因为这个精灵位于其他精灵之间,所以无法重复 - 毕竟,如果纹理坐标移动到精灵矩形之外,其他精灵将被采样。
精灵图谱中给出精灵作为必需品 - 我使用的是实例渲染,其中每个实例都可以在动画中使用任何精灵,据我所知,实现它的唯一方法是使用纹理atlas(或OpenGL中的纹理数组等)。
tl; dr - 有没有办法在WebGL中实现纹理重复和精灵动画?
答案 0 :(得分:3)
如果您知道精灵图谱中的精灵位置,那么您是否只能在片段着色器中计算范围内的纹理坐标?
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>
&#13;
如果你想重复更多,那么增加你的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);
&#13;
canvas { border: 1px solid black; margin: 2px; }
&#13;
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas id="atlas"></canvas>
<canvas id="webgl"></canvas>
&#13;
请注意,上面的示例使用了制服,但您可以轻松地使用每个顶点spriteStartUV,spriteEndUV以及使用属性的任何其他数据并将该数据添加到缓冲区。
使用更多精灵的示例,使其更清晰,使用纹理图集
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);
&#13;
canvas { border: 1px solid black; margin: 2px; }
&#13;
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas id="atlas"></canvas>
<canvas id="webgl"></canvas>
&#13;