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

18

efekt_końcowy

Do tej pory aby wyświetlić obiekt musieliśmy stworzyć odpowiednie bufory, wypełnić je danymi o wierzchołkach, za pomocą szeregu funkcji wczytać teksturę z pliku i dopiero mogliśmy przejść do skomplikowanego rysowania w funkcji drawScene. Generalnie cały kod zajmował dużo miejsca i był niepraktyczny. Dzisiaj napiszemy klasę Model, dzięki której aby zainicjować oraz narysować dowolny obiekt na scenie wystarczą nam zaledwie dwie linijki kodu. Zapraszam do lektury!

Poza klasą Model stworzymy również prostą scenę z wieloma obiektami aby pokazać praktyczne użycie nowych narzędzi. Efekt całej aplikacji zobaczyć można na rysunku poniżej, na żywo można go obejrzeć na tej stronie.

przykładowe użycie klasy Model

Kod aplikacji znajdziecie tutaj. Proponuję go pobrać i śledzić w trakcie czytania lekcji. Możecie również pobrać samą klasę Model i używać jej w swoich aplikacjach.

Poziom trudności dzisiajszej lekcji jest dość zaawansowany, jednak dokładne zrozumienie działania każdej funkcji nie jest konieczne. Najważniejsze abyśmy potrafili się nimi swobodnie posługiwać, ponieważ będą wykorzystywane przy tworzeniu większości aplikacji. Zacznijmy więc.

Główny kod znajduje się oczywiście w pliku index.html, jednak narzędzia, które dzisiaj stworzymy, umieścimy w osobnym pliku. Nazwiemy go WGLtools.js. W pliku głównym umieszczamy adres do skryptu zewnetrznego:

<script type="application/x-javascript" src="WGLtools.js"></script>

Otwieramy plik i tworzymy klasę Model

Staramy się pisać obiektowo na tyle, na ile jest to możliwe w języku javascript. Choć jest on językiem obiektowym, nie pozwala na typowe programowanie oparte na hierarchicznym układzie klas. Mimo to możemy pisać funkcje, które będziemy traktowali jak klasy. Tworzyli będziemy później obiekty takich funkcji i pracowali na nich jak na obiektach typowych klas. Więcej o metodach programowania obiektowego w języku javascript powiemy sobie na jednej z kolejnych lekcji.

function Model()
{
  this.Texture = null;
  this.Vertices = null;
  this.Tex_coords = null;
  this.Indices = null;

  this.init = Model_init;
  this.display = Model_display;
  this.load_texture = Model_load_texture;
}

Nasza klasa Model posiada 4 zmienne oraz 3 metody. Pierwsza zmienna – Texture będzie przechowywała obiekt tekstury. Druga zmienna – Vertices będzie zawierała bufor ze współrzędnymi wierzchołków. Kolejna zmienna – Tex_coords – bufor z koordynatami tekstur. Ostatnia zmienna – Indices – bufor z indeksami wierzchołków. Dodatkowo klasa jest wyposażona w 3 metody: init, display oraz load_texture.

Zacznijmy od ostatniej w kolejności – metody load_texture. Jak można się domyślić służy ona do wczytania tekstury z pliku. Spójrzmy na nią:

