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

时间:2018-06-19 03:48:22

标签: libgdx glsl fragment-shader palette color-palette

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

不确定是在这里发布,在Gamedev SE上,还是在数学SE上发布。

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

我目前对地图的想法是保留省级颜色的索引调色板,并在我的片段着色器中执行调色板交换操作(就像在这个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);

问号是通过算法中的“重复”颜色,这意味着它们会错误地映射到同一个国家/地区索引。

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

例如,调色板纹理的大小(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);

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

此外,为了记录,我知道UV坐标和颜色值是浮点数,但我使用标准的0-255格式来解决问题。

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

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

2 个答案:

答案 0 :(得分:2)

不确定我是否正确,让我们假设整数通道<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

[Edit1]

因此,如果我使用此图片,则将您链接为输入(txr_map

map

并生成全部以0x00404040灰色填充的4096x4096纹理,除了:

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

在旧的API中,usampler2D无法在我的引擎中正常工作(这就是为什么我使用float最有可能是某些内部纹理格式问题)的原因。我的 CPU GL 代码如下所示:

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

结果如下:

preview

当我同时将结果和输入纹理(用于视觉检查)与:

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

结果如下:

mix

因此它显然可以按预期工作...

答案 1 :(得分:1)

不确定我确切地知道你想做什么。

首先,唯一地将所有 8位RGB颜色映射到索引的唯一方法是具有256 ^ 3个索引。您可以改组周围的位以具有非身份映射(例如here),但是仍然需要那么多目标索引。

如果仅使用所有颜色的一个子集,并且您希望目标索引少于256 ^ 3(如您所描述的),则需要采用某种机制来避免冲突。除非您对源颜色具有一些特殊的属性,可以通过数学方式加以利用,否则该机制将需要某种形式的存储(例如另一种纹理或SSBO)。

现在我不了解的是您要映射到索引的内容。您想将所有种可能的颜色映射到唯一索引吗?是否所有与映射有关的事情都必须专门在着色器内部完成?您提到了国家和省,但我不太了解它们与您想要的映射的确切关系。