【问题标题】:Threejs Custom Shader - Screen TearingThreejs 自定义着色器 - 屏幕撕裂
【发布时间】:2012-10-27 14:19:13
【问题描述】:

所以要使用 Threejs 和 Brandon Jone 的 tilemap 方法 (found here) 来实现 tilemap,我为每一层使用 THREE.Plane 几何,并使用以下自定义着色器绘制面部:

顶点着色器:

var tilemapVS = [
    "varying vec2 pixelCoord;",
    "varying vec2 texCoord;",

    "uniform vec2 mapSize;",
    "uniform vec2 inverseTileTextureSize;",
    "uniform float inverseTileSize;",

    "void main(void) {",
    "    pixelCoord = (uv * mapSize);",
    "    texCoord = pixelCoord * inverseTileTextureSize * inverseTileSize;",
    "    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
    "}"
].join("\n");

片段着色器:

var tilemapFS = [
    "varying vec2 pixelCoord;",
    "varying vec2 texCoord;",

    "uniform sampler2D tiles;",
    "uniform sampler2D sprites;",

    "uniform vec2 inverseTileTextureSize;",
    "uniform vec2 inverseSpriteTextureSize;",
    "uniform float tileSize;",
    "uniform int repeatTiles;",

    "void main(void) {",
    "    vec4 tile = texture2D(tiles, texCoord);", //load this pixel of the tilemap
    "    if(tile.x == 1.0 && tile.y == 1.0) { discard; }", //discard if R is 255 and G is 255
    "    vec2 spriteOffset = floor(tile.xy * 256.0) * tileSize;", //generate the offset in the tileset this pixel represents
    "    vec2 spriteCoord = mod(pixelCoord, tileSize);",
    "    vec4 texture = texture2D(sprites, (spriteOffset + spriteCoord) * inverseSpriteTextureSize);",
    "    gl_FragColor = texture;",
    "}"
].join("\n");

每个纹理的设置如下:

//Setup Tilemap
this.tilemap.magFilter = THREE.NearestFilter;
this.tilemap.minFilter = THREE.NearestMipMapNearestFilter;
//tilemap.flipY = false;
if(this.repeat) {
    this.tilemap.wrapS = this.tilemap.wrapT = THREE.RepeatWrapping;
} else {
    this.tilemap.wrapS = this.tilemap.wrapT = THREE.ClampToEdgeWrapping;
}

//Setup Tileset
this.tileset.wrapS = this.tileset.wrapT = THREE.ClampToEdgeWrapping;
this.tileset.flipY = false;
if(this.filtered) {
    this.tileset.magFilter = THREE.LinearFilter;
    this.tileset.minFilter = THREE.LinearMipMapLinearFilter;
} else {
    this.tileset.magFilter = THREE.NearestFilter;
    this.tileset.minFilter = THREE.NearestMipMapNearestFilter;
}

制服是:

//setup shader uniforms
this._uniforms = {
    mapSize: { type: 'v2', value: new THREE.Vector2(this.tilemap.image.width * this.tileSize, this.tilemap.image.height * this.tileSize) },
    inverseSpriteTextureSize: { type: 'v2', value: new THREE.Vector2(1/this.tileset.image.width, 1/this.tileset.image.height) },
    tileSize: { type: 'f', value: this.tileSize },
    inverseTileSize: { type: 'f', value: 1/this.tileSize },

    tiles: { type: 't', value: this.tilemap },
    sprites: { type: 't', value: this.tileset },

    inverseTileTextureSize: { type: 'v2', value: new THREE.Vector2(1/this.tilemap.image.width, 1/this.tilemap.image.height) },
    repeatTiles: { type: 'i', value: this.repeat ? 1 : 0 }
};

以及实际的几何和网格:

//create the shader material
this._material = new THREE.ShaderMaterial({
    uniforms: this._uniforms,
    vertexShader: tilemapVS,
    fragmentShader: tilemapFS,
    transparent: false
});

this._plane = new THREE.PlaneGeometry(
    this.tilemap.image.width * this.tileSize * this.tileScale,
    this.tilemap.image.height * this.tileSize * this.tileScale
);

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

this.tileSize16this.tileScale4。我遇到的问题是,在 16x16 瓷砖的边缘周围我有些撕裂:

奇怪的是,它并非一直发生,只是在沿 y 轴移动时偶尔发生(但是在我的 linux 机器上,问题要严重得多,并且还会影响 x 轴)。

当与我的顶点着色器一起放置时,几乎就像 16x16 瓦片有少量偏移;但我不确定是什么原因造成的。任何帮助表示赞赏,谢谢!

编辑

这是撕裂的更好图像,在草地上更明显:

如您所见,它位于 16x16 平铺边缘(因为它们按4 缩放)。

【问题讨论】:

    标签: javascript shader webgl three.js textures


    【解决方案1】:

    我怀疑您遇到这些问题是由于相机位置导致精灵 uv 映射错误。基本上,您是在应该渲染的精灵上显示精灵表中的相邻精灵。

    这一行尤其是: `vec2 spriteOffset = floor(tile.xy * 256.0) * tileSize;"

    似乎它可能导致偏移问题。你可以通过用明亮的颜色填充你的精灵来做一个更好的检查,看看它是否会弹出。

    【讨论】:

      【解决方案2】:

      您正在使用 mipmapped 纹理,并且您的纹理坐标在图块边缘不连续:

      "    vec2 spriteOffset = floor(tile.xy * 256.0) * tileSize;", //generate the offset in the tileset this pixel represents
      "    vec2 spriteCoord = mod(pixelCoord, tileSize);",
      "    vec4 texture = texture2D(sprites, (spriteOffset + spriteCoord) * inverseSpriteTextureSize);",
      

      floor 和 mod 函数意味着纹理坐标突然在两个像素之间跳跃(如您所愿,以获得不同的图块)。

      但是,纹理查找使用像素之间坐标的变化来确定要使用的 mipmap 级别,因此在图块边缘它会跳转到使用小得多的 mipmap,并且存在不连续性,因为这是与其余部分不同的 mipmap像素。

      解决这个问题的最快方法是不使用 mipmap,例如之一:

      texture.minFilter = THREE.LinearFilter;
      texture.minFilter = THREE.NearestFilter;
      

      否则,如果您需要 mipmap,则需要将第三个参数 bias 传递给 texture2D。通常将此设置为 0.0,然后当您位于标题边缘时,将其设置为您要遍历的 mipmap 数量的负数以抵消这种影响。

      例如-11.0 将带您从最低的单像素 mipmap 到 2048x2048 的全尺寸纹理的 11 个 mipmap。确切的值需要根据缩放图像大小纹理大小等进行一些计算。

      【讨论】:

      • 我稍后会试试这个;谢谢!虽然我可能正在用一种新格式重写我的磁贴引擎,并把这个方法全部废弃。
      • 虽然这减轻了大部分撕裂,但仍有少量。跑来跑去时最明显的是,你可以看到线条闪烁着进出。
      • 我接受了这个答案,因为它让我最接近;虽然我真的把这一切都废弃了,并为 TMX 地图编写了一个渲染器 (this question)
      • 谢谢。我发现了同样的技巧,但认为它出于不同的原因起作用。你的解释很有道理。
      猜你喜欢
      • 2015-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-26
      • 1970-01-01
      • 2015-09-27
      • 2012-09-09
      • 1970-01-01
      相关资源
      最近更新 更多