在Threejs平面上渲染TMX贴图

时间:2012-11-22 16:42:59

标签: javascript shader webgl three.js

  

使用新代码更新了问题

我正在尝试编写一个WebGL着色器,它将绘制一个TMX Layer(从Tiled编辑器中导出)。我正在使用THREE.js创建一个Plane网格,并使材质为ShaderMaterial,并在其上绘制地图。

对于那些不知道由Tiled editor导出的tilemap的人,因为json将为每个图层提供data属性;它包含一个数值数组,每个数值都是tileset中的tile索引,如:

"data": [5438, 5436, 5437, 5438, 5436, 5437, 5438, 5436, 5437, 5438, 845, ...]

由于我的tilemap是256乘256个tile,因此该数组的长度为65536个元素。每个元素的值引用tilemap中的tile,其中索引定义如下:

tile set http://www.melonjs.org/docs/symbols/spritesheet_grid.png

因此,tilemap的索引0指的是区块5438,其中它们如上所述被计数。索引表示tilemap中的哪个tile具有tileset中的tile,它们以相同的方式计数。

以下是我创建材质,平面和网格的方法:

this.size = new THREE.Vector2(256, 256);
this.tileSize = new THREE.Vector2(16, 16);

this._material = new THREE.ShaderMaterial({
    uniforms: this._uniforms,
    vertexShader: this.vShader,
    fragmentShader: this.fShader,
    transparent: (this.opacity === 0)
});

this._plane = new THREE.PlaneGeometry(
    this.size.x * this.tileSize.x,
    this.size.y * this.tileSize.y
);

this._mesh = new THREE.Mesh(this._plane, this._material);

最后是制服和着色器。基本上我需要将数据元素映射到tileset中的实际tile并绘制它。为了将data数组放入着色器,我将其作为THREE.DataTexture加载并将其视为纹理。

这是我的第二次尝试:

//Shaders
var vShader = [
    'varying vec2 pixelCoord;',
    'varying vec2 texCoord;',

    'uniform vec2 layerSize;',
    'uniform vec2 tilesetSize;',
    'uniform vec2 inverseTilesetSize;',

    'uniform vec2 tileSize;',
    'uniform vec2 inverseTileSize;',
    'uniform float scale;',

    'void main(void) {',
    '   pixelCoord = (uv * layerSize) * tileSize * scale;', //pixel we are at
    '   texCoord = pixelCoord * inverseTilesetSize * inverseTileSize;', //calculate the coord on this map
    '   gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
    '}'
].join('\n');

var fShader = [
    //"precision highp float;",

    'varying vec2 pixelCoord;',         
    'varying vec2 texCoord;',

    'uniform vec2 tilesetSize;',
    'uniform vec2 inverseTilesetSize;',

    'uniform vec2 tileSize;',
    'uniform vec2 inverseTileSize;',
    'uniform vec2 numTiles;',
    'uniform float scale;',

    'uniform sampler2D tileset;',
    'uniform sampler2D tileIds;',

    //I store the tile IDs as a texture (1 float value = rgba)
    //this will decode the rgba values back into a float ID
    'highp float decode32(highp vec4 rgba) {',
    '   const vec4 bit_shift = vec4(1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1.0);',
    '   float depth = dot(rgba, bit_shift);',
    '   return depth;',
    '}',

    'void main(void) {',
    '   vec4 tileId = texture2D(tileIds, texCoord);', //grab this tileId from the layer data
    '   tileId.rgba = tileId.abgr;', //flip flop due to endianess
    //I find that this value is always `0 < tileValue < 1`, I think my `decode32` sucks...
    '   float tileValue = decode32(tileId);', //decode the vec4 into the float ID
    '   vec2 tileLoc = vec2(mod(tileValue, numTiles.y), floor(tileValue / numTiles.y));', //convert the ID into x, y coords
    '   vec2 coord = floor(tileLoc * 256.0) * tileSize;', //coord in the tileset
    '   vec2 offset = mod(pixelCoord, tileSize);', //how much to draw
    '   gl_FragColor = texture2D(tileset, (coord + offset) * inverseTilesetSize);', //grab tile from tilset
    '}'
].join('\n');

制服和数据纹理:

//tried making this 256 x 256 like it is conceptually,
//and also tried 65536 x 1 like the data structure
this.dataTex = new THREE.DataTexture(
                this.data,
                this.data.length, //width (65536)
                1, //height (1)
                THREE.RGBAFormat, //format
                THREE.UnsignedByteType, //type
                THREE.UVMapping, //mapping
                THREE.ClampToEdgeWrapping, //wrapS
                THREE.ClampToEdgeWrapping, //wrapT
                THREE.NearestFilter, //magFilter
                THREE.NearestMipMapNearestFilter //minFilter
            );
this.dataTex.needsUpdate = true;


this._uniforms = window._uniforms = {
    layerSize:          { type: 'v2', value: this.size },
    tilesetSize:        { type: 'v2', value: new THREE.Vector2(this.tileset.image.width, this.tileset.image.height) },
    inverseTilesetSize: { type: 'v2', value: new THREE.Vector2(1 / this.tileset.image.width, 1 / this.tileset.image.height) },

    tileSize:           { type: 'v2', value: this.tileSize },
    inverseTileSize:    { type: 'v2', value: new THREE.Vector2(1 / this.tileSize.x, 1 / this.tileSize.y) },
    numTiles:           { type: 'v2', value: new THREE.Vector2(this.tileset.image.width / this.tileSize.x, this.tileset.image.height / this.tileSize.y) },
    scale:              { type: 'f', value: 1 / this.scale },

    tileset:            { type: 't', value: this.tileset },
    tileIds:            { type: 't', value: this.dataTex },
    repeatTiles:        { type: 'i', value: this.repeat ? 1 : 0 }
};

