2

Details: I have a glsl fragment shader with a uniform texture, "u_MapTexture" with several thousand colors on it (max of about 10k-15k unique rgb values). I also have a uniform palette texture ("u_paletteTexture") that is 16384 × 1 that I want to use to index the colors on u_MapTexture to. My problem is that no matter what I try mathematically, I can't seem to properly index the colors from the first texture to the palette texture using the RGB values of the passed color. Amy thoughts or ideas as to how I could do this?

Wasn't sure whether to post this here, on Gamedev SE, or on the Math SE.

Edit: I guess I might not have added enough information about the problem in, so here are some more details.

My current idea for the map is to keep an indexed palette of province colors, and to perform a palette-swap operation in my fragment shader (like the one outlined in this SO question: Simulating palette swaps with OpenGL Shaders (in LibGDX)). My shader is pretty much exactly copied from the linked article.

My problem: finding a way to uniquely index the province map (the original texture) -> province colors (the indexed palette texture).

At first, I decided that the palette texture would be configured as a (255+255)×(255+255) texture. This would give a large maximum number enough number of countries to choose from that would never in practice be reached.

I thought you could get the appropriate index of the palette texture of a country's color by getting its index in the texture as so: the index of each country would have been located at that palette texture's (x, y)->(r+g),(g+b)

I ran some example colors through this simple equation and came across a troubling scenario:

RGB (0, 0, 0) -> (0, 0);
RGB (1, 0, 1) -> (1, 1); ?
RGB (1, 3, 2) -> (4, 5);
RGB (0, 1, 0) -> (1, 1); ?
RGB (2, 5, 10) -> (7, 15);
RGB (255, 255, 255) -> (510, 510);

The question marks are by "recurring" colors in the algorithm, meaning that they would incorrectly map to the same country index.

Then I thought to add additional parameters and shrink the texture to a 1-dimensional array.

For example, the palette texture would have been of size (r+g+b),(r, g, b).

With this, with them same texture points:

RGB(0, 0, 0) -> (0);
RGB(1, 0, 1) -> (2); ?
RGB(0, 1, 1) -> (2); ?
RGB(1, 3, 2) -> (6); ?
RGB(3, 2, 1) -> (6); ?
RGB(0, 1, 0) -> (1);
RGB(2, 5, 10) -> (17);
RGB(255, 255, 255) -> (1020);

The recurrence problem is exacerbated. I did some quick calculations in my head (and thought about it more deeply in general) and I realized that no matter how many ways I add/multiply the color rgb variables, the same problem will occur due to the laws of mathematics. This leads to the actual problem: How can I uniquely and procedurally index country colors in the palette texture and access them via my shader? This seems like the most performant method, but its implementation is eluding me.

Also, for the record, I know that the UV coords and color values are floats, but I'm using the standard 0-255 format to logic the problem out.

TL;DR I need to extract a unique index from every RGB value, and that doesn't appear to be possible based on my test sets.

Basically the MCVE would be creating a 2D sprite and passing the fragment shader of the accepted answer of the linked SO question to sprite. The sprite would be comprised of about 10 unique RGB values, however whatever system used would have to support at least several thousand different unique colors. I don't have stable internet connection or I would upload my test textures.

Jax
  • 402
  • 7
  • 24
  • 2
    Hard to say without MCVE but my bet is you do not use `GL_NEAREST` filtering for both textures so your indexes and also palette colors got interpolated with neighbors. Use `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);` for both textures – Spektre Jun 19 '18 at 12:35
  • 1
    Good guess by @Spektre. If that doesn't solve it, you need to provide more information about the results you get, the shader involved, etc. – bernie Jun 19 '18 at 13:50
  • @Spektre No, I think you misunderstood the problem (entirely my fault), I've added more details, please give it another look-over. – Jax Jun 19 '18 at 16:23
  • @bernie Yeah, I've added some more details and a test set, so please give the problem another look over. Sorry for the inconvenience. – Jax Jun 19 '18 at 16:24
  • 1
    @the_OTHER_DJMethaneMan I added answer but not sure if that is what you want... – Spektre Jun 19 '18 at 21:54

