如何在WebGL上使用图层?

时间:2017-02-22 07:55:45

标签: glsl webgl

我在做一个带有光照贴图的瓷砖地图,我想知道如何将瓷砖贴图(在两个三角形中光栅化)声明为第1层,而上面的其他瓷砖贴图在某些部分使用透明度来查看第一个层

1 个答案:

答案 0 :(得分:0)

WebGL是一种光栅化API。它只是画画。它没有“层”的概念。

您可以按帧实现图层,绘制第一个图块,然后在第一个图块之上绘制第二个图块。这与canvas 2D API没有什么不同。

至于如何使用2个三角形(或偶数)see this article

渲染瓷砖地图

this project中也有使用相同的技术,但它也支持翻转和旋转的图块(90度),还有从Tiled加载地图的代码。对不起,虽然没有文档。有关用于绘制图层的着色器和代码,请参阅tilemap.js,有关从Tiled加载地图和图块的代码,请参阅tiledloader.js

让我们从基础开始。首先,如果我们只绘制2个矩形,则第二个(蓝色)是第一个(红色)

上的“层”

const ctx = document.querySelector("canvas").getContext("2d");
function render(time) {
  time *= 0.001;  // seconds
  
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  
  var t1 = time * -1.1;
  ctx.fillStyle = "red";
  ctx.fillRect(50 + Math.sin(t1) * 20, 50 + Math.cos(t1) * 20, 128, 64);
  
  var t2 = time * 1.3;
  ctx.fillStyle = "blue";
  ctx.fillRect(75 + Math.sin(t2) * 20, 30 + Math.cos(t2) * 20, 64, 128);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; }
<canvas /> 

这在WebGL中没有什么不同。

如果我们将静态tilemap放在每个图像中,除了矩形的内容之外什么都没有变化。

这是第一张图片

layer2

这是第二个

layer2

const ctx = document.querySelector("canvas").getContext("2d");
const layer1 = new Image();
layer1.src = "http://i.imgur.com/KTXDmsa.png";
const layer2 = new Image();
layer2.src = "http://i.imgur.com/3qVLkO5.png";


function render(time) {
  time *= 0.001;  // seconds
  
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  
  var t1 = time * -1.1;
  ctx.drawImage(layer1, 50 + Math.sin(t1) * 20, 50 + Math.cos(t1) * 20);
  
  var t2 = time * 1.3;
  ctx.drawImage(layer2, 75 + Math.sin(t2) * 20, 30 + Math.cos(t2) * 20);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; }
<canvas />

同样,WebGL也没有什么不同。

现在您需要从tilemap生成这些图像,而不是静态加载它们,这是代码链接所做的和下面的代码。

基于this tileset

tileset

const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");

// compile & link shaders and lookup locations
const progInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);

// make a unit quad
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl, 1, .5, .5);

// load tiles into texture
const tilesAcross = 16;
const tilesDown = 16;
const tileWidth = 32;
const tileHeight = 32;
const tiles = twgl.createTexture(gl, {
  src: "http://i.imgur.com/sz79FPd.png",
  crossOrigin: "",
  minMag: gl.NEAREST,
});

// layer 0
const tilemap0 = createTilemap({
   width: 8,
   height: 5,
   map: new Uint32Array([
     t(1, 2), t(1, 2), t(1, 2), t(1, 2), t(1, 2), t(1, 2), t(1, 2), t(1, 2), 
     t(1, 2), t(9, 6), t(9, 6), t(9, 6), t(9, 6), t(9, 6), t(9, 6), t(1, 2),    
     t(1, 2), t(9, 6), t(9, 6), t(9, 6), t(9, 6), t(9, 6), t(9, 6), t(1, 2),    
     t(1, 2), t(9, 6), t(9, 6), t(9, 6), t(9, 6), t(9, 6), t(9, 6), t(1, 2),    
     t(1, 2), t(1, 2), t(1, 2), t(1, 2), t(1, 2), t(1, 2), t(1, 2), t(1, 2), 
   ]),
});

// layer 1
const tilemap1 = createTilemap({
   width: 8,
   height: 5,
   map: new Uint32Array([
     t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(0, 0), 
     t(0, 0), t(4, 5), t(5, 5), t(6, 5), t(0, 0), t(0, 0), t(0, 0), t(0, 0), 
     t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(4, 5), t(5, 5), t(6, 5),  
     t(4, 5), t(5, 5), t(6, 5), t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(0, 0), 
     t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(0, 0), t(0, 0), 
   ]),
});

function t(x, y, xflip, yflip, xyswap) {
  return x | (y << 8) | 
        (((xflip ? 0x80 : 0) | (yflip ? 0x40 : 0) | (xyswap ? 0x20 : 0)) << 24);
}

// copy the tilemap into a texture
function createTilemap(tilemap) {
  tilemap.texture = twgl.createTexture(gl, {
    src: new Uint8Array(tilemap.map.buffer),
    width: tilemap.width,
    minMag: gl.NEAREST,
  });
  return tilemap;
};


