Shader "sprite constants" limit

Hi, i’m trying to create dynamic sprite coloring, where you can change parts of sprite colors in runtime.
Because i can’t pass texture/array to shader (as i figured out there), i decided to pass a bunch of vec4’s to vertex shader -> convert them into array of colors -> pass this array to fragment shader -> grab the colors from that array.
So, i have shader variables ‘color0’ … ‘color9’. In game object init() function i set this values with

go.set(’#sprite’, data.hash, data.color)

Where hash = hash(‘color*’) and color is the default or randomized (unique for this exact sprite) color i want.
But when i do that, i get

WARNING:GAMESYS: Out of sprite constants (4)

errors after every call except for the first ones, and only this first couple of colors are being set for every game object (others remain the same as in material itself).
Shaders code: vertex, fragment. In the material i added 10 vertex constants with the same name (screenshot). Interesting, that without adding constants in the material i got the same results.

  1. What does that error mean? I saw it is possible to pass more variables to shader here. What am i doing wrong?
  2. Maybe there are some more optimal approaches to this task?
  3. Or are there any way not to waste time on filling “colors” array in vertex shader on every frame for every vertex at least?
1 Like

It looks like go.set might be the wrong way to set a shader constant?

You can set the initial value of the constant in the constant definition, but it is mutable through the functions sprite.set_constant() and sprite.reset_constant() (resets the value to the one given in the definition)

Looks like it does the same thing. Tried replacing go.set for sprite.set_constant and recieved the same error (only first couple of vec4’s are set properly aswell).

Another note: if i remove, for example, next lines in vertex shader:

colors[1] = color1;
colors[2] = color2;

I will get errors when trying to do sprite.set_constant like

ERROR:GAMESYS: ‘location:/instance3#sprite’ has no constant named ‘color1’ (or color2)

while all other variables will still be set up properly or throw “out of sprite constants” error.
How does using the variable in the shader affects its visibility from the lua-code?

So, i tried to use .model object instead of sprite, got basically the same results, but with better errors output.
Basically, i have:

  1. Model, which material has next vertex (same can be in fragment, doesnt matter) shader:
uniform mediump mat4 viewproj;
uniform mediump mat4 world;

// Out variables we are setting from code
uniform lowp vec4 test1;
uniform lowp vec4 test2;
uniform lowp vec4 test3;
uniform lowp vec4 test4;
uniform lowp vec4 test5;
uniform lowp vec4 test6;
uniform lowp vec4 test7;

attribute mediump vec4 position;
attribute mediump vec2 texcoord0;
attribute mediump vec3 normal;

varying mediump vec4 var_position;
varying mediump vec2 var_texcoord0;
varying mediump vec3 var_normal;

void main()
{
    var_position = viewproj * world * position;
    gl_Position = var_position;
    var_texcoord0 = texcoord0;
    var_normal = normal;
    
    // We need this so we wont get "... does not have any property called..." errors
    test1;
    test2;
    test3;
    test4;
    test5;
    test6;
    test7;
}
  1. A script attached next to the model, which has next code in the init:
for i=1,7 do
  local name = 'test' .. i
  print('setting: ' .. name)
  --sprite.set_constant('#sprite', data.hash, data.color)
  go.set('#model', name, vmath.vector4(0, 1, 0, 1))
end

And i resieve the following console output:

DEBUG:SCRIPT: setting: test1
DEBUG:SCRIPT: setting: test2
DEBUG:SCRIPT: setting: test3
DEBUG:SCRIPT: setting: test4
DEBUG:SCRIPT: setting: test5
ERROR:RENDER: Out of per object constant slots, max 4, when setting constant test5
DEBUG:SCRIPT: setting: test6
ERROR:RENDER: Out of per object constant slots, max 4, when setting constant test6
DEBUG:SCRIPT: setting: test7
ERROR:RENDER: Out of per object constant slots, max 4, when setting constant test7

So, what are those constants and how can i change its amount (can’t find any simillar settings in the game.project file)?

@sven, could you please help out here?

The “sprite” constant, or “object” constants are stored per component, since they are in fact properties. As such, you can animate them. Batching is also dependent on the constants, so setting one sprites’ “tint” to color1, and another sprites’ to color2, means they’ll be rendered separately, since we only upload shader constants once per batch.

The limit of 4 render constants per component, seems a bit random,I guess it was to be able to both preallocate and keep memory usage down. But I agree it doesn’t feel flexible enough. We’ll ping @Ragnar_Svensson to see if he can shed some light on it :slight_smile:

I’m not sure how your example link from 1) was achieved though :confused:

As for “a better solution”, I’m not entirely sure what you are trying to achieve. It seems like a palette texture? Perhaps you can elaborate a little on your use case?

1 Like

The limit was fairly random and an attempt at choosing a value that is guaranteed to work for all gpus we support. It might very well be the case that it’s in fact too low, but that needs to be investigated. We have most likely dropped some low end devices since that limit was chosen, 5 years ago. :wink:

1 Like

I asked op of this topic, he said it was done with the constant buffers, which is not a solution for me.
But a DEF-2055 feature request about passing arrays to shaders was created in that topic. This would be a nice solution for me aswell, are there any plans for this in the not-so-far future?

@Mathias_Westerdahl
Thanks for the explanation! What i want to do is fairly well described here. I have a game character (zombie) with spritesheet animations and everything. There are lots of them and i want to randomize their colors in some way, altering, for example, their shirt and pants but without affecting skin and bandages color. Game has pixel-art style, so the character has small neat color palette (~8 colors) and there seems to be no need in any more sophisticated approach.
In the article it is suggested to procedurally create a unique 1x256 texture and then just do “color = new_palette[color.x * 255]” in the fragment shader (of course considering the fact that no colors should have the same red value). This would be the best solution for me, but there seems to be no way to create a procedural texture, except to render part of the scene, no “set_pixel” functions for us :frowning:
So i’m trying another one - i don’t even need 256 colors, i’m fine with 10, so i’m just creating 10 vec4 variables, collect them into vec4 array in vertex shader and use that array like a 1x10 texture in fragment shader. It could work for me (after fixing some colors in the base zombie spritesheet), but i can’t use more than 4 constants (which also leaves me with no tint and saturation constants aswell i suppose).
Here is the zombies (Smash Bash, ho!) with altered shirt color (not randomized but its easy to add): http://joxi.ru/krDDz85SE0NYxr.png . The problem is this is done with “if (int(floor(color.x * 255.0)) == 115) color = vec4(1,0,0,1);”, and even though i can live with that, it will require a much more complex fragment shader with lots of if statements, which is most probably a really bad idea considering perfomance. And i would have to create a unique shader for every type of monster with this approach.

@Mathias_Westerdahl @Ragnar_Svensson
So basically any of this would help me:

  1. Creating and passing procedurally created 1x256 texture as shader constant
  2. Passing array (vec4’s, but simple floats will work too) as shader constant. (DEF-2055)
  3. Increasing the amount of variables to at least 10 (16 would be better). Not so good approach, as it requires me to do some work in vertex shader, but still possible. Is it possible to check if you can increase constants amount?
  4. If there are any other way to make this “procedural diversification” (i like the sound of that lol), i’m all ears. Without increasing the amount of work for the artists too much if possible :slight_smile:
  5. Not really good solution, but better than nothing: if i could manually create, like, 10 different “palettes” and save them somehow in the material/shader, and then just pass one number constant - which of this 10 palettes to use, there will be at least 10 different zombie types in the game. Problem is i have no idea how to save this data in the material and how then to use it from fragment shader.

There probably is also a more complex way - to use a mask spritesheet that is somehow passed to the shader with the same current frame as original sprite. Then i can use any of x,y,z,w values of the pixel to represent the color, aka "final_color = procedural_color1 * pixel.x + … + procedural_color_4 * pixel.w (this is used in some terrain frameworks, i fogot how this approach is called). But still, every mask texture allows to use only 4 channels and is overkill, because i dont need to combine colors. And still 4 shader constants wouldnt be enough for me.

4 Likes

So, i did it with the “naive” shader approach. Removed tint and saturation constants, so i can change 4 colors now. I can’t change boots/bandages/etc color, but it is still better than nothing. Yet the fragment shader with 4 conditionals depresses me a bit :cry:
Still hoping for DEF-2055 to be implemented in the next couple of months :8ball:

...

uniform lowp vec4 color_a;
uniform lowp vec4 color_b;
uniform lowp vec4 color_c;
uniform lowp vec4 color_d;

void main()
{
    lowp vec4 color = texture2D(DIFFUSE_TEXTURE, var_texcoord0.xy);
    
    lowp int index = int(color.x * 255);
    if (index == 115)
        color = vec4(color_a.xyz, color.w);
    else if (index == 73)
        color = vec4(color_b.xyz, color.w);
    else if (index == 151) 
        color = vec4(color_c.xyz, color.w);
    else if (index == 110) 
        color = vec4(color_d.xyz, color.w);
        
    gl_FragColor = color;
}
2 Likes