godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.15k stars 96 forks source link

Support creating TileSets from a TextureArray / TextureLayered #1026

Open agausmann opened 4 years ago

agausmann commented 4 years ago

Describe the project you are working on:

I'm working on a project that uses tilemaps/tilesets, and I'm wanting to use shaders that sample the surrounding texture pixels for the current tile, in order to accomplish various things:

Describe the problem or limitation you are having in your project:

The problem with sampling nearby texels in a tileset / atlas is that on the edge of the tile, you may be sampling some pixels that belong to a neighboring tile. This is known as "texture bleeding", and produces undesirable results. For example, this is what happens with my outline shader:

2020-06-08-152831_218x204_scrot

This is happening because some of the tiles in my tileset share a boundary, and so texels from a neighboring tile get sampled as if they were valid neighbor texels.

Describe the feature / enhancement and how it helps to overcome the problem or limitation:

Creating a tileset from a layered texture / texture array would solve this problem, because the tiles would no longer neighbor each other in UV space, so there is no way for the tiles to bleed over each other if the layer is constant/invariant for each tile.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:

There would be an API change for TileSet, allowing it to accept layered textures and optionally accepting a layer index when setting texture offsets for tiles. I'd also like support in the editor itself, to make working with layered textures easier, maybe it could show/treat each of the layers as if it were its own "texture" in the list.

If this enhancement will not be used often, can it be worked around with a few lines of script?:

Adding a 1px or 2px margin around the tiles would solve this, as long as I don't sample further than the margin width, but I would like to avoid doing that. Maybe another option would be to clamp the UV coordinates to the boundaries of the current tile? I'm not sure how I would approach that.

(edit: I'm aware it can be done within the shader, but I don't know of a good way to pass the UV bounds to it or calculate them from within the shader.)

It may be possible to dynamically create the TileSet using the existing APIs, creating ImageTextures from the image data from each layer and setting them for each tile. Something like:

tileset.tile_set_texture(i, ImageTexture.create_from_image(layered_texture.get_layer_data(i)))

However, if I understand correctly, this would take away the main benefit of making the atlas a single texture, which is that you don't have to change render state before rendering each tile. They'd all be converted into separate textures if it was created like this.

Is there a reason why this should be core and not an add-on in the asset library?:

I don't see how this feature could be integrated into the official TileSet from a third-party asset without incurring some kind of performance penalty. To avoid that, it would have to be a fork/port of the existing TileSet code. It might also have to reimplement some of the dependents of TileSet, like TileMap, to accept this new tileset. Further, I'm not sure how well a third-party asset can be integrated into the editor, I don't know if it could have the same interface that is currently available for tilemaps and tilesets.

Calinou commented 4 years ago

Maybe another option would be to clamp the UV coordinates to the boundaries of the current tile? I'm not sure how I would approach that.

This can be done using a shader, but it's known to be relatively expensive.