2 Answers2

2

Not sure if I get it right anyway let assume integer channels <0,255> so:

id = r + 256*g + 65536*b

that will give you id = <0,16777215>. Now just remap to your xs*ys texture:

x = id%xs
y = id/xs

where xs,ys is the resolution of the texture. Once you realize you can use powers of 2 for all of this you can use bit operations instead. For example let xs=4096,ys=4096 ...

id = r + g<<8 + b<<16
x = id&4095
y = id>>12

[Edit1]

So if I use this image you linked as input (txr_map):

map

And generate 4096x4096 texture all filed with 0x00404040 gray color except:

((DWORD*)(scr.txrs.txr.txr))[0x4A3020]=0x00FF0000;
((DWORD*)(scr.txrs.txr.txr))[0x49247E]=0x0000FF00;
((DWORD*)(scr.txrs.txr.txr))[0xCB3EAD]=0x000000FF;
((DWORD*)(scr.txrs.txr.txr))[0xC78A4F]=0x0000FFFF;
((DWORD*)(scr.txrs.txr.txr))[0x593D4E]=0x00FF00FF;
((DWORD*)(scr.txrs.txr.txr))[0x4B3C7E]=0x00FFFF00;

where scr.txrs.txr.txr is linearly allocated texture array so address is also your id... This selects few regions I picked up with color picker and set them with specific colors (red,green,blue,...).

Do not forget to set GL_LINEAR for min and mag filter. Then applying these shaders should do the trick:

//---------------------------------------------------------------------------
// Vertex
//---------------------------------------------------------------------------
#version 120
varying vec2 pos;   // screen position <-1,+1>
varying vec2 txr;   // texture position <0,1>
void main()
    {
    pos=gl_Vertex.xy;
    txr=gl_MultiTexCoord0.st;
    gl_Position=gl_Vertex;
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Fragment
//---------------------------------------------------------------------------
#version 130
in vec2 pos;    // screen position <-1,+1>
in vec2 txr;    // texture position <0,1>
out vec4 col;
uniform sampler2D txr_map;
uniform sampler2D txr_pal;
//---------------------------------------------------------------------------
void main()
    {
    vec3 c;
    int id,x,y;

    c=texture2D(txr_map,txr).rgb;
    x=int(float(c.b*255.0f)); id =x;
    x=int(float(c.g*255.0f)); id|=x<<8;
    x=int(float(c.r*255.0f)); id|=x<<16;

    x= id     &4095;
    y=(id>>12)&4095;
    c.s=(float(x)+0.5f)/4096.0f;
    c.t=(float(y)+0.5f)/4096.0f;
    col=texture2D(txr_pal,c.st);
    }
//---------------------------------------------------------------------------

Sadly usampler2D does not work in my engine in the old API (that is why I use floats most likely some internal texture format problem). My CPU side GL code looks like this:

//---------------------------------------------------------------------------
OpenGLscreen scr;   // my GL engine
GLSLprogram shd;    // shaders
GLint txr_map=-1;   // map
GLint txr_pal=-1;   // palette
//---------------------------------------------------------------------------
void TForm1::draw()
    {
    scr.cls();  // glClear

    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);
    shd.bind(); // use shader program
    int unit=0;
    scr.txrs.bind(txr_map,unit); shd.set1i("txr_map",unit); unit++; // bind textures and set uniforms
    scr.txrs.bind(txr_pal,unit); shd.set1i("txr_pal",unit); unit++;
    float a=5632.0/8192.0;  // handle texture power of 2 size correction

    glActiveTexture(GL_TEXTURE0);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0,1.0); glVertex2f(-1.0,-1.0);
    glTexCoord2f(0.0,0.0); glVertex2f(-1.0,+1.0);
    glTexCoord2f( a ,0.0); glVertex2f(+1.0,+1.0);
    glTexCoord2f( a ,1.0); glVertex2f(+1.0,-1.0);
    glEnd();

    for (unit--;unit>=0;unit--) scr.txrs.unbind(unit);  // unbind textures
    shd.unbind();   // unbind shaders

    // just prints the GLSL logs for debug
    scr.text_init_pix(1.0);
    glColor4f(1.0,1.0,1.0,0.75);
    scr.text(0.0,0.0,shd.log);
    scr.text_exit_pixel();

    scr.exe();  // glFlush
    scr.rfs();  // swap buffers
    }
