为了账号安全,请及时绑定邮箱和手机立即绑定

webgltilemap渲染不正确的UV计算

webgltilemap渲染不正确的UV计算

千万里不及你 2023-09-07 16:01:32
我正在尝试将图块地图渲染到单个四边形上。我的方法使用“图块地图”纹理,其中每个像素存储图块集中的图块的 X 和 Y 索引。渲染片段时,想法是:使用顶点纹理坐标对“平铺贴图”纹理进行采样从纹理的 R 和 G 通道检索 X 和 Y 索引计算所选图块的 UV使用 UV 对纹理图集进行采样我在让 #3 工作时遇到问题。这是我尝试用来渲染它的着色器代码:顶点#version 300 esprecision mediump float;uniform mat4 uVIEW;uniform mat4 uPROJECTION;uniform mat3 uMODEL;layout(location = 0) in vec2 aPOSITION;layout(location = 1) in vec2 aTEXCOORD;out vec2 vTEXCOORD;void main(){    // flip uv and pass it to fragment shader    vTEXCOORD = vec2(aTEXCOORD.x, 1.0f - aTEXCOORD.y);    // transform vertex position    vec3 transformed = uMODEL * vec3(aPOSITION, 1.0);    gl_Position = uPROJECTION * uVIEW * vec4(transformed.xy, 0.0, 1.0);}分段#version 300 esprecision mediump float;precision mediump usampler2D;uniform usampler2D uMAP;uniform sampler2D uATLAS;uniform vec2 uATLAS_SIZE;in vec2 vTEXCOORD;out vec4 oFRAG;void main(){    // 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    oFRAG = texture(uATLAS, uv);}我相信这是罪魁祸首:vec2 uv = (data.xy * 32.0 / uATLAS_SIZE) + (vTEXCOORD * 32.0 / uATLAS_SIZE);这里的公式是uv = (tile_xy_indices * tile_size) + (texcoord * tile_size),其中:texcoord是顶点uv(标准[0, 1], [0, 0], [1, 0], [1, 1])tile_xy_indices是图块集中图块的 X、Y 坐标tile_size是图块集中一个图块的标准化尺寸因此,如果我有值texcoord = (0, 0), tile_xy_indices = (7, 7), tile_size = (32 / 1024, 32 / 1024),那么该片段的 UV 应该是(0.21875, 0.21875),如果texcoord = (1, 1),那么它应该是(0.25, 0.25)。这些值对我来说似乎是正确的,为什么它们会产生错误的结果,以及如何修复它?
查看完整描述

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。或者换句话说,图块 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>

下次发布工作片段对回答者来说会更加友好。


查看完整回答
反对 回复 2023-09-07
  • 1 回答
  • 0 关注
  • 45 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信