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>