1 回答

TA貢獻1998條經驗 獲得超6個贊
代碼將這兩行中的三件事混為一談
// sample "tile map" texture
vec4 data = vec4(texture(uMAP, vTEXCOORD));
// calculate UV
vec2 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?;蛘邠Q句話說,圖塊 2x7 從 U 中的 2.0<->3.0 和 V 中的 7.0<->8.0 開始。然后我們可以從地圖圖塊 2,7 中查找并使用該圖塊覆蓋以下空間中的fract圖塊:瓷磚占據四邊形。
const vs = `#version 300 es
precision 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 es
precision mediump float;
precision mediump usampler2D;
uniform usampler2D uMAP;
uniform sampler2D uATLAS;
uniform vec2 uTILESET_SIZE; // how many tiles across and down the tileset
in 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 locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// gl.createBuffer, bindBuffer, bufferData
const 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 tiles
const 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 tilemap
const 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>
下次發布工作片段對回答者來說會更加友好。
添加回答
舉報