pic
Pomimo iż technologia WebGL jest stosunkowo młoda, już dostępna jest bogata baza frameworków. W zasadzie jest ich tak dużo, że na początku trudno któryś wybrać. W ostatnim czasie miałem okazję spróbować kilku z nich i podzielę się z  wami moimi spostrzeżeniami. Ostrzegam, że pewne cechy bibliotek, o których piszę mogą się zmienić w czasie (np. uwagi dot. niepełnych dokumentacji). Dodatkowo oceniałem ich możliwości pod kątem swoich potrzeb, ktoś inny może mieć inne wrażenia. SceneJS Dynamicznie rozwijany Framework z API sceny opartym na formacie JSON. Projekt zapoczątkowany przez Lindsay Key, aktualnie rozwijany przez kilka osób. Framewo...
pic
Artykuł poświęcony jest eksportowaniu modeli (stworzonych w programie blender) do formatu JSON i ładowaniu ich w WebGL. Zrobimy również mały porządek z shaderami, które wrzucimy do osobnych plików, co daje lepszą czytelność i pozwala chociażby na kolorowanie składni, jeśli mamy edytor, który rozpoznaje GLSL. Lekcja ta jest modyfikacją poprzedniej lekcji. Efekt końcowy na rysunku poniżej lub na żywo tutaj. JSON (JavaScript Object Notation) jest czytelnym i intuicyjnym formatem, który ma swoje korzenie w JavaScript (więcej możecie doczytać na wikipedii). JavaScript posiada wbudowany obiekt globalny JSON, który potrafi parsować ten format. Poniżej mamy zaw...
pic
Ukończyliśmy właśnie prace nad grą "Vehicle Soccer". Jest to trójwymiarowa zręcznościówka polegająca na umiejętnym manewrowaniu jednym z pojazdów w celu łapania, uderzania oraz omijania różnych piłek na arenach. Grać można samemu przechodząc i odblokowując kolejne rundy (tryb single) lub w 2 osoby na jednej klawiaturze starając się zdobyć więcej punktów od przeciwnika (tryb multi). Gra została wystawiona do konkursu Mozilla Labs - Game ON więc możecie trzymać kciuki i jeśli wam się spodoba - oddać swój głos :-) Lista wszystkich gier zgłoszonych do konkursu znajduje się w galerii gier konkursu Game On. Aby zagrać wystarczy kliknąć w scr...
pic
źródło: Mozilla Konkurs skierowany jest do niezależnych twórców gier. Aby wziąć w nim udział trzeba stworzyć grę przeglądarkową wykorzystującą wyłącznie otwarte standardy webowe. Mile widziane są nowo powstające technologie takie jak WebGL, czy HTML5. Zasada jest jedna: gra musi być kompatybilna z przegladarką Firefox w najnowszej (testowej jeszcze) wersji 4.0. Gdy gra będzie gotowa, trzeba zarejestrować się na oficjalnej stronie wydarzenia: gaming.mozillalabs.com i podać nazwę, krótki opis oraz link do dzieła umieszczonego w sieci. Podczas oceniania gier brane będą pod uwagę następujące walory: Poprawność techniczna (engine, kod), wykorz...
pic
Dzisiaj dodamy trochę światła do naszej sceny. Lekcja ta bazuje na kodzie jednej z poprzednich: Porządek w kodzie i omówię jedynie różnice. Efekt końcowy dzisiejszej lekcji zobaczyć możecie na rysunku poniżej, lub na żywo tutaj. Możecie również pobrać kod dzisiejszej lekcji. Osoby zaznajomione z OpenGL mogą być trochę zawiedzone faktem, że WebGL (który bazuje na OpenGL ES) nie posiada automatycznego wsparcia dla światła (czytaj funkcji ustawiających pozycję, kolor itp. parametrów światła). Wszelkie obliczenia związane z oświetleniem musimy zrobic sami w shaderach. Przytoczona tu metoda jest tzw. "opartą na wierzchołkach". Znaczy to tyle, że...
mar

13

efekt_końcowy

Witam na kolejnej lekcji. Ostatnio nauczyliśmy się jak wyświetlić kwadrat z nałożoną teksturą, dzisiaj pójdziemy dalej. Tym razem stworzymy dwa w pełni trójwymiarowe obiekty – sześcian i piramidę, oraz wprowadzimy ruch na scenie.

