(SOLVED) Is it possible to draw a sprite's entire Atlas to a render target?

I’ve been working on a modular sprite system where the player character is made up of a bunch of heavily layered sprites so that worn equipment can be reflected on the model. Currently I’m creating each equipment slot via a collection factory, parenting each sprite to their appropriate z-offset layer so everything renders in the right order, and storing references to each sprite url. It’s working well so far, but each sprite needs to be updated whenever an animation is played, and there’s obviously a ton of overdraw happening which isn’t too efficient.

I was thinking it should be possible to, whenever this slot configuration changes, pre-render all of the layering that would happen on the scale of the whole atlas to a render target. Then all I’d have to do every frame is animate one of these sprites to use its texcoords to draw from this render target. The big thing that has me stumped is how to draw the entire contents of an atlas, since its texcoords are already manipulated by the sprite component to be focused on its current animation frame.

I’d really appreciate if anyone could point me in the right direction here, or let me know if what I want to do is just not possible. Thank you!

I’m not 100% sure I understand what you are looking for. Is all of the layered sprites of the same size? Or do they have different sizes and positioned/offset differently from their game objects?

I’m not 100% sure I understand what you mean here:

1 Like

Yes, all of the layered sprites and their atlases are the same size. The only difference in position is their z-offset, to get them to draw in the right order. Their images are also named consistently across every layer, so each layer’s atlas ends up packed in the same order. Basically what I’m hoping to do is draw each atlas’s entire generated texture to a render target, not just the small section of it that corresponds to the sprite’s currently set image.

pre-render all of the layering that would happen on the scale of the whole atlas to a render target

I mean rather than having to redraw each layer every frame, I’m hoping to overlay each layer’s full texture in the appropriate order, to construct a single texture for the player to use in the same way an atlas’s generated texture would be used.

every frame is animate one of these sprites to use its texcoords to draw from this render target

If it’s possible to collapse all of the textures into one, then I’d only need to play the flipbook animation of one of the sprites, rather than each layer’s sprite. Then I can use a new material on this one “dummy” sprite that uses its vp to draw from the pre-layered master texture rather than its normal texture. Since the image dimensions and order are the same for each layer’s atlas, texcoords should match 1:1, correct?

how to draw the entire contents of an atlas, since its texcoords are already manipulated by the sprite component to be focused on its current animation frame

Correct me if I’m wrong, but from playing around with the texcoord attribute in a sprite’s vp it seems like the sprite animation system works by passing texture coordinates to only draw from the section of the texture that corresponds to the currently playing animation frame? As opposed to, say, drawing from a render target, where you get passed texcoords going all the way from 0-1, on a sprite you only get a small range that varies based on the current image being played.

Thank you for your time, let me know if anything is still unclear.

Hmm, ok, I think I understand.

One solution is if you create another atlas to hold the final composition and use resource.get_atlas() and resource.get_texture() on each layer to get access to the pixels and then write them in layer order (taking alpha into account) to the final atlas for the composited image. It will require several possibly time consuming iterations over a lot of pixels, but it is doable. It will not work though if the atlas has texture compression applied.

The other solution is as you say to draw the entire atlas for each layer to a render target to hold the final composition and use that when drawing the character. We’ve recently added the ability to pass around texture handles to and from the render script which makes me think that this should be much simpler to achieve now. @jhonny.goransson will be able to provide more info.

1 Like

I don’t understand all of the layering stuff you want to achieve, but rendering an entire atlas to a render target is not that difficult to do, if that’s what you want. You can use texture handles for doing this:

In a script:

  • grab the atlas metadata:
    local a_info = resource.get_atlas("/path-to-my-atlas.a.texturesetc")
  • the a_info table contains a texture field, which is a path to the texture that’s backing the atlas
    local t_info = resource.get_texture_info(a_info.texture)
  • the t_info table contains a handle field that can be passed to the render script:
    msg.post("@render:", "here_is_my_cool_atlas", { handle = t_info.handle })

In the render script, you can use this handle to enable a texture when generating your render target:

render.set_render_target(self.my_rt)
render.enable_texture(0, self.my_cool_texture)
render.draw(self.my_custom_predicate)

Note that for this to work you will need a custom material and a model when doing the generation. For the model I usually use the 2x2 square that’s available under builtins and then create a material with a vertex shader that just sets the gl_Position to the position attribute and then also pass the texture coordinates to the fragment shader. In the fragment shader you can now use the incoming texture coordinate (var_texcoord0) to sample the texture.

3 Likes

@jhonny.goransson @britzl Thank you both for your answers! I actually also just stumbled across this old thread: Shader - UVs based on whole atlas? - #3 by ross.grams that made me realize the problem is actually not as complicated as I thought. If I pass the center point of the sprite to the vp, I can figure out which of the vertices is currently being evaluated based on its displacement from the center point. Then I can just assign the texcoord for that vertex to be the appropriate corner of the full texture, and set the vertex position to match the dimensions of the full texture.

#define ATLAS_HALF_WIDTH 256.0
#define ATLAS_HALF_HEIGHT 256.0

uniform highp mat4 view_proj;

// updated with the position of the sprite (center point)
uniform highp vec4 pos;

// positions are in world space
attribute highp vec4   position;
attribute mediump vec2 texcoord0;
attribute lowp float   page_index;

varying mediump vec2 var_texcoord0;
varying lowp float   var_page_index;

void main()
{
    vec4 temp_pos = vec4(position.xyz, 1.0);
    
    var_texcoord0  = texcoord0;
    vec2 diff = position.xy - pos.xy;
    if (diff.x < 0.0) {
        var_texcoord0.x = 0.0;
        temp_pos.x = pos.x - ATLAS_HALF_WIDTH;
    } else {
        var_texcoord0.x = 1.0;
        temp_pos.x = pos.x + ATLAS_HALF_WIDTH;
    }

    if (diff.y < 0.0) {
        var_texcoord0.y = 0.0;
        temp_pos.y = pos.y - ATLAS_HALF_HEIGHT;
    } else {
        var_texcoord0.y = 1.0;
        temp_pos.y = pos.y + ATLAS_HALF_HEIGHT;
    }
    
    var_page_index = page_index;
    gl_Position    = view_proj * temp_pos;
}

Based on my preliminary testing it seems to work, so I just need to set up the whole system. Thanks again!

3 Likes