-->

How to create and use very large palette textures

2019-07-07 17:17发布

问题:

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.

回答1:

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):

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:

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:

So it clearly works as expected...



回答2:

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.