Efekt końcowy będzie taki jak na rysunku poniżej, na żywo można go obejrzeć na tej stronie, jednak aby go zobaczyć trzeba mieć kompatybilną przeglądarkę. Jeśli takowej nie posiadacie, instrukcję instalacji znajdziecie w ustawieniach przeglądarki.

Lekcja druga - efekt końcowy

Kod aplikacji znajdziecie tutaj. Proponuję go pobrać i śledzić w trakcie czytania lekcji.

Kod aplikacji zawarty jest w pliku index.html. Tak jak ostatnio zaczniemy go omawiać od dołu. W sekcji body nic się nie zmienia. Jest tam umieszczone odpowiednio zwymiarowane płótno, oraz przy wystąpieniu zdarzenia onLoad, czyli po załadowaniu strony, wywoływana jest funkcja webGLStart. Przejdźmy do jej ciała. Widzimy 3 nowe linie kodu.

gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);

Odpowiadają one za uruchomienie bufora głębokości zwanego również z-buforem. Co takiego robi ten bufor? Krótko mówiąc mierzy odległości obiektów od kamery. Uruchomiony w powyższej konfiguracji sprawia, że obiekty znajdujące się bliżej obserwatora przesłaniają obiekty będące w dalszej odległości. Gdybyśmy go nie uruchomili, obiekty przesłaniały by się wzajemnie w kolejności rysowania, niezależnie od położenia (czasem również taki efekt bywa przydatny, ale opowiem o tym przy innej okazji). W każdym razie przy tworzeniu większości scen uruchomienie tego bufora jest standardowa procedurą.

Kolejna zmiana dotyczy wywołania następującej funkcji:

setInterval(tick, 15);

Tym razem cyklicznie wywoływaną funkcją nie jest drawScene, lecz funkcja tick. A cóż takiego ona robi? Spójrzmy

function tick() {
    timeTick();
    animate();
    drawScene();
}

W jej ciele zawarty jest zbiór wszystkich funkcji wykonywanych cyklicznie w głównej pętli programu. Tylko co umieszczamy w takiej pętli. Oczywiście funkcję drawScene, ponieważ scena rysowana jest w każdej klatce. Ale oprócz drawScene mamy również dwie nowe funkcje. Pierwsza – timeTick odpowiada za obliczanie czasu, jaki upłynął od wyświetlenia poprzedniej klatki. Czas ten potrzebny nam będzie do obliczeń fizycznych związanych z ruchem. Wszystkie te obliczenia wykonujemy w funkcji animate. Podsumowując funkcja tick jest sercem całej aplikacji, które uderza co 15 milisekund.

Przejdźmy teraz do ciała funkcji timeTick i zobaczmy, jak działa

var lastTime = 0;
var elapsed=0;
var elapsed_s=0;
function timeTick()
{
    var timeNow = (new Date).getTime();
    if (lastTime != 0)
    {
        elapsed = timeNow - lastTime;
        elapsed_s=elapsed/1000;
    }
    lastTime = timeNow;
}

Na wstępie deklarowane są trzy zmienne. Pierwsza przechowuje ostatni odczyty zegara systemowego, druga czas, jaki minął od ostatniego wywołania funkcji w milisekundach, ostania czyli elapsed_s –  ten sam czas wyrażony w sekundach.  Przejdźmy do ciała funkcji. Najpierw zmienna timeNow pobiera aktualną wartość zegara systemowego. Jest to czas, jaki minął od 1 stycznia 1970 roku wyrażony w milisekundach. Jak możemy sobie wyobrazić, jest to wartość bardzo duża, ale dla nas istotna jest różnica między nią, a wartością odczytaną w poprzedniej klatce. Różnica ta przekazana jest do zmiennej elapsed. Dodatkowo obliczamy wartość zmiennej elapsed_s, która, jak już powiedziałem, przechowuje ten sam czas wyrażony w sekundach. Tworzymy ją, ponieważ w większości wzorów fizycznych wykorzystuje się czas mierzony w sekundach, a lepiej wykonać przekształcenie raz i wykorzystać we wszystkich wzorach, niż przy każdym obliczeniu fizycznym dzielić zmienną elapsed przez 1000. Pomaga to w optymalizacji skryptu. Na koniec czas obecny przekazujemy do zmiennej lastTime , która będzie wykorzystana w ten sam sposób w kolejnej klatce.

