czwartek, 22 grudnia 2011

AndroidPlay - część 5: Przeźroczystość i przekształcenia modelowania

Do tej pory nauczyliśmy się rysować czworokąt w OpenGL ES na platformie Android. Chcemy stworzyć grę, a więc musimy wprowadzić trochę ruchu do naszej sceny. W dzisiejszej lekcji nauczymy się:
  1. rysować przeźroczyste bitmapy 
  2. wykonywać przekształcenia modelowania

1. Przeźroczyste bitmapy.

Załóżmy, że posiadamy odpowiedni plik z obrazkiem posiadającym kanał alpha w formacie .png. Rysowanie i tworzenie bitmapy przebiega dokładnie tak samo jak w lekcji o rysowaniu oteksturowanych czworokątów. Musimy tylko uruchomić łączenie kolorów i określić sposób ich łączenia w buforze koloru przy pomocy funkcji glBlendFunc(). Jej pierwszy parametr określa funkcję łączenia źródła, drugi celu. W praktyce jest ich kilka, natomiast nie używa się ich prawie wcale. Do uzyskania przeźroczystości używamy takich konfiguracji.
gl.glEnable(GL10.GL_BLEND);               // Włącz łączenie kolorów
gl.glBlendFunc(GL10.GL_SRC_ALPHA, 
               GL10.GL_ONE_MINUS_SRC_ALPHA); // Określ funkcje łączenia kolorów

U mnie powyższy kod dawał efekt taki, że zamiast przeźroczystości widziałem w jej miejscu czarny kolor. Rozwiązaniem jest użycie innego parametru w funkcji glBlendFunc - mianowicie GL_ONE.
gl.glEnable(GL10.GL_BLEND);               // Włącz łączenie kolorów
gl.glBlendFunc(GL10.GL_ONE, 
               GL10.GL_ONE_MINUS_SRC_ALPHA); // Określ funkcje łączenia kolorów;

Szczerze mówiąc nie chce mi się zagłębiać w specyfikację formatu PNG, wyczytałem na necie, że powyższe zmiany mają związek ze sposobem zapisu kanału alpha w pliku. Normalnie gdyby plik był zapisany zgodnie ze standardem, to pierwszy sposób z opcją GL_SRC_ALPHA powinien działać. Natomiast w życiu jak to w życiu, czasami ludzie mają standardy w ... gdzieś. Drugie ustawienia powinny załatwić sprawę.

2. Przekształcenia układu współrzędnych.

Okej, mamy już naszą kulkę na scenie. Teraz poznamy pokrótce przekształcenia układu współrzędnych: przesunięcie, obrót, skalowanie. Wprowadzą one trochę ruchu na naszą scenę.

glScalef(x, y, z)
glTranslatef(x, y, z)
glRotatef(angle, x, y, z)

           
Przesunięcie wykonujemy za pomocą funkcji glTranslatef(x, y, z). Jest to przesunięcie początku układu współrzędnych o wektor [x, y, z].

Obrót układu współrzędnych zapewnia nam funkcja glRotetef(angle, x, y, z), obrót dokonywany jest przeciwnie do ruchu wskazówek zegara (dla wartości dodatniej, jeśli podamy ujemną, to obracamy się zgodnie z ruchem wskazówek zegara). Kąt podajemy w stopniach.

Skalowanie finansuje funkcja glScalef(x, y, z). Odpowiednie wartości x, y, z jeśli są większe od 1 to powiększają tylokrotnie obiekt, natomiast jeśli są z zakresu od 0 do 1 to pomniejszają.

Te operacje przekształceń wykonywane są na aktualnej macierzy modelowania. Zauważmy, że jak np. przesuniemy układ współrzędnych, to tracimy informację o jego poprzednim położeniu. Ale możemy na to coś poradzić. Wykorzystamy dwie funkcje: glPushMatrix() oraz glPopMatrix() - glPushMatrix tworzy kopię aktualnej macierzy i odkłada ją na szczyt stosu macierzy. Dzięki temu po wykonaniu przekształceń możemy zdjąć (glPopMatrix) aktualną macierz ze stosu i powrócić do poprzedniego stanu.

