WebGL - 动画精灵+动画坐标

时间:2017-11-30 03:23:02

标签: animation glsl webgl

我需要运行精灵动画和动画坐标。

也就是说,动画中某些特定精灵给出了[0,1]中的纹理坐标,然后它被另一个坐标翻译。

平移可能导致坐标超出[0,1],这是重复所需的。

问题是这个 - 我将sprite作为纹理图集提供。 因此,选择精灵意味着在[0,1]中获得一个子矩形。 因为这个精灵位于其他精灵之间,所以无法重复 - 毕竟,如果纹理坐标移动到精灵矩形之外,其他精灵将被采样。

精灵图谱中给出精灵作为必需品 - 我使用的是实例渲染,其中每个实例都可以在动画中使用任何精灵,据我所知,实现它的唯一方法是使用纹理atlas(或OpenGL中的纹理数组等)。

tl; dr - 有没有办法在WebGL中实现纹理重复和精灵动画?

1 个答案:

答案 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;
&#13;
&#13;

如果你想重复更多,那么增加你的texcoords或添加一个multplier

&#13;
&#13;
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;
&#13;
&#13;

请注意,上面的示例使用了制服,但您可以轻松地使用每个顶点spriteStartUV,spriteEndUV以及使用属性的任何其他数据并将该数据添加到缓冲区。

更新

使用更多精灵的示例,使其更清晰,使用纹理图集

&#13;
&#13;
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;
&#13;
&#13;