Zanim przejdziemy do opisu działania funkcji animate, omówię funkcje, które umożliwiają poruszanie obiektów na scenie. Funkcje te nie są częścią samego WebGL’a, jednak są praktycznymi narzędziami ułatwiającymi wykonywanie operacji na macierzy modelowania. Odpowiadają znanym z OpenGL’a funkcjom: glLoadIdentity,  glPushMatrix, glPopMatrix, glTranslate oraz glRotate. Dla wyjaśnienia macierz modelowania, czy też macierz przekształceń należy traktować jako reprezentacja bieżącego położenia układu współrzędnych.

loadIdentity()

Funkcja ta resetuje macierz modelowania, czyli ustawia bieżące położenie układu w punkcie 0.

mvPushMatrix()

Odkłada bieżącą macierz modelowania na stos.

mvPopMatrix()

Zdejmuje macierz modelowania ze stosu i ustawia jako bieżącą.

mvTranslate( [_x , _y, _z] )

Przesuwa bieżący układ współrzędnych o wartości _x, _y, _z odpowiednio wzdłuż osi x, y, z. Należy zwrócić uwagę, iż argumentem tej funkcji jest trójelementowa tablica.

mvRotate( angle,[bit_x, bit_ y, bit_ z]);

Obraca układ współrzędnych o kąt angle podawany w stopniach dookoła osi, której bit ustawiony jest na 1. Pierwszym argumentem jest tutaj kąt obrotu, natomiast drugim trójelementowa tablica z bitami osi. Przykładowo jeśli chcemy obrócić obiekt o 90 stopni dookoła osi x, wywołujemy mvRotate(90,[1,0,0]). Bity osi y oraz z ustawione są na 0, więc obiekt obróci się tylko dookoła osi x. Można dokonywać obrotów dookoła kilku osi jednocześnie.

mvRotateRad(angle, [bit_x, bit_y, bit_z]);

W grafice najlepiej jest operować na kątach mierzonych w radianach, ponieważ uwalnia to nas od przymusu przeliczania stopni na radiany przy wykonywaniu obliczeń z użyciem funkcji trygonometrycznych. Tym samym przyspiesza to znacznie działanie programu. Dla tego do dyspozycji mamy funkcję mvRotateRad, która w odróżnieniu od funkcji mvRotate, jako pierwszy parametr przyjmuje kąt mierzony w radianach.

Są to wszystkie funkcje potrzebne nam do poruszania obiektami. Przejdźmy więc do funkcji animate.

var X_rot = 0;
var Y_rot = 0;
var Z_rot = 0;
var Z_position  = 0;
var Direction_forward = false;

Na początku deklarujemy zmienne przechowujące położenie obiektów. Pierwsze trzy reprezentowały będą obrót, czwarta położenie, natomiast ostatnia kierunek ruchu piramidy.

function animate() {
X_rot += 20 * elapsed_s;
Y_rot += 40 * elapsed_s;
Z_rot += 60 * elapsed_s;

Zmienne reprezentujące obrót wokół danej osi będą zwiększały wartości z różną szybkością. W wyliczeniach wykorzystujemy omówioną wcześniej zmienną elapsed_s.

    if(Direction_forward)
    {
        Z_position += 5*elapsed_s;
        if(Z_position > 3)
            Direction_forward = !Direction_forward;
    }

Jeśli zmienna Direction_forward ustawiona jest na true,  Z_position zwiększa swoją wartość o 5*elapsed_s w każdej klatce animacji aż do przekroczenia wartości 3. Następnie wartość zmiennej Direction_forward ustawiana jest na przeciwną i sytuacja się odwraca.

    else
    {
        Z_position -= 5*elapsed_s;
        if(Z_position < -3)
            Direction_forward = !Direction_forward;
    }
}

