2014-10-01 28 views
10

Próbuję użyć LibGDX, aby stworzyć małą grę w stylu retro, i chciałbym pozwolić graczom wybrać kolory kilku postaci, więc pomyślałem o ładowanie obrazów indeksowanych png i późniejsze aktualizowanie palet ... Jak źle byłem^^ USymulowanie zamieniania palet za pomocą shaderów OpenGL (w LibGDX)

Wygląda na to, że kolorowe palety są czymś w rodzaju przeszłości, a także wydaje się, że najlepszą opcją osiągnięcia podobnego rezultatu jest użycie Shaderów .

Oto obraz wyjaśniający, co usiłuję teraz:

What I'm trying to do

Moim zamiarem jest użycie 2 zdjęcia. Jeden z nich, pixel_guy.png jest obrazem png z tylko 6 kolorami (te kolory są jego oryginalną paletą). Inny obraz, colortable.png, byłby png 6x6 pikseli, który zawiera 6 palet po 6 kolorów (każdy wiersz jest inną paletą). Kolory z pierwszego wiersza pikseli colortable.png pasują do kolorów użytych w pixel_guy.png, która byłaby pierwszą/oryginalną paletą, a pozostałe wiersze byłyby paletami od 2 do 6. Próbowałem osiągnąć, aby użyć pierwszej palety kolortable do indeksuj kolory pixelguy, a następnie zmień paletę, wysyłając numer (od 2 do 6) do modułu cieniującego.

Po przeprowadzeniu badań znalazłem post in gamedev stackexchange i najwyraźniej właśnie tego szukałem, więc próbowałem go przetestować.

Utworzyłem wierzchołki cieniowania wierzchołków i fragmentów i wczytałem moje tekstury (tę, której paletę chciałem zamienić, i tę, która zawierała kilka palet), ale wynik jest nieoczekiwanym białym obrazem.

My Vertex Shader:

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

My Fragment Shader:

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

Kod I wykorzystywane do wiązania tekstury i przekazać numer palety shadera:

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

Nie wiem, czy to jest właściwy sposób przekazania wartości zmiennoprzecinkowej do jednolitego fragmentu. Nie wiem też, jak działa fragment kodu, którego próbowałem użyć.

Edit: Próbowałem zmian sugerowanych przez TenFour04 i działał bez zarzutu :)

Oto wstępnie przetwarzane sprite

Pre-processed sprite

I zostały zaktualizowane repozytorium ze zmianami, (Kod Java here, Fragment Shader here), na wypadek, gdyby ktoś był zainteresowany, można go pobrać tutaj: https://bitbucket.org/hcito/libgdxshadertest

Edycja 2: Właśnie dodałem do repozytorium ostatnią optymalizację, którą TenFour04 zalecił (aby przekazać indeks palety do każdego ikonki wewnątrz metody R wywoływania metody sprite.setColor()) i znowu zadziałało idealnie :)

+0

Dlaczego to się kończy głosowaniem? – Tenfour04

+0

Nie mam pojęcia:/Wiem, że mój angielski nie jest bardzo dobry, i że jestem trochę niezgrabny i wszystko, ale naprawdę staram się zrobić tu wszystko najlepiej ^^ Bardzo dziękuję za pomoc, Tenfour :) (Nawiasem mówiąc, potrzebuję 5 punktów więcej w reputacji, aby przegłosować twoją odpowiedź^^ U) – hcito

Odpowiedz

8

Zauważyłem kilka problemów.

1) W twoim cieniującym cieniu nie powinien być vec2 index = vec2(color.r + paletteIndex, 0); być vec2 index = vec2(color.r, paletteIndex);, więc tekstura sprite mówi ci, który wiersz i paletaIndeks mówi ci, na której kolumnie tabeli kolorów ma wyglądać?

2) Indeks paleta jest używana jako tekstury współrzędnych, tak więc musi być liczbą od 0 do 1. Zestaw tak:

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) Wywołanie shader.end dzieje się w batch.end więc musisz ustawić mundury na każdej ramce, a nie w create. Prawdopodobnie dobrym pomysłem jest również powiązanie tekstury drugorzędnej z każdą ramką, na wypadek gdyby później wykonano dowolny multiteksturowanie.