function Model_load_texture(adres,mag_filter,min_filter)
{

Funkcja przyjmuje 3 parametry: adres pliku z grafiką, sposób filtrowania tekstury w przypadku jej powiększania przy nakładaniu na obiekt, oraz sposób filtrowania w przypadku pomniejszania przy nakładaniu.

  var Texture = gl.createTexture();
  var tempImage;
  tempImage = new Image();

Tworzymy obiekt tekstury oraz obiekt obrazu.

  tempImage.onload = function()
{

Definiujemy funkcję dla zdarzenia onload obiektu obrazu. Wywołana ona będzie, gdy cały obraz załaduje się z pliku

  gl.enable(gl.TEXTURE_2D);
  gl.bindTexture(gl.TEXTURE_2D, Texture);
  gl.texImage2D(gl.TEXTURE_2D, 0, tempImage);

Bindujemy nasz obiekt tekstury, czyli ustawiamy go jako bieżący oraz określamy źródło grafiki dla tekstury, w tym przypadku obiekt tempImage.

Dochodzimy do filtrowania. Zanim omówię działanie kolejnych operacji, powiem krótko na czym polega owo filtrowanie. Gdy nakładamy teksturę na obiekt mogą się zdarzyć dwa przypadki: obiekt może być mniejszy od rozdzielczyści tekstury lub większy od niej. Filtrowanie określa sposób doboru teksela (piksela tekstury) dla każdego piksela teksturowanego obiektu. Definiujemy je za pomocą funkcji gl.texParameteri, która przyjmuje 3 parametry. Pierwszy określa rodzaj stosowanych tekstur – w naszym przypadku gl.TEXTURE_2D, ponieważ używamy tekstur dwuwymiarowych. Drugi parametr określa czy definiujemy filtrowanie dla powiększania czy dla pomniejszania. W pierwszym przypadku przyjmuje on wartość gl.TEXTURE_MAG_FILTER, natomiast w drugim gl.TEXTURE_MIN_FILTER.

Jeśli obiekt jest większy od rozdzielczości tekstury, trzeba jakoś wypełnić brakujące piksele. Możliwości są dwie. Można pobrać wartość piksela tekstury położonego najbliżej pustego piksela na obiekcie lub zastosować liniową interpolację najbliższych pikseli tekstury. Metoda najbliższego piksela jest szybsza, ale mniej efektowna. Aby ją włączyć, jako ostatni parametr funkcji gl.texParameteri podajemy gl.NEAREST. Liniowa interpolacja wymaga większej ilości obliczeń, ale efekt jest bardziej przyjazny dla oka. Aktywujemy ją za pomocą parametru gl.LINEAR.

W przypadku, gdy rozdzielczość tekstury jest większa od obiektu sytuacja wygląda nieco inaczej. Pikseli obiektu jest mniej niż tekseli, więc trzeba jakoś określić jaki teksel być nałożony na dany piksel obiektu. I tak jak poprzednio możemy wybrać pierwszy najbliższy teksel podając parametr gl.NEAREST, lub zastosować interpolację najbliższych tekseli podając gl.LINEAR.

Jednak niezależnie od tego, którą opcje wybierzemy, jeśli obiekt będzie dużo mniejszy od tekstury, będą się na nim pojawiały dziwne wzorki (szczególnie podczas przybliżania i oddalania). Wynika to z wykorzystania tylko części tekseli przy teksturowaniu obiektu. Aby temu zapobiec stosuje się mipmapy.

Mipmapy są powszechnie znanym i stosowanym mechanizmem pozwalającym kontrolować poziom szczegółowości tekstur. Polega on na stworzeniu szeregu tekstur o coraz to mniejszych rozdzielczościach od nominalnej, aż do uzyskania tekstury składającej się z jednego piksela. Następnie mechanizm ten dobiera odpowiednią mipmapę zależnie od aktualnej rozdzielczości obiektu. Jeśli obiekt znajduje się dalej od obserwatora, nałożona będzie mipmapa o mniejszej szczegółowości, jeśli bliżej – o większej.

Decydując się na stosowanie mipmap mamy do wyboru cztery rodzaje filtrowania:

  • gl.NEAREST_MIPMAP_NEAREST – Wybiera mipmapę o rozdzielczości najbardziej zbliżonej do wielkości obiektu i stosuje filtr gl.NEAREST przy teksturowaniu pojedynczych pikseli.
  • gl.NEAREST_MIPMAP_LINEAR – Wybiera mipmapę o rozdzielczości najbardziej zbliżonej do wielkości obiektu i stosuje filtr gl.LINEAR przy teksturowaniu pojedynczych pikseli.
  • gl.LINEAR_MIPMAP_NEAREST – Stosuje liniową interpolację dwóch mipmap o najbardziej złożonej rozdzielczości i stosuje filtr gl.NEAREST przy teksturowaniu pojedynczych pikseli.
  • gl.LINEAR_MIPMAP_LINEAR – Stosuje liniową interpolację dwóch mipmap o najbardziej złożonej rozdzielczości i stosuje filtr gl.LINEAR przy teksturowaniu pojedynczych pikseli.

Przy wyborze filtra trzeba znaleźć równowagę pomiędzy wydajnością a efektem. Filtry stosujące metodę interpolacji wartości są ładniejsze, ale bardziej obciążają sprzęt.

Ok. wróćmy do funkcji ładującej tekstury. Poza adresem pliku przyjmuje ona 2 parametry określające właśnie rodzaj filtrowania. Pierwszy dotyczy opcji gl.TEXTURE_MAG_FILTER, a drugi gl.TEXTURE_MIN_FILTER.

if( !mag_filter )
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

Jeśli nie podamy parametru mag_filter, lub podamy zero jako jego wartość, dla opcji gl.TEXTURE_MAG_FILTER domyślnie zastosowany będzie filtr gl.LINEAR.

else
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, mag_filter);

W przeciwnym wypadku wartość zmiennej zostanie przekazana do funkcji gl.texParameteri.

if( !min_filter )
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

Podobnie jest w przypadku opcji gl.TEXTURE_MIN_FILTER. Jeśli nie podamy parametru min_filter lub podamy 0, domyślnie zastosowany będzie filtr gl.LINEAR.

else
{
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, min_filter);

Jeśli natomiast podamy konkretną wartość, zostanie ona przekazana do funkcji gl.texParameteri.

  if( min_filter==gl.LINEAR_MIPMAP_NEAREST || min_filter==gl.LINEAR_MIPMAP_LINEAR || min_filter==gl.NEAREST_MIPMAP_NEAREST || min_filter==gl.NEAREST_MIPMAP_LINEAR )
    gl.generateMipmap(gl.TEXTURE_2D);
}