Wartość zmiennej Z_position maleje aż do osiągnięcia wartości poniżej -3, po czym zmienna Direction_forward ustawiana jest z powrotem na true. Dzięki tej prostej operacji piramida będzie wykonywała jednostajny, liniowy ruch do przodu i z powrotem w zakresie 6 jednostek.

Wszystkie wyliczone zmienne związane z ruchem wykorzystywane są w funkcji drawScene, ale zanim do niej przejdziemy, przyjrzyjmy się buforom przechowującym dane obiektów.

//cube buffers
var cubeVertexPositionBuffer;
var cubeVertexTextureCoordBuffer;
var cubeVertexIndexBuffer;
//pyramid buffers
var pyramidVertexPositionBuffer;
var pyramidVertexTextureCoordBuffer;
var pyramidVertexIndexBuffer;

Dla każdego obiektu tworzymy tak jak poprzednio 3 bufory zawierające dane określające położenie wierzchołków, koordynaty tekstur oraz indeksy.

function initBuffers() {
    //set cube buffers
    cubeVertexPositionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
    vertices = [
      // front face
      -1.0, -1.0,  1.0,
       1.0, -1.0,  1.0,
       1.0,  1.0,  1.0,
      -1.0,  1.0,  1.0,

      // back face
      -1.0, -1.0, -1.0,
      -1.0,  1.0, -1.0,
       1.0,  1.0, -1.0,
       1.0, -1.0, -1.0,

      // top face
      -1.0,  1.0, -1.0,
      -1.0,  1.0,  1.0,
       1.0,  1.0,  1.0,
       1.0,  1.0, -1.0,

      // bottom face
      -1.0, -1.0, -1.0,
       1.0, -1.0, -1.0,
       1.0, -1.0,  1.0,
      -1.0, -1.0,  1.0,

      // right face
       1.0, -1.0, -1.0,
       1.0,  1.0, -1.0,
       1.0,  1.0,  1.0,
       1.0, -1.0,  1.0,

      // left face
      -1.0, -1.0, -1.0,
      -1.0, -1.0,  1.0,
      -1.0,  1.0,  1.0,
      -1.0,  1.0, -1.0,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertices), gl.STATIC_DRAW);
    cubeVertexPositionBuffer.itemSize = 3;
    cubeVertexPositionBuffer.numItems = 24;

Na początku tworzymy bufor ze współrzędnymi wierzchołków sześcianu.

    cubeVertexTextureCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
    var textureCoords = [
      // front face
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,

      // back face
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      0.0, 0.0,

      // top face
      0.0, 1.0,
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,

      // bottom face
      1.0, 1.0,
      0.0, 1.0,
      0.0, 0.0,
      1.0, 0.0,

      // right face
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      0.0, 0.0,

      // left face
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(textureCoords), gl.STATIC_DRAW);
    cubeVertexTextureCoordBuffer.itemSize = 2;
    cubeVertexTextureCoordBuffer.numItems = 24;

Następnie tworzymy bufor z koordynatami tekstur.

    cubeVertexIndexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
    var cubeVertexIndices = [
      0, 1, 2,      0, 2, 3,    // front face
      4, 5, 6,      4, 6, 7,    // back face
      8, 9, 10,     8, 10, 11,  // top face
      12, 13, 14,   12, 14, 15, // bottom face
      16, 17, 18,   16, 18, 19, // right face
      20, 21, 22,   20, 22, 23  // left face
    ]
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(cubeVertexIndices), gl.STATIC_DRAW);
    cubeVertexIndexBuffer.itemSize = 1;
    cubeVertexIndexBuffer.numItems = 36;

Oraz bufor z indeksami wierzchołków sześcianu.

Teraz bufory piramidy:

    //set pyramid buffers
    pyramidVertexPositionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer);
    vertices = [
        // front face
         0.0,  1.0,  0.0,
        -1.0, -1.0,  1.0,
         1.0, -1.0,  1.0,
        // right face
         0.0,  1.0,  0.0,
         1.0, -1.0,  1.0,
         1.0, -1.0, -1.0,
        // back face
         0.0,  1.0,  0.0,
         1.0, -1.0, -1.0,
        -1.0, -1.0, -1.0,
        // left face
         0.0,  1.0,  0.0,
        -1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0

    ];
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertices), gl.STATIC_DRAW);
    pyramidVertexPositionBuffer.itemSize = 3;
    pyramidVertexPositionBuffer.numItems = 12;

