Godot: TextureAtlas doesn't work in shader when passed as sampler2D

Created on 15 Feb 2019  路  11Comments  路  Source: godotengine/godot

Godot version: 3.0.6

Window 7 Home 64 bit

passed a texture atlas to a shader for palette swapping it seems like the shader ignores the texture atlas crop.

  • make a shader material
  • copy the shader code below and paste it as your shader code
  • copy the palette image to your project
  • create two texture atlases 1 with a rect of 0,0,4,1 and 1 with a rect of 0,1,4,1
  • pass these texture atlases as parameters to the shader.
  • see that the shader does not update the color.

if you use separate images it works.

shader_type canvas_item;

uniform sampler2D palette_old; //the color to test against
uniform sampler2D palette_new; //the target color
uniform float     threshold; 

//get the texture color
void fragment()
{
    vec4  texture_col  = texture    (TEXTURE,     UV);
    int   palette_size = textureSize(palette_old,  0).x;

    for (int i = 0; i < 4; i ++)
    {
        vec4 palette_col_old = texelFetch(palette_old, ivec2(i, 0), 0);
        vec4 palette_col_new = texelFetch(palette_new, ivec2(i, 0), 0);
        if (distance(texture_col.rgb, palette_col_old.rgb) < threshold) 
        {
            texture_col.rgb = palette_col_new.rgb; break;
        }
    }
    COLOR = texture_col;
}

pal8 <-- palette

bug confirmed rendering

Most helpful comment

shouldn't it crop it before passing it to the shader?
or at least not allow texture types to be passed?

I prefer the the first since it makes godot more flexible.

All 11 comments

Have the same problem. I had asked several times in Discord about it, no answer. So I had to cut my texture atlas on ~200 separated textures, and this is awful

What is a "texture atlas"? Do you mean you passed an AtlasTexture? Do you have an example project?

I'm quite skeptical as to how shaders are currently supposed to adjust sampling coordinates for AtlasTextures, because those textures are not actually textures, but just a region with a reference to an actual texture. They work in simple use cases because the draw_texture function adjusts the UVs when drawing, and if you use those UVs to sample the texture then it's all fine in the end. However, in the shader language, the sampler2D type stands for an actual texture, and doesn't have any clue about the region. So when you assign an AtlasTexture as parameter for a shader to do something ELSE, the region information is not available, because it would need another uniform, all you get is the texture used by the AtlasTexture.

shouldn't it crop it before passing it to the shader?
or at least not allow texture types to be passed?

I prefer the the first since it makes godot more flexible.

The problem with baking region into the sampling operation under the hood, is that it will break the common use case. As I explained, when you draw an AtlasTexture, the region information is actually used to change the UV parameter, which ends up with the correct result if your use case is to draw sprites. If the sampling itself was doing this region transform, it would break that because it would transform texture coordinates twice in the flow, and Godot would need to be refactored to leave UV unmodified.

Preventing from setting atlas textures in shader parameters would also be inconvenient, despite the annoyance, because it prevents again the "common" use case.

Those two use cases are both valid to me, but they can't work both without breaking compatibility.
There is a workaround, which involves some boilerplate to pass region information as a second parameter to the shader, so that you can do the cooordinate transform yourself when sampling the texture. Or, if you know how your texture is supposed to be, you can do some math to avoid having to pass it (i.e if you know the number of palettes laid out vertically, you can divide X by that number).

I really don't want to have to manually modify uv coords in the shader based on the region info passed in.
maybe godot could do some shader code behind the scenes right before running our shaders to clip the region.

Can confirm on version 3.2 on commit cbfb944a7be4cc111fe4
In the top right you can see the texture being correctly visualized by the editor, while in the scene you can see the texture applied to a 3D plane and the texture region being ignored.
Screenshot_2019-11-02_16-13-11

I ran into this while trying to work around the performance issues with Sprite3D, by trying to use an AtlasTexture on a MeshInstance material. Here's the shader and script I'm using to do that https://gist.github.com/t-mw/0b78167372ed97e7c78e3f3844f3ae75, assuming you're using an orthogonal camera and quad geometry.

It won't work for geometry that uses perspective, maybe someone can rework it to do that. The important part is:

    vec2 uv_offset = region_origin * inv_atlas_size;
    vec2 uv_scale = region_size * inv_atlas_size;
    UV = UV * uv_scale + uv_offset;

and then you'll need to somehow adjust for the atlas margins.

In a visual shader, plug in the uv input into a texture's uv that has the correct region, it will output the texture that you want.
image

This is my workaround.

the above solution is broken with godot 3.2.2.stable.

Was this page helpful?
0 / 5 - 0 ratings