Dodatkowo jeśli wybrany będzie jeden z czterech filtrów wykorzystujący mechanizm mipmap, wywołujemy funkcję gl.generateMipmap, która wygeneruje szereg mipmap o coraz to mniejszych rozdzielczościach.

  gl.bindTexture(gl.TEXTURE_2D, null);
}

Dla bezpieczeństwa informujemy WebGL, że żadna tekstura nie jest ustawiona jako aktywna.

  tempImage.src = adres;
  this.Texture = Texture;
}

I na koniec ładujemy grafikę z pliku oraz przekazujemy obiekt tekstury do zmiennej Texture naszej klasy Model.

Omówimy teraz kolejną metodę – init. Podpięta jest pod nią funkcja Model_init.

function Model_init(tex,mag_filter,min_filter,tab1,tab2,tab3)
{

Funkcja przyjmuje 5 parametrów charakteryzujących obiekt:

  • tex – adres pliku z teksturą, możemy tutaj podać 0 lub null, wtedy obiekt nie będzie oteksturowany.
  • mag_filter – rodzaj filtra dla opcji gl.TEXTURE_MAG_FILTER, jeśli podamy 0 ustawiony zostanie domyślnie filtr gl.LINEAR
  • min_filter – rodzaj filtra dla opcji gl.TEXTURE_MIN_FILTER, jeśli podamy 0 ustawiony zostanie domyślnie filtr gl.LINEAR
  • tab1 – tablica ze współrzędnymi wierzchołków obiektu, parametr jest opcjonalny, możemy podać 0 lub nie podać go w ogóle, wtedy bufor wierzchołków nie zostanie utworzony
  • tab2 – tablica z koordynatami tekstur, parametr jest opcjonalny, możemy podać 0 lub nie podać go w ogóle, wtedy bufor z koordynatami nie zostanie utworzony
  • tab3 – tablica z indeksami wierzchołków, parametr jest opcjonalny, możemy podać 0, lub nie podać go w ogóle, wtedy poczynając od zera każda kolejna trójka wierzchołków opisanych w talicach tab1 oraz tab2 traktowana będzie jako osobny trójkąt.

Przejdźmy do działania funkcji.

if( tex )
  this.load_texture(tex,mag_filter,min_filter);

Najpierw wczytywana jest tekstura za pomocą opisanej wcześniej metody load_texture.

if( tab1 )
{
  this.Vertices = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, this.Vertices);
  gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(tab1), gl.STATIC_DRAW);
  gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
  this.Vertices.itemSize = 3;
  this.Vertices.numItems = tab1.length/3;
}