Tak samo jak poprzednio bufor z położeniem wierzchołków

    pyramidVertexTextureCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexTextureCoordBuffer);
    var textureCoords = [
      // front face
      0.5, 1.0,
      0.0, 0.0,
      1.0, 0.0,

     // right face
      0.5, 1.0,
      0.0, 0.0,
      1.0, 0.0,

      // back face
      0.5, 1.0,
      0.0, 0.0,
      1.0, 0.0,

      // left face
      0.5, 1.0,
      0.0, 0.0,
      1.0, 0.0
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(textureCoords), gl.STATIC_DRAW);
    pyramidVertexTextureCoordBuffer.itemSize = 2;
    pyramidVertexTextureCoordBuffer.numItems = 12;

Bufor z koordynatami

    pyramidVertexIndexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, pyramidVertexIndexBuffer);
    var pyramidVertexIndices = [
      0, 1, 2,	// front face
      3, 4, 5,	// right face
      6, 7, 8,	// back face
      9, 10, 11	// left face
    ]
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(pyramidVertexIndices), gl.STATIC_DRAW);
    pyramidVertexIndexBuffer.itemSize = 1;
    pyramidVertexIndexBuffer.numItems = 12;
  }

I z indexami wierzchołków.

Poza zwiększoną ilością buforów nasza dzisiejsza aplikacja ma również jedną teksturę więcej, ponieważ na każdy z obiektów nakładamy inny obrazek. Tekstury wczytywane są w funkcji initTextures.

var logo1Texture;
var logo2Texture;

Najpierw delklarujemy zmienne przechowujące obiekty naszych tekstur

function initTextures() {
  logo1Texture = gl.createTexture();
  logo1Texture.image = new Image();
  logo1Texture.image.onload = function() {
    handleLoadedTexture(logo1Texture)
  }
  logo1Texture.image.src = "logo1.jpg";

  logo2Texture = gl.createTexture();
  logo2Texture.image = new Image();
  logo2Texture.image.onload = function() {
    handleLoadedTexture(logo2Texture)
  }
  logo2Texture.image.src = "logo2.jpg";
}

W ciele funkcji wczytujemy najpierw pierwszą, następnie drugą teksturę z plików logo1.jpg oraz logo2.jpg.

Po przygotowaniu tekstur, buforów, oraz obliczeń fizycznych, możemy przejść do ciała funkcji drawScene. Omówimy ją dokładnie krok po kroku, tak żeby wszystko było dla nas zrozumiałe.

function drawScene() {
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
  perspective(45, 1.333, 0.1, 100.0);
  loadIdentity();

Na początku czyścimy bufory koloru oraz głębi, ustawiamy właściwości okna widoku oraz resetujemy macierz modelowania. Operacje te omówione zostały na poprzedniej lekcji, jedyną różnicą jest tutaj czyszczenie bufora głębi. Ostatnio tego nie robiliśmy, gdyż bufor głębi nie był uruchamiany.

mvTranslate([0.0, 0.0, -10.0]);

Przesuwamy bieżący układ o 10 jednostek do przodu, aby obiekty rysowane były przed nami.

mvPushMatrix();

Odkładamy macierz na stos, dzięki czemu będziemy mogli w prosty sposób wrócić do tego miejsca.

mvTranslate([-3.0, 0.0, 0.0]);

Przesuwamy bieżący układ o 3 jednostki w lewo, ponieważ sześcian rysowany będzie po lewej stronie sceny.

mvRotate(X_rot, [1, 0, 0]);
mvRotate(Y_rot, [0, 1, 0]);
mvRotate(Z_rot, [0, 0, 1]);

Wykonujemy obroty bieżącego układu zgodnie z wyliczonymi wcześniej wartościami zmiennych: X_rot, Y_rot, Z_rot.

gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, logo1Texture);
gl.uniform1i(shaderProgram.samplerUniform, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);

Rysujemy sześcian zgodnie z aktualnym położeniem układu na podstawie zawartości buforów z danymi obiektu.

mvPopMatrix();

