【问题标题】:Simulating palette swaps with OpenGL Shaders (in LibGDX)使用 OpenGL 着色器模拟调色板交换(在 LibGDX 中)
【发布时间】:2014-11-25 18:00:24
【问题描述】:

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

调色板似乎已成为过去,而且似乎获得类似结果的最佳选择是使用着色器。

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

我的意图是使用 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();
    }
}

我不知道这是否是将浮点值传递给片段制服的正确方法。也不确定我尝试使用的代码 sn-p 是如何工作的。

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

这是预处理的精灵

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

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

【问题讨论】:

  • 为什么这会导致投票失败?
  • 不知道:/我知道我的英语不是很好,而且我有点笨拙,但我真的在努力做到最好^^U 非常感谢帮助我,Tenfour :) (顺便说一句,我需要 5 点的声望来支持你的答案^^U)

标签: android opengl-es libgdx shader fragment-shader


【解决方案1】:

我注意到了一些问题。

1) 在您的片段着色器中,vec2 index = vec2(color.r + paletteIndex, 0); 不应该是vec2 index = vec2(color.r, 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) 您可能还希望透明像素保持透明。所以我会更改片段着色器的最后一行以保留 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) 您的源精灵需要进行预处理以映射到调色板查找表的列。您的着色器正在从纹理的红色通道中提取坐标。因此,您需要对源图像中的每个像素颜色进行颜色替换。您可以提前手动完成此操作。这是一个例子:

肤色是六列 (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.draw 之前调用 batch.setColor(paletteIndex,0,0,0);

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

paletteIndex = a_color.r;

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

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

【讨论】:

  • 非常感谢您的回答:) 我尝试了您告诉我的更改,但它仍然显示一个白色矩形。现在我没时间了,但明天我可以更彻底地测试它。无论如何,如果您愿意,可以查看存储库(进行更改时我可能搞砸了)
  • 我犯的一个错误是 glActiveTexture 调用必须在 batch.end 之前。我错过了看到你获得紫外线的时髦方式。另外,我从您的 bitbucket 中看到您的源精灵尚未编码。我会更新答案。
  • 还刚刚注意到我使用 getWidth 而不是 getHeight 来计算调色板编号,这是错误的,因为您的调色板是行而不是列。
  • 还有一件事。由于 UV 坐标是自下而上的,所以调色板 0 是底行,调色板 5 是顶行。或者如果你更喜欢从上到下,你可以从 1 中减去 paletteIndex 来翻转它。
  • 效果很好,非常感谢! :D 我将更改添加到test repository,以防万一其他人对调色板交换感兴趣;)
猜你喜欢
  • 1970-01-01
  • 2013-01-12
  • 2016-08-07
  • 2018-09-10
  • 2016-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多