米琪卡哇伊
代码将这两行中的三件事混为一谈// sample "tile map" texturevec4 data = vec4(texture(uMAP, vTEXCOORD));// calculate UVvec2 uv = (data.xy * 32.0 / uATLAS_SIZE) + (vTEXCOORD * 32.0 / uATLAS_SIZE);// sample the tileset第一个,对于您正在绘制的整个四边形,遍历整个图块地图。这可能不是你想要的。通常使用图块地图的应用程序希望显示其中的一部分,而不是整个内容。第二个问题是第二行需要知道一个图块将覆盖多少个像素,而不是一个图块有多少个像素。换句话说,如果您有一个 32x32 的图块并以 4x4 像素绘制它,那么您的纹理坐标需要在该图块上以 4 像素(而不是 32 像素)从 0.0 到 1.0。第三个问题是除以 32 不会穿过 32 像素图块,除非纹理上有 32 个图块。假设您有一个 32x32 像素的图块,但图块集中有 8x4 的图块。你需要从 0 到 1 穿过 1/8 和 1/4,而不是穿过 1/32实际上它使用了 2 个矩阵。一个是绘制四边形,四边形可以旋转、缩放、以 3D 方式投影等,但我们可以说,对于图块地图来说,正常情况就是只绘制一个覆盖画布的四边形。第二个是纹理矩阵(或图块矩阵),其中每个单元是 1 个图块。因此,给定一个 0 到 1 的四边形,您可以计算一个矩阵来将该四边形展开并旋转到上面的四边形。假设您不旋转,您仍然需要决定在四边形上和下绘制多少块。如果您希望四边形上有 4 个图块,向下有 3 个图块,那么您可以将比例设置为 x=4 和 y=3。这样,每个图块都会在自己的空间中自动从 0 变为 1。或者换句话说,图块 2x7 从 U 中的 2.0<->3.0 和 V 中的 7.0<->8.0 开始。然后我们可以从地图图块 2,7 中查找并使用该图块覆盖以下空间中的fract图块:瓷砖占据四边形。const vs = `#version 300 esprecision mediump float;uniform mat4 uVIEW;uniform mat4 uPROJECTION;uniform mat4 uMODEL;uniform mat4 uTEXMATRIX;layout(location = 0) in vec4 aPOSITION;layout(location = 1) in vec4 aTEXCOORD;out vec2 vTEXCOORD;void main(){ vTEXCOORD = (uTEXMATRIX * aTEXCOORD).xy; gl_Position = uPROJECTION * uVIEW * uMODEL * aPOSITION;}`;const fs = `#version 300 esprecision mediump float;precision mediump usampler2D;uniform usampler2D uMAP;uniform sampler2D uATLAS;uniform vec2 uTILESET_SIZE; // how many tiles across and down the tilesetin vec2 vTEXCOORD;out vec4 oFRAG;void main(){ // the integer portion of vTEXCOORD is the tilemap coord ivec2 mapCoord = ivec2(vTEXCOORD); uvec4 data = texelFetch(uMAP, mapCoord, 0); // the fractional portion of vTEXCOORD is the UV across the tile vec2 texcoord = fract(vTEXCOORD); vec2 uv = (vec2(data.xy) + texcoord) / uTILESET_SIZE; // sample the tileset oFRAG = texture(uATLAS, uv);}`;const tileWidth = 32;const tileHeight = 32;const tilesAcross = 8;const tilesDown = 4;const m4 = twgl.m4;const gl = document.querySelector('canvas').getContext('webgl2');if (!gl) alert('need WebGL2');// compile shaders, link, look up locationsconst programInfo = twgl.createProgramInfo(gl, [vs, fs]);// gl.createBuffer, bindBuffer, bufferDataconst bufferInfo = twgl.createBufferInfoFromArrays(gl, { aPOSITION: { numComponents: 2, data: [ 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, ], }, aTEXCOORD: { numComponents: 2, data: [ 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, ], },});function r(min, max) { if (max === undefined) { max = min; min = 0; } return min + (max - min) * Math.random();}// make some tilesconst ctx = document.createElement('canvas').getContext('2d');ctx.canvas.width = tileWidth * tilesAcross;ctx.canvas.height = tileHeight * tilesDown;ctx.font = "bold 24px sans-serif";ctx.textAlign = "center";ctx.textBaseline = "middle";const f = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~';for (let y = 0; y < tilesDown; ++y) { for (let x = 0; x < tilesAcross; ++x) { const color = `hsl(${r(360) | 0},${r(50,100)}%,50%)`; ctx.fillStyle = color; const tx = x * tileWidth; const ty = y * tileHeight; ctx.fillRect(tx, ty, tileWidth, tileHeight); ctx.fillStyle = "#FFF"; ctx.fillText(f.substr(y * 8 + x, 1), tx + tileWidth * .5, ty + tileHeight * .5); }}document.body.appendChild(ctx.canvas);const tileTexture = twgl.createTexture(gl, { src: ctx.canvas, minMag: gl.NEAREST,});// make a tilemapconst mapWidth = 400;const mapHeight = 300;const tilemap = new Uint32Array(mapWidth * mapHeight);const tilemapU8 = new Uint8Array(tilemap.buffer);const totalTiles = tilesAcross * tilesDown;for (let i = 0; i < tilemap.length; ++i) { const off = i * 4; // mostly tile 9 const tileId = r(4) < 1 ? (r(totalTiles) | 0) : 9; tilemapU8[off + 0] = tileId % tilesAcross; tilemapU8[off + 1] = tileId / tilesAcross | 0;}const mapTexture = twgl.createTexture(gl, { internalFormat: gl.RGBA8UI, src: tilemapU8, width: mapWidth, minMag: gl.NEAREST,});function ease(t) { return Math.cos(t) * .5 + .5;}function lerp(a, b, t) { return a + (b - a) * t;}function easeLerp(a, b, t) { return lerp(a, b, ease(t));}function render(time) { time *= 0.001; // convert to seconds; gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); gl.clearColor(0, 1, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); gl.useProgram(programInfo.program); twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo); // these mats affects where the quad is drawn const projection = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1); const view = m4.identity(); const model = m4.scaling([gl.canvas.width, gl.canvas.height, 1]); const tilesAcrossQuad = 10;//easeLerp(.5, 2, time * 1.1); const tilesDownQuad = 5;//easeLerp(.5, 2, time * 1.1); // scroll position in tiles // set this to 0,0 and the top left corner of the quad // will be the start of the map. const scrollX = time % mapWidth; const scrollY = 0;//time % (mapHeight * tileHeight); const tmat = m4.identity(); // sets where in the map to look at in tile coordinates // so 3,4 means start drawing 3 tiles over, 4 tiles down m4.translate(tmat, [scrollX, scrollY, 0], tmat); // sets how many tiles to display m4.scale(tmat, [tilesAcrossQuad, tilesDownQuad, 1], tmat); twgl.setUniforms(programInfo, { uPROJECTION: projection, uVIEW: view, uMODEL: model, uTEXMATRIX: tmat, uMAP: mapTexture, uATLAS: tileTexture, uTILESET_SIZE: [tilesAcross, tilesDown], }); gl.drawArrays(gl.TRIANGLES, 0, 6); requestAnimationFrame(render);}requestAnimationFrame(render);canvas { border: 1px solid black; }<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script><canvas></canvas>下次发布工作片段对回答者来说会更加友好。