如何创建和使用非常大的调色板纹理以用于 opengl?

How to create and use very large palette textures for use in opengl?

详细信息:我有一个具有统一纹理的 glsl 片段着色器,"u_MapTexture" 上面有几千种颜色(最多约 10k-15k 独特的 rgb 值)。我还有一个 16384 × 1 的统一调色板纹理 ("u_paletteTexture"),我想用它来索引 u_MapTexture 上的颜色。我的问题是,无论我在数学上尝试什么,我似乎都无法使用传递颜色的 RGB 值正确地将颜色从第一个纹理索引到调色板纹理。艾米关于如何做到这一点的想法或想法?

不确定是在此处、在 Gamedev SE 上还是在 Math SE 上post。

编辑:我想我可能没有添加足够的关于问题的信息,所以这里有一些更多的细节。

我目前对地图的想法是保留省份颜色的索引调色板,并在我的片段着色器中执行调色板交换操作(就像这个 SO 问题中概述的那样:Simulating palette swaps with OpenGL Shaders (in LibGDX))。我的着色器几乎完全是从链接文章中复制的。

我的问题:找到一种唯一索引省份地图(原始纹理)-> 省份颜色(索引调色板纹理)的方法。

起初,我决定将调色板纹理配置为(255+255)×(255+255)纹理。这将提供足够多的国家/地区供选择,而实际上永远无法达到。

我认为你可以通过在纹理中获取索引来获得国家颜色调色板纹理的适当索引:每个国家的索引将位于该调色板纹理的 (x, y)- >(r+g),(g+b)

我 运行 通过这个简单的等式给出了一些示例颜色,并遇到了一个令人不安的场景:

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

问号在算法中由 "recurring" 颜色表示,这意味着它们会错误地映射到同一国家/地区索引。

然后我想添加额外的参数并将纹理缩小到一维数组。

例如,调色板纹理的大小为 (r+g+b),(r, g, b)。

有了这个,与他们相同的纹理点:

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

复发问题加剧。我在脑子里做了一些快速计算(并且总体上更深入地考虑了它)并且我意识到无论我 add/multiply 颜色 rgb 变量有多少种方式,由于数学定律,同样的问题都会发生。这导致了实际问题:我怎样才能在调色板纹理中唯一地和程序化地索引国家颜色并通过我的着色器访问它们?这似乎是性能最高的方法,但它的实现让我望而却步。

另外,郑重声明,我知道 UV 坐标和颜色值是浮点数,但我使用标准的 0-255 格式来解决问题。

TL;DR 我需要从每个 RGB 值中提取一个唯一索引,但根据我的测试集,这似乎是不可能的。

基本上,MCVE 将创建一个 2D 精灵并将链接的 SO 问题的已接受答案的片段着色器传递给精灵。精灵将由大约 10 个独特的 RGB 值组成,但是无论使用什么系统都必须支持至少数千种不同的独特颜色。我没有稳定的互联网连接,或者我会上传我的测试纹理。

不确定我是否完全理解你想要做什么。

首先,将 所有 8 位 RGB 颜色唯一映射到索引的唯一方法是使用 256^3 个索引。您可以将这些位打乱以获得非身份映射(如 here),但您仍然需要那么多目标索引。

如果只使用了所有颜色的一个子集,并且您想要少于 256^3 个目标索引(正如您所描述的那样),则需要采用某种机制来避免冲突。除非您在源颜色中有一些可以数学利用的特殊属性,否则此机制将需要某种形式的存储(如另一个纹理或 SSBO)。

现在我不明白你想映射到索引的是什么。您要将 所有 种可能的颜色映射到唯一索引吗?是否所有与映射相关的事情都必须在着色器中专门完成?你提到了国家和省份,但我不太明白它们与你想要的映射有什么关系。

不确定我是否做对了假设整数通道<0,255>所以:

id = r + 256*g + 65536*b

那会给你 id = <0,16777215>。现在只需重新映射到您的 xs*ys 纹理:

x = id%xs
y = id/xs

其中 xs,ys 是纹理的分辨率。一旦您意识到可以使用 2 的幂来完成所有这些操作,您就可以改用位运算。例如让 xs=4096,ys=4096 ...

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

[编辑 1]

因此,如果我使用您链接的这张图片作为输入 (txr_map):

并生成 4096x4096 纹理,所有文件都使用 0x00404040 灰色,除了:

((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;

其中 scr.txrs.txr.txr 是线性分配的纹理数组,所以地址也是您的 id...蓝色,...).

不要忘记为最小和放大过滤器设置 GL_LINEAR。然后应用这些着色器应该可以解决问题:

//---------------------------------------------------------------------------
// 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);
    }
//---------------------------------------------------------------------------

遗憾的是 usampler2D 在旧的 API 中无法在我的引擎中工作(这就是为什么我使用 float 很可能是一些内部纹理格式问题)。我的 CPUGL 代码如下所示:

//---------------------------------------------------------------------------
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
    }
//---------------------------------------------------------------------------

结果如下所示:

当我混合结果和输入纹理(用于视觉检查)时:

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

结果如下所示:

所以它显然按预期工作...