Cha*_*had 3 javascript shader webgl three.js
使用新代码更新了问题
我正在尝试编写一个 WebGL 着色器来绘制TMX 图层(从 Tiled 编辑器导出)。我正在使用 THREE.js 创建一个Plane网格,并让材质成为ShaderMaterial将在其上绘制地图的材质。
对于那些不知道由Tiled 编辑器导出为 json 的瓦片地图的人,将为data每个图层提供一个属性;它包含一个数值数组,每个数值都是图块集中的图块索引,如下所示:
"data": [5438, 5436, 5437, 5438, 5436, 5437, 5438, 5436, 5437, 5438, 845, ...]
Run Code Online (Sandbox Code Playgroud)
由于我的图块地图为 256 x 256 个图块,因此该数组的长度为 65536 个元素。每个元素的值引用tilemap中的一个tile,其中索引的定义如下:

(来源:melonjs.org)
因此图块地图的索引 0 指的是图块 5438,它们的计数方式如上所示。索引表示图块图中的哪个图块具有图块集中的该图块,它们以相同的方式进行计数。
以下是我创建材质、平面和网格的方法:
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);
Run Code Online (Sandbox Code Playgroud)
最后是制服和着色器。基本上我需要将数据元素映射到图块集中的实际图块并绘制它。为了将data数组放入着色器中,我将其加载为 aTHREE.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');
Run Code Online (Sandbox Code Playgroud)
还有制服和数据纹理:
//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 }
};
Run Code Online (Sandbox Code Playgroud)
因此,当渲染时,我只会一遍又一遍地重复瓷砖集的第一个图块:

不知道是什么原因造成的,但由于它处于位置,0, 0我认为我在某个地方对我的干扰为零。
终于有了一个可行的解决方案,我最大的问题是将浮点数数组打包到纹理中,然后进行解码。我忘记了,当从数据纹理加载字节时,它们是“标准化的”。这意味着每个值都除以 255 以将其限制为 [0, 1]。移位后,我需要乘以 255.0 来进行非规格化。
我还在某处读到 webgl 浮点数是 24 位而不是 32 位,因此我更改了编码和解码算法以仅使用 RGB 通道。
现在它成功地将整数(或浮点)数组打包到 a 中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;
Run Code Online (Sandbox Code Playgroud)
着色器:
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');
Run Code Online (Sandbox Code Playgroud)
制服:
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 }
};
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2225 次 |
| 最近记录: |