Jeśli parametr tab1 zawiera tablicę, na jej podstawie tworzony jest bufor ze współrzędnymi wierzchołków.

if( tab2 )
{
  this.Tex_coords = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, this.Tex_coords);
  gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(tab2), gl.STATIC_DRAW);
  gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);
  this.Tex_coords.itemSize = 2;
  this.Tex_coords.numItems = tab2.length/2;
}

Jeśli parametr tab2 zawiera tablicę, na jej podstawie tworzony jest bufor z koordynatami tekstur.

  if( tab3 )
  {
    this.Indices = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.Indices);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(tab3), gl.STATIC_DRAW);
    this.Indices.itemSize = 1;
    this.Indices.numItems = tab3.length;
  }
}

I na koniec jeśli parametr tab3 zawiera tablicę, na jej podstawie tworzony jest bufor z indeksami wierzchołków.

Jak widać metoda init służy do inicjalizacji obiektu. Za jej pośrednictwem wczytywana jest tekstura oraz tworzone są bufory z danymi wierzchołków. Po jej odpowiednim wywołaniu obiekt jest gotowy do narysowania. Będziemy go rysowali za pomocą metody display, pod którą podpięta jest funkcja Model_display. Spójrzmy jak ona działa.

function Model_display(mode)
{

Zmienna mode jest opcjonalnym parametrem funkcji określającym metodę wyświetlania prymitywów. Do wyboru mamy następujące prymitywy: gl.POINTS, gl.LINES, gl.LINE_LOOP, gl.LINE_STRIP, gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN. Jeśli tego parametru nie podamy, obiekt rysowany będzie domyślnie za pomocą pojedynczych trójkątów, czyli gl.TRIANGLES.

gl.bindBuffer(gl.ARRAY_BUFFER, this.Vertices);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);

Najpierw bindujemy bufor zawierający współrzędne wierzchołków,

gl.bindBuffer(gl.ARRAY_BUFFER, this.Tex_coords);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);

następnie bufor z koordynatami tekstury

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.Texture);
gl.uniform1f(gl.getUniformLocation(shaderProgram, "uSampler"), 0);

oraz samą teksturę. Przechodzimy do rysowania obiektu. Metoda rysowania zależna jest od tego, czy stworzyliśmy bufor z indexami wierzchołków poprzez przekazanie do metody init odpowiedniej tablicy, czy też nie.

Jeżeli nie stworzyliśmy bufora z indeksami, bufory z koordynatami tekstur oraz ze współrzędnymi wierzchołków muszą zawierać osobno opis wierzchołków każdego trójkąta. Jeśli przykładowo naszym obiektem jest kwadrat, a podstawowym prymitywem trójkąt, bufory muszą zawierać opis sześciu, a nie czterech wierzchołków, ponieważ składa się on z dwóch trójkątów.

if( !this.Indices )
{
  setMatrixUniforms();
  if( !mode )
    gl.drawArrays(gl.TRIANGLES, 0, this.Vertices.numItems);
  else
    gl.drawArrays(mode, 0, this.Vertices.numItems);
}

Obiekt rysowany jest za pomocą funkcji gl.drawArrays na podstawie zawartości aktywnych buforów za pomocą prymitywów podanych jako pierwszy jej parametr. Jeżeli tego parametru nie podaliśmy, obiekt rysowany jest za pomocą trójkatów, czyli za pomocą gl.TRIANGLES.

Jeśli stworzyliśmy bufor z indeksami postępujemy nieco inaczej.

  else
  {
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.Indices);
    setMatrixUniforms();
    if( !mode )
      gl.drawElements(gl.TRIANGLES, this.Indices.numItems, gl.UNSIGNED_SHORT, 0);
    else
      gl.drawElements(mode, this.Indices.numItems, gl.UNSIGNED_SHORT, 0);
  }
}