function drawTilemap(options) {
  const tilemap = options.tilemap;
  
  const scaleX = options.scaleX || 1;
  const scaleY = options.scaleY || 1;

  const dispScaleX = options.width / gl.canvas.width;
  const dispScaleY = options.height / gl.canvas.height;

  let texMat = m4.translation([options.scrollX, options.scrollY, 0]);
  texMat = m4.rotateZ(texMat, options.rotation);
  texMat = m4.scale(texMat, [ 
    gl.canvas.width  / tileWidth  / scaleX * (dispScaleX),
    gl.canvas.height / tileHeight / scaleY * (dispScaleY),
    1,
  ]);
  texMat = m4.translate(texMat, [ 
    -options.originX / gl.canvas.width,
    -options.originY / gl.canvas.height,
    0,
  ]);

  const matrix = [
    2 * dispScaleX,0,0,0,
    0,-2 * dispScaleY,0,0,
    0,0,1,0,
   -1 + 2 * (options.x | 0) / gl.canvas.width, 1 - 2 * (options.y | 0) / gl.canvas.height,0,1,
  ];

  gl.useProgram(progInfo.program);
  
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, progInfo, quadBufferInfo);
  
  // calls gl.uniformXXX and gl.activeTexture, gl.bindTexture
  twgl.setUniforms(progInfo, {
    u_matrix: matrix,
    u_texMatrix: texMat,
    u_tilemap: tilemap.texture,
    u_tiles: tiles,
    u_tilemapSize: [tilemap.width, tilemap.height],
    u_tilesetSize: [tilesAcross, tilesDown],
  });
  
  // calls gl.drawElements
  twgl.drawBufferInfo(gl, quadBufferInfo);
}

function render(time) {
  time *= 0.001;
  
  // draw layer 0
  drawTilemap({
     tilemap: tilemap0,
     tiles: tiles,
     // position and width, height on canvas
     x: Math.cos(time * .9) * 20,
     y: Math.sin(time * .9) * 20,
     width: 256,
     height: 160,
     // offset into tilemap (repeats at edges)
     scrollX: 0,
     scrollY: 0,
     // rotation/scale point
     originX: 0,
     originY: 0,
     // rotation in radians
     rotation: 0,
     // scale
     scaleX: 1,
     scaleY: 1,
  });

  // draw layer 1
  drawTilemap({
     tilemap: tilemap1,
     tiles: tiles,
     x: Math.sin(time) * 20,
     y: Math.cos(time) * 20,
     width: 256,
     height: 160,
     scrollX: 0,
     scrollY: 0,
     originX: 0,
     originY: 0,
     rotation: 0,
  });


  requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; }
<canvas />
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<script id="vs" type="foo">
attribute vec4 position;
attribute vec4 texcoord;

uniform mat4 u_matrix;
uniform mat4 u_texMatrix;

varying vec2 v_texcoord;

void main() {
  gl_Position = u_matrix * position;
  v_texcoord = (u_texMatrix * texcoord).xy;
}
</script>
<script id="fs" type="foo">
precision mediump float;

uniform sampler2D u_tilemap;
uniform sampler2D u_tiles;
uniform vec2 u_tilemapSize;   // tiles across/down map
uniform vec2 u_tilesetSize;   // pixels across a single tile

varying vec2 v_texcoord;

void main() {
  // v_texcoord is in tile units which is based on u_texMatrix from the
  // vertex shader
  
  // this is the tile to start at
  vec2 tilemapCoord = floor(v_texcoord);
  
  // this is a fractional amount into a tile
  vec2 texcoord = fract(v_texcoord);
  
  // computes the UV coord pull the correct value out of tilemap
  vec2 tileFoo = fract((tilemapCoord + vec2(0.5, 0.5)) / u_tilemapSize);
  
  // get a single tile out of the tilemap and convert from 0 -> 1 to 0 -> 255
  vec4 tile = floor(texture2D(u_tilemap, tileFoo) * 256.0);

  // flags for the tile are in w (xflip, yflip, xyswap)
  float flags = tile.w;
  float xflip = step(128.0, flags);
  flags = flags - xflip * 128.0;
  float yflip = step(64.0, flags);
  flags = flags - yflip * 64.0;
  float xySwap = step(32.0, flags);
  
  // based on the flags swap the texcoord inside the tile
  if (xflip > 0.0) {
    texcoord = vec2(1.0 - texcoord.x, texcoord.y);
  }
  if (yflip > 0.0) {
    texcoord = vec2(texcoord.x, 1.0 - texcoord.y);
  }
  if (xySwap > 0.0) {
    texcoord = texcoord.yx;
  }

  // scale the tex coords for a single tile
  vec2 tileCoord = (tile.xy + texcoord) / u_tilesetSize;
  
  // get the color from the tile
  vec4 color = texture2D(u_tiles, tileCoord);
  
  // if alpha is below some threshold don't draw at all
  if (color.a <= 0.1) {
    discard;
  }
  gl_FragColor = color;
}
</script>