因此,当渲染时,我只是反复重复使用tileset的第一个tile:

repeated tiles

不确定导致它的原因,但由于它位于0, 0位置,我认为我在某个地方没有弄乱我。

1 个答案:

答案 0 :(得分:4)

最后有一个可行的解决方案,我最大的问题是将我的浮动数组包装到纹理中然后解码。我忘记了当从数据纹理加载字节时它们被“标准化”。这意味着每个值除以255以将其钳制为[0,1]。移位后,我需要乘以255.0进行非规范化。

我还读过webgl浮点数是24位而不是32位的地方,因此我将编码和解码算法改为仅使用RGB通道。

现在它成功地将一个int(或浮点数)数组打包到Uint8Array中,将其作为纹理传递给webgl并正确使用它:

数组打包

var array = [5438, 5436, 5437, 5438, 5436, 5437, ...],
    arrayBuff = new ArrayBuffer(array.length * 3),
    array8 = new Uint8Array(arrayBuff);

for(var i = 0, y = 0, il = array.length; i < il; ++i, y += 3) {
    var value = array[i];

    array8[y + 0] = (value & 0x000000ff);
    array8[y + 1] = (value & 0x0000ff00) >> 8;
    array8[y + 2] = (value & 0x00ff0000) >> 16;
}


dataTex = new THREE.DataTexture(
                            array8,
                            this.size.x, //width
                            this.size.y, //height
                            THREE.RGBFormat, //format
                            THREE.UnsignedByteType, //type
                            THREE.UVMapping, //mapping
                            THREE.ClampToEdgeWrapping, //wrapS
                            THREE.ClampToEdgeWrapping, //wrapT
                            THREE.NearestFilter, //magFilter
                            THREE.NearestMipMapNearestFilter //minFilter
                        );
dataTex.needsUpdate = true;

<强>着色

var vShader = [
    'varying vec2 pixelCoord;',
    'varying vec2 texCoord;',

    'uniform vec2 mapSize;',
    'uniform vec2 inverseLayerSize;',

    'uniform vec2 inverseTileSize;',

    'void main(void) {',
    '   pixelCoord = (uv * mapSize);',
    '   texCoord = pixelCoord * inverseLayerSize * inverseTileSize;',
    '   gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);', //hand this position to WebGL
    '}'
].join('\n');

var fShader = [
    'varying vec2 pixelCoord;',         
    'varying vec2 texCoord;',

    'uniform vec2 inverseTilesetSize;',

    'uniform vec2 tileSize;',
    'uniform vec2 numTiles;',

    'uniform sampler2D tileset;',
    'uniform sampler2D tileIds;',

    'float decode24(const in vec3 rgb) {',
    '   const vec3 bit_shift = vec3((256.0*256.0), 256.0, 1.0);',
    '   float fl = dot(rgb, bit_shift);', //shift the values appropriately
    '   return fl * 255.0;', //denormalize the value
    '}',

    'void main(void) {',
    '   vec3 tileId = texture2D(tileIds, texCoord).rgb;', //grab this tileId from the layer data
    '   tileId.rgb = tileId.bgr;', //flip flop due to endianess
    '   float tileValue = decode24(tileId);', //decode the normalized vec3 into the float ID
    '   vec2 tileLoc = vec2(mod(tileValue, numTiles.x) - 1.0, floor(tileValue / numTiles.x));', //convert the ID into x, y coords;
    '   tileLoc.y = numTiles.y - 1.0 - tileLoc.y;', //convert the coord from bottomleft to topleft

    '   vec2 offset = floor(tileLoc) * tileSize;', //offset in the tileset
    '   vec2 coord = mod(pixelCoord, tileSize);', //coord of the tile.

    '   gl_FragColor = texture2D(tileset, (offset + coord) * inverseTilesetSize);', //grab tile from tileset
    '}'
].join('\n');

<强>制服

this._uniforms = window._uniforms = {
    mapSize:            { type: 'v2', value: new THREE.Vector2(this.size.x * this.tileSize.x, this.size.y * this.tileSize.y) },
    inverseLayerSize:   { type: 'v2', value: new THREE.Vector2(1 / this.size.x, 1 / this.size.y) },
    inverseTilesetSize: { type: 'v2', value: new THREE.Vector2(1 / this.tileset.image.width, 1 / this.tileset.image.height) },

    tileSize:           { type: 'v2', value: this.tileSize },
    inverseTileSize:    { type: 'v2', value: new THREE.Vector2(1 / this.tileSize.x, 1 / this.tileSize.y) },
    numTiles:           { type: 'v2', value: new THREE.Vector2(this.tileset.image.width / this.tileSize.x, this.tileset.image.height / this.tileSize.y) },

    tileset:            { type: 't', value: this.tileset },
    tileIds:            { type: 't', value: this.dataTex }
};