使用OpenGL着色器模拟调色板交换(在LibGDX中)

hci*_*ito 11 shader android opengl-es fragment-shader libgdx

我正在尝试使用LibGDX制作复古风格的小游戏,我想让玩家选择几个角色的颜色,所以我想加载png索引图像,然后以编程方式更新调色板...错了我是^^ U.

看起来颜色托盘已经成为过去,而且看起来实现类似结果的最佳选择是使用着色器.

这是一张图片,解释了我现在正在尝试的内容:

我想做什么

我的意图是使用2张图片.其中之一pixel_guy.png 是只有6种颜色的png图像(这些颜色是其原始调色板).另一个图像colortable.png是一个6x6像素的png,其中包含6个调色板,每个调色板有6种颜色(每行是不同的调色板).来自第一行像素colortable.png的颜色将匹配使用的颜色pixel_guy.png,即第一个/原始调色板,其他行将是调色板2到6.我尝试实现的是使用colortable的第一个调色板来索引像素颜色,然后通过向着色器发送一个数字(从2到6)来更改调色板.

在做了一些研究之后,我在gamedev stackexchange中发现了一个帖子,显然它正是我想要的,所以我试着测试它.

我创建了顶点和片段着色器并加载了我的纹理(我想要交换的调色板,以及包含多个调色板的调色板),但输出是一个意外的白色图像.

我的顶点着色器:

attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;

uniform mat4 u_projTrans;

varying vec4 v_color;
varying vec2 v_texCoords;

void main() {
    v_color = a_color;
    v_texCoords = a_texCoord0;
    gl_Position = u_projTrans * a_position;
}
Run Code Online (Sandbox Code Playgroud)

我的片段着色器:

    // Fragment shader
// Thanks to Zack The Human https://gamedev.stackexchange.com/questions/43294/creating-a-retro-style-palette-swapping-effect-in-opengl/

uniform sampler2D texture; // Texture to which we'll apply our shader? (should its name be u_texture?)
uniform sampler2D colorTable; // Color table with 6x6 pixels (6 palettes of 6 colors each)
uniform float paletteIndex; // Index that tells the shader which palette to use (passed here from Java)

void main()
{
        vec2 pos = gl_TexCoord[0].xy;
        vec4 color = texture2D(texture, pos);
        vec2 index = vec2(color.r + paletteIndex, 0);
        vec4 indexedColor = texture2D(colorTable, index);
        gl_FragColor = indexedColor;      
}
Run Code Online (Sandbox Code Playgroud)

我用来制作纹理绑定并将调色板编号传递给着色器的代码:

package com.test.shaderstest;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;

public class ShadersTestMain extends ApplicationAdapter {
    SpriteBatch batch;

    Texture imgPixelGuy;
    Texture colorTable;

    private ShaderProgram shader;
    private String shaderVertIndexPalette, shaderFragIndexPalette;

    @Override
    public void create () {
        batch = new SpriteBatch();

        imgPixelGuy = new Texture("pixel_guy.png"); // Texture to which we'll apply our shader
        colorTable =  new Texture("colortable.png"); // Color table with 6x6 pixels (6 palettes of 6 colors each)

        shaderVertIndexPalette = Gdx.files.internal("shaders/indexpalette.vert").readString();
        shaderFragIndexPalette = Gdx.files.internal("shaders/indexpalette.frag").readString();

        ShaderProgram.pedantic = false; // important since we aren't using some uniforms and attributes that SpriteBatch expects

        shader = new ShaderProgram(shaderVertIndexPalette, shaderFragIndexPalette);
        if(!shader.isCompiled()) {
            System.out.println("Problem compiling shader :(");
        }
        else{
            batch.setShader(shader);
            System.out.println("Shader applied :)");
        }

        shader.begin();
        shader.setUniformi("colorTable", 1); // Set an uniform called "colorTable" with index 1
        shader.setUniformf("paletteIndex", 2.0f); // Set a float uniform called "paletteIndex" with a value 2.0f, to select the 2nd palette 
        shader.end();

        colorTable.bind(1); // We bind the texture colorTable to the uniform with index 1 called "colorTable"
    }

    @Override
    public void render () {
        Gdx.gl.glClearColor(0.3f, 0.3f, 0.3f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.begin();
        batch.draw(imgPixelGuy, 0, 0); // Draw the image with the shader applied
        batch.end();
    }
}
Run Code Online (Sandbox Code Playgroud)

我不知道这是否是将浮点值传递给片段的统一的正确方法.我不确定我尝试使用的代码片段是如何工作的.

编辑:我尝试了TenFour04建议的更改,他们完美无缺地工作:)

这是预处理的精灵

预处理的精灵