@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 to może zrobić dla ciebie i tak, ale skoro nie używasz kolorów wierzchołków, można usunąć dwa wiersze z udziałem v_color cieniowania wierzchołków.

5) Prawdopodobnie chcesz również, aby przezroczyste piksele pozostały przezroczyste. Więc chciałbym zmienić ostatnią linię swojej fragment shader zachować alpha:

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

6) Ten jest prawdopodobnie powód, dla którego były coraz cała biała. W cieniującym ciele powinieneś używać v_texCoords zamiast gl_TexCoord[0].xy, który jest czymś z OpenGL na pulpicie i nie jest używany w OpenGL ES. Więc fragment shader powinny być:

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) Twoje źródło sprite musi być wstępnie przetwarzane do mapowania kolumn tabeli paleta wyszukiwania. Twój moduł cieniujący pobiera współrzędną z czerwonego kanału tekstury. Dlatego konieczne jest zastąpienie kolorów każdego koloru piksela na obrazie źródłowym. Możesz to zrobić ręcznie z wyprzedzeniem. Oto przykład:

Odcień skóry to indeks 2 z sześciu kolumn (0-5). Tak jak obliczyliśmy paletteIndex, normalizujemy go do środka piksela: skinToneValue = (2+0.5)/6 = 0.417. Liczba 6 jest dla sześciu kolumn. Następnie w programie do rysowania potrzebujesz odpowiedniej wartości czerwieni.

0.417 * 255 = 106, która jest 6A w hex, więc chcesz kolor # 6A0000. Zastąp wszystkie piksele skóry tym kolorem. I tak dalej dla innych odcieni.


Edit:

Jeszcze optymalizacji jest to, że prawdopodobnie chcesz, aby móc partii wszystkie skrzaty razem. Tak jak jest teraz, będziesz musiał zgrupować wszystkie twoje duszki dla każdej palety osobno i zadzwonić pod numer batch.end dla każdego z nich. Możesz tego uniknąć umieszczając paletteIndex w kolorze wierzchołka każdego ikonki, ponieważ i tak nie używamy koloru wierzchołków.

Można więc ustawić go na dowolny z czterech kanałów kolorów ikonki, powiedzmy kanał R. Jeśli używasz klasy Sprite, możesz zadzwonić pod numer sprite.setColor(paletteIndex, 0,0,0);. W przeciwnym razie zadzwoń pod numer batch.setColor(paletteIndex,0,0,0);, zanim zadzwonisz pod numer batch.draw dla każdego z duszków.

Wierzchołek shader będzie musiał zadeklarować varying float paletteIndex; i ustawić go tak:

paletteIndex = a_color.r; 

Fragment shader będzie musiał zadeklarować varying float paletteIndex; zamiast uniform float paletteIndex;.

Oczywiście nie należy już dzwonić pod numer shader.setUniformf("paletteIndex", paletteIndex);.

+0

Dziękuję bardzo za odpowiedź :) Próbowałem zmian, które mi powiedziałeś, ale wciąż pokazuje biały prostokąt. Teraz jestem poza czasem, ale będę w stanie przetestować go dokładniej jutro. W każdym razie, jeśli chcesz, możesz rzucić okiem na repozytorium (być może wkręciłem coś przy dokonywaniu zmian) – hcito

+0

Jedną z popełnionych błędów jest to, że wywołanie glActiveTexture musi być przed 'batch.end'. Przegapiłem się widząc funky, w które się pakujesz. Poza tym widzę z twojego bitbucketu, że twój sprite źródłowy nie został zakodowany. Zaktualizuję odpowiedź. – Tenfour04

+0

Właśnie zauważyłem, że używałem getWidth zamiast getHeight do obliczania numeru palety, co jest błędne, ponieważ twoje palety są wierszami, a nie kolumnami. – Tenfour04