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

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

看起来,色盘是过去的事情,而且似乎达到类似结果的最佳select是使用着色器

这里是一个图像,解释我现在正在尝试:

我正在尝试做什么

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

在做了一些研究之后,我在gamedev stackexchange中find了一个post ,显然这是我正在寻找的,所以我试图去testing它。

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

我的顶点着色器:

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

我的片段着色器:

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

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

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

我不知道如果这是一个正确的方法来传递一个浮点值的统一的片段。 不知道如何使用我试图使用的代码片段。

编辑:我尝试了由TenFour04build议的变化,他们工作完美无瑕:)

这是预处理的精灵

预处理的精灵

我已经更新了存储库中的更改( 这里的 Java代码, 这里是Fragment Shader),如果有人感兴趣,可以在这里下载: https : //bitbucket.org/hcito/libgdxshadertest

编辑2:我刚刚添加到存储库的最后一个优化,TenFour04build议(传递调色板索引到R通道内的每个Sprite调用sprite.setColor()方法),并再次运作完美:)

Solutions Collecting From Web of "使用OpenGL着色器模拟调色板交换(在LibGDX中)"

我注意到一些问题。

1)在你的片段着色器中,不应该vec2 index = vec2(color.r + paletteIndex, 0); be 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 

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 } 

4)GPU可能会为你做这个,但是由于你没有使用顶点颜色,所以你可以从顶点着色器中移除包含v_color的两行v_color

5)你可能也希望透明像素保持透明。 所以我会改变你的片段着色器的最后一行来保存alpha:

 gl_FragColor = vec4(indexedColor.rgb, color.a); 

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 } 

7)你的源精灵需要预处理映射到你的调色板查找表的列。 你的着色器从纹理的红色通道中拉出一个坐标。 因此,您需要对源图像中的每个像素颜色进行颜色replace。 你可以提前手工做到这一点。 这是一个例子:

肤色是六列中的第二个索引(0-5)。 所以就像我们计算paletteIndex一样,我们将它归一化为像素的中心: skinToneValue = (2+0.5) / 6 = 0.417 。 6是为六列。 然后在你的绘图程序中,你需要适当的红色值。

0.417 * 255 = 106 ,这是6A的hex,所以你想要的颜色#6A0000。 用这个颜色replace所有的皮肤像素。 等其他色调等。


编辑:

还有一个优化是你可能希望能够将所有的精灵集中在一起。 现在是这样,你将不得不分别为每个调色板分组所有的精灵,并为他们每个调用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; 

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

当然,你不应该再调用shader.setUniformf("paletteIndex", paletteIndex);