Na początku ustawiamy go jako aktywny. Obiekty w tym przypadku rysowane są za pomocą funkcji gl.drawElements, która w odróżnieniu od funkcji gl.drawArrays, pozwala na dowolny porządek przetwarzania wierzchołków, określony właśnie za pomocą indeksów. I tak jak ostatnio jeśli podaliśmy parametr określający rodzaj prymitywów, to jest on przekazywany do funkcji gl.drawElements, natomiast jeśli nie – obiekt domyślnie rysowany jest za pomocą metody gl.TRIANGLES.

Funkcja do wyświetlania obiektu zakończona, cała klasa też już jest gotowa. Jej kod jest na tyle elastyczny, że bez problemu będziemy mogli wprowadzać dalsze modyfikacje w zależności od potrzeb.

Przykładowy program

Aby pokazać działanie klasy Model stworzymy prosty program, który będzie wyświetlał na płótnie 40 obracających się sześcianów. Ich położenie na osi x oraz y będzie wybierane losowo za pomocą funkcji random. Dodatkowo nałożymy na nie 10 różnych tekstur.

Szkielet aplikacji oparty jest na kodzie tworzonym na poprzednich lekcjach. Omówimy tylko funkcje związane z zastosowaniem klasy Model.

function c_Cube()
{
  //position
  this.Position_x = 0;
  this.Position_y = 0;
  //rot
  this.Rot_x = 0;
  this.Rot_y = 0;
  this.Rot_z = 0;
  //rot speed
  this.X_speed;
  this.Y_speed;
  this.Z_speed
}

Na początku tworzymy prostą klasę c_Cube, która będzie przechowywała dane naszych obiektów związane z położeniem, kątem oraz prędkością obrotu.

c_Cube.prototype = new Model;

Podpinamy pod jej prototyp naszą klasę Model, dzięki czemu dziedziczy po niej wszystkie metody oraz zmienne.

var Objects = new Array();
var Objects_amount = 40;

Tworzymy tablicę, która będzie przechowywała obiekty oraz zmienną Objects_amount określającą ich ilość. Dla przykładu stworzymy 40 sześcianów.

Wykorzystamy dane wierzchołków sześcianu z poprzedniej lekcji i umieścimy je w tablicach Tab1, Tab2 oraz Tab3.

Dodatkowo przygotowujemy 10 tekstur. Nazywamy je kolejno od 0.jpg do 9.jpg.

W funkcji WebGLStart tworzymy następującą pętlę wykonującą operacje inicjujące każdy obiekt

for(var i=0; i<Objects_amount; i++)
{
  var tex_num = i % 10;

Na początku deklarujemy zmienną tex_num, określającą numer pliku, z którego załadujemy teksturę. Nadajemy wartość z zakresy 0 – 9.

Objects[i] = new c_Cube();

Tworzymy obiekt klasy c_Cube;

Objects[i].init(tex_num+".jpg",gl.LINEAR,gl.LINEAR_MIPMAP_LINEAR,Tab1,Tab2,Tab3);

Wywołujemy dla niego metodę init z następującymi parametrami: zmienna tex_num z rozszerzeniem JPG jako adres pliku z teksturą, filtr gl.LINEAR dla gl.TEXTURE_MAG_FILTER, filtr gl.LINEAR_MIPMAP_LINEAR dla gl.TEXTURE_MIN_FILTER, oraz trzy tablice z danymi wierzchołków sześcianu.

  Objects[i].Position_x = Math.random()*30-15;
  Objects[i].Position_y = Math.random()*30-15;
  Objects[i].X_speed = Math.random()*100-50;
  Objects[i].Y_speed = Math.random()*100-50;
  Objects[i].Z_speed = Math.random()*100-50;
}

Na koniec określamy losowe położenie obiektu na osi x oraz y z zakresu od -15 do 15 oraz losową prędkość obrotu wokół poszczególnych osi z zakresu od -50 do 50.

function animate()
{
  for(var i=0; i<Objects_amount; i++)
  {
    Objects[i].Rot_x += Objects[i].X_speed * elapsed_s;
    Objects[i].Rot_y += Objects[i].Y_speed * elapsed_s;
    Objects[i].Rot_z += Objects[i].Z_speed * elapsed_s;
  }
}

W funkcji animate zwiększamy kąty obrotu obiektów o iloczyn wartości prędkości ich obrotu i zmiany czasu.