我已经更新与变化,仓库(Java代码在这里,片段着色器在这里),如果有人有兴趣,可以在这里下载:https://bitbucket.org/hcito/libgdxshadertest

编辑2:我刚刚在存储库中添加了TenFour04建议的最后一个优化(将调色板索引传递给调用sprite.setColor()方法的R通道中的每个sprite),并且它再次完美地运行:)

Ten*_*r04 9

我注意到了一些问题.

1)在你的片段着色器中,不vec2 index = vec2(color.r + paletteIndex, 0);应该vec2 index = vec2(color.r, paletteIndex);,所以精灵纹理告诉你哪一行,paletteIndex告诉你颜色表的哪一列要看?

2)调色板索引用作纹理坐标,因此它需要是0到1之间的数字.设置如下:

int currentPalette = 2; //A number from 0 to (colorTable.getHeight() - 1)
float paletteIndex = (currentPalette + 0.5f) / colorTable.getHeight();
//The +0.5 is so you are sampling from the center of each texel in the texture
Run Code Online (Sandbox Code Playgroud)

3)shader.end内部发生呼叫batch.end所以你需要在每一帧上设置你的制服,而不是在create.如果您稍后进行任何多重纹理,也可以将每个帧的二级纹理绑定在一起.

@Override
public void render () {
    Gdx.gl.glClearColor(0.3f, 0.3f, 0.3f, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    colorTable.bind(1);

    //Must return active texture unit to default of 0 before batch.end 
    //because SpriteBatch does not automatically do this. It will bind the
    //sprite's texture to whatever the current active unit is, and assumes
    //the active unit is 0
    Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0); 

    batch.begin(); //shader.begin is called internally by this line
    shader.setUniformi("colorTable", 1);
    shader.setUniformf("paletteIndex", paletteIndex);
    batch.draw(imgPixelGuy, 0, 0);
    batch.end(); //shader.end is called internally by this line

}
Run Code Online (Sandbox Code Playgroud)

4)GPU无论如何都可能为你做这件事,但由于你没有使用顶点颜色,你可以v_color从你的顶点着色器中删除两行.

5)您可能还希望透明像素保持透明.所以我会更改片段着色器的最后一行以保留alpha:

gl_FragColor = vec4(indexedColor.rgb, color.a); 
Run Code Online (Sandbox Code Playgroud)

6)这个可能是你全白的原因.在片段着色器中,您应该使用v_texCoords而不是gl_TexCoord[0].xy,这是来自桌面OpenGL而不是在OpenGL ES中使用.所以你的片段着色器应该是:

uniform sampler2D texture; // Texture to which we'll apply our shader? (should its name be u_texture?)
uniform sampler2D colorTable; // Color table with 6x6 pixels (6 palettes of 6 colors each)
uniform float paletteIndex; // Index that tells the shader which palette to use (passed here from Java)
varying vec2 v_texCoords;

void main()
{
    vec4 color = texture2D(texture, v_texCoords);
    vec2 index = vec2(color.r, paletteIndex);
    vec4 indexedColor = texture2D(colorTable, index);
    gl_FragColor = vec4(indexedColor.rgb, color.a); // This way we'll preserve alpha      
}
Run Code Online (Sandbox Code Playgroud)

7)您的源精灵需要预处理以映射到调色板查找表的列.您的着色器正在从纹理的红色通道中拉出坐标.因此,您需要对源图像中的每种像素颜色进行颜色替换.您可以提前手动完成此操作.这是一个例子:

肤色是六列(0-5)中的索引2.就像我们计算paletteIndex一样,我们将它标准化为像素的中心:skinToneValue = (2+0.5) / 6 = 0.417.6是六列.然后在绘图程序中,您需要适当的红色值.

0.417 * 255 = 106,这是六角形的6A,所以你想要颜色#6A0000.用这种颜色替换所有皮肤像素.对于其他色调等等.


编辑:

另一个优化是您可能希望能够将所有精灵一起批量处理.现在的方式是,您必须分别为每个调色板分组所有精灵并为每个调色板调用batch.end它们.你可以通过放入paletteIndex每个精灵的顶点颜色来避免这种情况,因为我们还没有使用顶点颜色.

所以你可以把它设置为精灵的四个颜色通道中的任何一个,比方说R通道.如果使用Sprite类,你可以调用sprite.setColor(paletteIndex, 0,0,0);,否则调用batch.setColor(paletteIndex,0,0,0);之前调用batch.draw每个精灵.

顶点着色器需要声明varying float paletteIndex;并设置如下:

paletteIndex = a_color.r;
Run Code Online (Sandbox Code Playgroud)

片段着色器需要声明varying float paletteIndex;而不是uniform float paletteIndex;.

当然你不应该打电话了shader.setUniformf("paletteIndex", paletteIndex);.