Zdejmujemy macierz ze stosu. Tym samym cofamy położenie układu do miejsca, w którym ostatnio odłożyliśmy ją na stos. Wszystkie następne przesunięcia oraz obroty będą się odnosiły do tamtego położenia, czyli w naszym przypadku do punktu położonego 10 jednostek przed nami.

mvTranslate([2.0, 0.0, Z_position]);

Przesuwamy bieżący układ o 2 jednostki w prawo, w miejsce, gdzie położona będzie piramida oraz o zmienną Z_position wzdłuż osi Z. Jej wartość, wyliczana wcześniej w funkcji animate, będzie powodowała oddalanie oraz przybliżanie się do nas piramidy w zakresie 6 jednostek.

mvRotate(Y_rot, [0, 1, 0]);

Piramida, w odróżnieniu od sześcianu, będzie się obracała tylko dookoła osi y.

  gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer);
  gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, pyramidVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
  gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexTextureCoordBuffer);
  gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, pyramidVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, logo2Texture);
  gl.uniform1i(shaderProgram.samplerUniform, 0);
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, pyramidVertexIndexBuffer);
setMatrixUniforms();
  gl.drawElements(gl.TRIANGLES, pyramidVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
}

Rysujemy piramidę w odpowiednim miejscu na podstawie zawartości jej buforów.

Funkcja drawScene zakończona, pętla programu również. Uruchamiamy aplikację i voila! Widzimy wirujący sześcian, oraz poruszającą się piramidę, więc wszystko działa jak należy.

Nauczyliśmy się dzisiaj, jak wykonać dowolny ruch na scenie i gdzie najlepiej obliczać wartości zmiennych związanych z ruchem. Jest to doskonała baza do dalszych eksperymentów, do których gorąco zachęcam. Kod gotowej aplikacji możecie pobrać tutaj.

Na dzisiaj to wszystko. Na kolejnej lekcji stworzymy bardziej kompleksowe funkcje do wczytywania tekstur, oraz wyświetlania obiektów. Pozwoli nam to uporządkować kod oraz ułatwi wyświetlanie wielu różnych obiektów.

autor: RecRoot

3 Comments

  1. gliniak pisze:

    Odnośnie obrotu, to chyba jest błąd – drugi parametr (tablica) definiuje wektor, który jest (jedną) osią obrotu. Mówienie o tej tablicy jako tablicy bitów osi jest błędem

  2. gliniak pisze:

    Przepraszam, że nie w 1 poście: wydaje mi się, że przesadziłeś z ilością wierzchołków dla sześcianu i piramidy. W sześcianie wierzchołków wystarczy 8 i 5 dla piramidy. O to właśnie chodzi w indeksach, że możesz podawać indeks wierzchołka kilkakrotnie (żeby nie mieć wielokrotnie tego samego wierzchołka).

  3. Kris pisze:

    Pierwszy wpis:
    Widzę, że posiadasz sporą wiedzę z OpenGL’a. Tam faktycznie jako parametr podawany jest wektor osi obrotu. Jednak funkcja mvRotate różni się od glRotate. Zaimplementowana jest w taki sposób, że obiekt albo obraca się wokół danej osi albo nie, więc określenie ‘tablica bitów osi’ uważam za poprawne. Spróbuj wpisać inne wartości niż 0/1. Zobaczysz, że efekt będzie się różnił od tego z OpenGL’a (przy wpisaniu 2 obiekt nie będzie obracał się 2 X szybciej).

    Co do drugiego wpisu:
    Masz rację. W indeksach chodzi właśnie o to, aby wykorzystywać jeden wierzchołek kilka razy, co wpływa pozytywnie na optymalizację. Jednak w tym tutorialu optymalizacja nie jest najważniejsza (scena jest stosunkowo prosta). Zdecydowałem się na taki sposób przechowywania danych obiektu, aby kod był bardziej czytelny i łatwiejszy do zrozumienia. Nie każdy zetknął się z indeksowaniem, a myślę, że w ten sposób łatwiej będzie zrozumieć ten mechanizm.

Comment

Copyright © 2012 3dgames - Kolejny blog oparty na WordPressie.
Website powered by WordPress and Emescale wordpress theme designed by TopTut.com & TopWPThemes.com.
Visit WebHostingFan.com for the latest news on web hosting and cms review.