2014-10-01 73 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