//---------------------------------------------------------------------------

The result looks like this:

preview

When I mix both result and input texture (for visual check) with:

col=(0.9*texture2D(txr_pal,c.st))+(0.1*texture2D(txr_map,txr));

The result looks like this:

mix

So it clearly works as expected...

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • These might seem like stupid questions but: where did you get 65536? For the integer channels, does r == 0 and g == 255, or does r == 0 and b == 255? And is 4096×4096 the resolution of the source texture or the palette? – Jax Jun 20 '18 at 04:07
  • @the_OTHER_DJMethaneMan `0-255` means 8 bit (`2^8=256`) per channel so 2 channels needs 16 bit (`2^16=65536`) and 3 channels need 24 bit (`2^24=16.7M`). Yes 4096 is the target resolution as `16.7M^0.5=4096` so it has the exact same amount of pixels/texels as you need. The `id` equation just loads the channels into its binary bit position thats why I multiply the channels by `2^(8*i)` where `i={0,1,2}`. The `x,y` equation does the opposite `x= low 12 bit` and `y =high 12 bit` of `id`. In the end is this just 1:1 mapping of `256^3 -> 4096^2` – Spektre Jun 20 '18 at 06:12
  • @the_OTHER_DJMethaneMan after I saw the images you linked was curious so I tried to encode this ... see **[Edit1]** with the result ... – Spektre Jun 20 '18 at 09:33
  • Okay, thanks for the clarification, I think I get it now. – Jax Jun 20 '18 at 16:39
1

Not sure I understand exactly what you want to do.

First of all, the only way to uniquely map all 8-bit RGB colors to indices is to have 256^3 indices. You can shuffle the bits around to have a non-identity mapping (like here), but you still need that many destination indices.

If only a subset of all colors is used and you want fewer than 256^3 destination indices (as you seem to describe), some mechanism needs to be put in place to avoid collisions. Unless you have some special properties in the source colors that can be exploited mathematically, this mechanism will require some form of storage (like another texture or an SSBO).

Now what I don't understand is what you want to map to indices. Do you want to map all possible colors to a unique index? Does everything related to the mapping have to be done exclusively inside the shader? You mention countries and provinces, but I don't quite get how they relate exactly to the mapping you want.

bernie
  • 9,820
  • 5
  • 62
  • 92
  • I'm trying to make a political map, like those in the Europa Universalis or Total War series. Each unique RGB value that is present in the colormap represents a province. I wanted to generate a terrain mesh and overlay the colormap on the terrain, and use a palette swap to change province colors (for example, if the province changes owner). The idea was I'd maintain a separate palette texture and each province would have it's own unique index in the palette, but I have no way to send data to the shader on a per-province basis, so I need to find some other way to achieve indexing. – Jax Jun 19 '18 at 22:06
  • 1
    Something like the colormap found here: https://eu4.paradoxwikis.com/Map_modding to generate something like the images here: https://www.google.com/search?q=eu4+world+map&client=ms-android-americamovil-us&prmd=ivmn&source=lnms&tbm=isch&sa=X&ved=0ahUKEwin2d6y4ODbAhVL6IMKHUDpAxcQ_AUIESgB&biw=360&bih=559&dpr=1.5 – Jax Jun 19 '18 at 22:28
  • 1
    @the_OTHER_DJMethaneMan In that case, I think Spektre describred the simplest unique mapping available. Let us know if you need something more. – bernie Jun 20 '18 at 02:52