mvTranslate([0.0, 0.0, -40]);
for(var i=0; i<Objects_amount; i++)
{
  mvPushMatrix();
    mvTranslate([Objects[i].Position_x, Objects[i].Position_y, 0.0]);
    mvRotate(Objects[i].Rot_x, [1, 0, 0]);
    mvRotate(Objects[i].Rot_y, [0, 1, 0]);
    mvRotate(Objects[i].Rot_z, [0, 0, 1]);
      Objects[i].display();
  mvPopMatrix();
}

W funkcji drawScene wyświetlamy obiekty w odpowiednim miejscu za pomocą stworzonej dzisiaj metody display.

Uruchamiamy i wszystko działa zgodnie z planem. Efekt działania nie jest może oszałamiający, ale stworzyliśmy dzisiaj potężne narzędzie i zobaczyliśmy, jak można je wykorzystać. Zapraszam do eksperymentów. Kod aplikacji możecie pobrać tutaj.

W kolejnej lekcji zrobimy porządek w kodzie. Podzielimy go na poszczególne skrypty i umieścimy je w osobnych plikach. Dzięki temu kod aplikacji będzie bardziej czytelny i łatwiejszy w modyfikacji. Zapraszam.

Artykuł został poprawiony dnia 23.05.2010. Podziękowania dla gliniaka.
autor: RecRoot

4 Comments

  1. Szymon pisze:

    Witam, bardzo fajny tutorial, tylko mam jakiś dziwny problem z teksturami, niestety obiekty są nioteksturowane, w poprzednim przykładzie byłby, natomiast tutaj już nie mam kartę ATI, ciekawe czy jest jakiś związek pomiędzy tym.

  2. Kris pisze:

    Hej.

    W stworzonym programie tekstury wykorzystują mechanizm mipmap. Prawdopodobnie nie masz zainstalowanych najnowszych sterowników do karty graficznej lub używasz starszej wersji systemu Windows i dlatego nie możesz korzystać z tej funkcjonalności. Miałem podobny problem, pomogło gdy przesiadłem się z XP na Windows7 i zainstalowałem najnowsze stery.

    Można również zrezygnować z tego mechanizmu. Właściwie w tak prostej aplikacji nie widać efektu działania mipmap. Aby to zrobić zamień linijkę
    Objects[i].init(tex_num+”.jpg”,gl.LINEAR,gl.LINEAR_MIPMAP_LINEAR,Tab1,Tab2,Tab3);
    na
    Objects[i].init(tex_num+”.jpg”,gl.LINEAR,gl.LINEAR,Tab1,Tab2,Tab3);

    Powinno pomóc :)

  3. gliniak pisze:

    Świetna lekcja. Chyba najlepsza jak do tej pory.
    Ale widzę chyba kilka rzeczy, które możnaby poprawić:
    1. zamiast warunków (zmienna == undefined || zmienna == null) wystarczy napisać (!zmienna). A zamiast (zmienna != null && zmienna != 0) można napisać po prostu (zmienna)
    2.
    var Texture = gl.createTexture();
    var Vertices_buffer = gl.createBuffer();
    var Coord_buffer = gl.createBuffer();
    var Indices_buffer = gl.createBuffer();
    wg mnie (testowałem i działa) niepotrzebnie używasz tych dodatkowych zmiennych, które później i tak przypisujesz do
    this.Texture, this.Vertices, this.Tex_coords, this.Indices. A można przecież od razu ich używać.
    3. zamiast
    for (i…) {
    var tex_num = i;
    while(tex_num>9)
    tex_num-=10;
    }
    mozna napisac
    for (i…)
    var tex_num = i % 10;

    Pozdrawiam

  4. Kris pisze:

    Zgadzam się z Tobą we wszystkich trzech przypadkach. Zapis, który zaproponowałeś jest bardziej przejrzysty. Dzięki za uwagi.
    W przypadku tworzenia obiektu tekstury, bez wykorzystania zmiennej pośredniej metoda nie działa prawidłowo. Pewnie dlatego, że wykorzystana jest przy definicji uchwytu zdarzenia obiektu osobnej klasy.

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.