Sprawdźmy to w praktyce.

Przypominam, że korzystam w dużej mierze z kodu lekcji 4, a więc środek naszego układu współrzędnych jest w lewym-dolnym rogu okna, my nie rysujemy naszego obiektu w środku układu współrzędnych, dlatego zmodyfikujemy odpowiednie wartości. Środek układu współrzędnych zostawimy w lewym-dolnym rogu okna, natomiast zrobimy tak, żeby rysowanie odbywało się w środku układu współrzędnych. Musimy zmienić współrzędne czworokąta tak, aby rysowany był wokół punktu (0.0, 0.0):
float quadsCoords[] = {
    // Pierwszy
    - 75.0f, - 75.0f, 0,
      75.0f, - 75.0f, 0,
      75.0f,   75.0f, 0,
    - 75.0f,   75.0f, 0,
};
W metodzie onDrawFrame organizujemy rysowanie tak:
public void onDrawFrame(GL10 gl) {
    // Wyczyść bufor głębi i koloru ustawioną wcześniej wartością
    GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT | GLES10.GL_DEPTH_BUFFER_BIT);
    
    // Ustaw kolor obiektu
    gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        
    if(angle < 360.0f)
        angle += 0.5f;
    else
        angle = angle - 360.0f;
        
    gl.glPushMatrix();
    // Przesun układ z lewego-dolnego rogu na środek okna.
    gl.glTranslatef((float)resolutionX/2.0f, (float)resolutionY/2.0f, 0.0f);
        gl.glRotatef(angle, 0.0f, 0.0f, 1.0f); // Obróć
        gl.glTranslatef(0.0f, 50.0f, 0.0f);    // Oddal od środka obrotu
        
        // Rysuj obiekt
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, quadsVB);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, quadsTCB);
        gl.glDrawElements(GL10.GL_TRIANGLES, 6, GL10.GL_UNSIGNED_SHORT, quadsIB);
    gl.glPopMatrix();
}
Powyższy przykład w łatwy sposób obrazuje zasadę działania omawianych przekształceń. Najpierw wyliczamy wartość kąta o jaki chcemy obrócić obiekt, bo przecież poprzez wywołania glPushMatrix i glPopMatrix po narysowaniu jednej klatki animacji przywracamy pierwotny układ współrzędnych sprzed obrotów. Przesuwamy układ na środek ekranu, obracamy go, przesuwamy wzdłuż osi y o 50 jednostek, rysujemy obiekt. Coś w powyższym kodzie jest nie do końca tak jak być powinno... widzisz to? Właśnie, modyfikacja kąta obrotu jest naiwna. Chodzi o to, że różnych urządzeniach będziemy mieli różne prędkości animacji. Zauważ, że w każdej klatce dodajemy pół stopnia, jeśli rysujemy 30 klatek na sekundę, to w ciągu tej sekundy uzyskamy 15 stopniowy obrót, natomiast na lepszym sprzęcie, gdzie wyciągniemy 120 klatek na sekundę w ciągu sekundy obrócimy się o 60 stopni. Rozwiązaniem jest uzależnienie zmian od czasu, zgodnie z tym, czego uczono nas w podstawówce: s = v * t ;). Dodajemy koło zmiennej angle odpowiednio:
private float angle = 0.0f;                          // kąt obrotu
private long startTime = SystemClock.uptimeMillis(); // czas rozpoczęcia klatki
private long deltaTime = 0L;                         // czas trwania klatki
Następnie modyfikujemy metodę onDrawFrame:
public void onDrawFrame(GL10 gl) {
    // Mierz czas klatki
    deltaTime = SystemClock.uptimeMillis() - startTime;
    startTime = SystemClock.uptimeMillis();

    // (...) 
        angle += 0.2f * deltaTime;
    // (...) 
}
Metoda SystemClock.uptimeMillis() zwraca czas, który upłynął od uruchomienia, nie licząc uśpienia. Na zakończenie wrzucam zrzut z telefonu, oczywiście ta kuleczka się porusza.



<< Poprzednia lekcja Następna lekcja >>
Materiały do pobrania:

Brak komentarzy:

Prześlij komentarz