2014-10-01 46 views
10

我想用LibGDX制作复古风格的小游戏,我想让玩家选择几个字符的颜色,所以我想了想加载png索引图像,然后以编程方式更新调色板...我错过了多少错误^^用OpenGL着色器模拟调色板互换(在LibGDX中)

看来,颜色托盘是过去的东西,而且似乎最好的选择来实现类似的结果是通过使用着色器。

这里是一个图像解释什么,我想现在:

What I'm trying to do

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

经过一番研究,我发现了一个post in 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; 
} 

我的片段着色器:

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

我不知道这是否是将float值传递给片段统一的正确方法。不知道如何使用我尝试使用的代码片段。

编辑:我试图通过TenFour04建议的修改和他们的工作完美:)

这里是预处理精灵

Pre-processed sprite

我已经更新了更改的存储库, (Java代码here,片段着色器here),如果有人有兴趣,可以在这里下载:https://bitbucket.org/hcito/libgdxshadertest

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

+0

为什么这会降低选票? – Tenfour04 2014-10-01 12:20:14

+0

不知道:/我知道我的英语不太好,而且我有点笨拙,但我真的很想在这里尽我所能^^ U非常感谢你帮助我,Tenfour :)(顺便说一下,我需要5点声望upvote你的答案^^ U) – hcito 2014-10-01 18:34:45

回答

8

我注意到了一些问题。

1)在你的片段着色器中,不应该为vec2 index = vec2(color.r + paletteIndex, 0);vec2 index = vec2(color.r, paletteIndex);,所以sprite纹理告诉你哪一行和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两条线。

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

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)你的源精灵需要预处理映射到你的调色板查找表的列。你的着色器从纹理的红色通道中拉出一个坐标。因此,您需要对源图像中的每个像素颜色进行颜色替换。您可以提前手动完成。以下是一个示例:

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

0.417 * 255 = 106,它是6A的十六进制数,所以你想要颜色#6A0000。用这种颜色替换所有的皮肤像素。等其他色调。


编辑:

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

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

顶点着色器需要声明varying float paletteIndex;,并将其设置是这样的:

paletteIndex = a_color.r; 

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

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

+0

非常感谢你回答:)我试过你告诉我的变化,但它仍然显示一个白色的矩形。现在我没时间了,但我明天可以更彻底地测试它。无论如何,如果你想要的话,你可以看看版本库(我可能搞错了什么) – hcito 2014-10-01 18:24:37

+0

我犯的一个错误是glActiveTexture调用需要在'batch.end'之前。我错过了看到你获得uvs的时髦方式。另外,我从你的bitbucket看到你的源代码精灵尚未被编码。我会更新答案。 – Tenfour04 2014-10-01 19:09:25

+0

也只是注意到我使用getWidth而不是getHeight来计算调色板号码,这是错误的,因为您的调色板是行而不是列。 – Tenfour04 2014-10-01 19:19:58