How can I use layers on WebGL?

人走茶凉 提交于 2019-12-14 04:09:21

问题


I´m doing a tile map with light mapping, and I want to know how can I declare a tile map (rasterized in two triangules) as layer 1, and other tile map above with transparency in some parts to see the first layer?


回答1:


WebGL is a rasterization API. It just draws. It has no concept of "layers".

You can achieve layers by every frame, drawing your first tilemap, then drawing your second tilemap on top of the first one. This is no different than the canvas 2D API.

As for how to render a tilemap with just 2 triangles (or one even) see this article

There is also in this project that uses the same technique but it also supports flipped and rotated tiles (by 90 degrees) and there's code to load maps from Tiled. Sorry there's no docs though. See tilemap.js for the shader and code that draws a layer and tiledloader.js for code that loads maps and tiles from Tiled.

Let's start from basics. First if we just draw 2 rectangles the 2nd one (blue) is a "layer" over the first (red)

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 /> 

This would be no different in WebGL.

If we put static tilemap like images in each nothing changes except the content of the rectangles.

Here's the first image

And here's the second

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 />

Again, no different in WebGL.

Now you need to generate those image from a tilemap instead of statically loading them which is what the code linked does and the code below.

Based on this 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>


来源:https://stackoverflow.com/questions/42385663/how-can-i-use-layers-on-webgl

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!