Największą zmianą w stosunku do ostatniej części tego kursu jest dodanie obsługi klawiszy. Łatwiej będzie to wyjaśnić spoglądając na kod który to wywołuje. Jeśli otworzysz źródło tego przykładu i szybko przejrzysz zauważysz zdefiniowane zmienne globalne:
var xRot = 0; var xSpeed = 0; var yRot = 0; var ySpeed = 0; var z = -5.0; var filter = 0;
xRot, yRot
powinny być Ci znane z poprzedniego przykładu. Reprezentują one aktualny stan obrotu kostki. ySpeed, xSpeed
też powinny być jasne - określają prędkość obrotu. Zmienna z
odpowiada za odległość obiektu od kamery, natomiast filter
jest liczbą od 0 do 2, która mówi o tym, który filtr tekstury jest aktualny.
Zanim przejdziemy do teksturowania powiedzmy sobie czym jest filtrowanie. W momencie nakładania tekstury na obiekt mogą mieć miejsce dwie sytuacje. Albo obiekt będzie większy od tekstury albo mniejszy. Filtrowanie określa w jaki sposób wybierany jest piksel tekstury dla każdego piksela obiektu. W ostatniej lekcji używaliśmy funkcja która to ustawia
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
Jak pamiętamy pierwszy parametr to rodzaj tekstury, drugi określa czy definiujemy filtrowanie dla powiększania czy pomniejszania, a trzeci to rodzaj filtrowania. W tym przykładzie poznamy efekty działania trzech rodzajów:
Aby przekonać się o działaniu filtrów uruchom przykład oddal obiekt w miarę daleko i zmieniaj filtry. Tylko mipmapa wygląda ładnie, czyż nie? Teraz gdy już wiemy co nieco zajrzyjmy do kodu:
function handleLoadedTexture(textures) { gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.bindTexture(gl.TEXTURE_2D, textures[0]); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[0].image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.bindTexture(gl.TEXTURE_2D, textures[1]); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[1].image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.bindTexture(gl.TEXTURE_2D, textures[2]); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[2].image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); gl.bindTexture(gl.TEXTURE_2D, null); } var crateTextures = Array(); function initTexture() { var crateImage = new Image(); for (var i=0; i < 3; i++) { var texture = gl.createTexture(); texture.image = crateImage; crateTextures.push(texture); } crateImage.onload = function () { handleLoadedTexture(crateTextures) } crateImage.src = "crate.gif"; }
Po tym krótkim wstępie teoretycznym wszystko powinno być jasne. W inicjalizacji jedyną zmianą jest utworzenie większej liczby tekstur. Natomiast funckja handleLoadedTexture
dla każdej tekstury zastosowuje rózne filtrowanie. Zwróć uwagę na linie 18. Aby utworzyć mipmapę konieczne jest wywołanie gl.generateMipmap(gl.TEXTURE_2D);
. Do tej pory przeanalizowaliśmy nowe zmienne globalne i teksturowanie, przejdźmy zatem do rysowania.
Pierwszym wartym zauważenia i skomentowania fragmentem jest linijka odpowiedzialna za pozycję obiektu:
mat4.translate(mvMatrix, [0.0, 0.0, z]);
Zamiast używać stałej wartości, teraz odległość będzie zależna od zmiennej z
.
Dalej idąc, tym razem nie będziemy chcieli obracać kostką wokół osi Z dlatego musimy zapisać:
mat4.rotate(mvMatrix, degToRad(xRot), [1, 0, 0]); mat4.rotate(mvMatrix, degToRad(yRot), [0, 1, 0]);
No i nareszcie wybór tekstury:
gl.bindTexture(gl.TEXTURE_2D, crateTextures[filter]);
Tyle w kwetii rysowania. Małe zmiany zachodzą również w animowaniu. Znowu zamiast używać stałych wartości będziemy korzystać ze zmiennych xSpeed
i ySpeed
.
xRot += (xSpeed * elapsed) / 1000.0; yRot += (ySpeed * elapsed) / 1000.0;
Pozostało wyjaśnić główny temat tej lekcji, czyli obsługę klawiszy. Pierwsza zmiana w tej kwestii ma miejsce już w naszej głównej funkcji. Poniżej zostało to podświetlone:
function webGLStart() { var canvas = document.getElementById("lesson06-canvas"); initGL(canvas); initShaders(); initBuffers(); initTexture(); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.enable(gl.DEPTH_TEST); document.onkeydown = handleKeyDown; document.onkeyup = handleKeyUp; tick(); }
To o czym informujemy tutaj JavaScript jest oczywsite. Jeśli jakiś klawisz zostanie wciśnięty to wywołaj funkcję handleKeyDown
i jeśli zostanie zwolniony to wywołaj handleKeyUp
. Przyjrzyjmy się zatem naszym nowym funkcjom
var currentlyPressedKeys = {}; function handleKeyDown(event) { currentlyPressedKeys[event.keyCode] = true; if (String.fromCharCode(event.keyCode) == "F") { filter += 1; if (filter == 3) { filter = 0; } } } function handleKeyUp(event) { currentlyPressedKeys[event.keyCode] = false; }
Najpierw deklarujemy zmienną (tablicę asocjacyjną), która będzie przechowywała kody i przypisane im klawisze. Zauważ, że aby sprawdzić czy wciśnięty jest klawisz F, konieczne jest napierw przekonwertowanie kodu aktualnie wciśniętego klawisza na stringa.
Warto jeszcze na chwilę się oderwać od naszego przykładu i wyjaśnić sobie pewną kwestię. W grach komputerowych i prawie wszystkich innych symulacjach 3D możemy rozróżnić dwa sposoby reakcji na wciśnięcie klawisza:
Szcególnie w drugiej opcji konieczne jest aby pamiętać o możliwości wciśkania kilku klawiszy na raz. Przecież jak biegniesz w grze, to żeby skręcić nie będziesz się zatrzymywał potem się obracał i znowu biegł. Do tego jest używany słownik. On przechowuje wszystkie aktualnie wciśnięte klawisze w danej chwili.
Z pierwszego typu w naszej animacji jest wciśnięcie klawisza "F". Reszta zalicza się do drugiego typu. Oto jak wygląda obsługa reszty klawiszy:
function handleKeys() { if (currentlyPressedKeys[33]) { // Page Up z -= 0.05; } if (currentlyPressedKeys[34]) { // Page Down z += 0.05; } if (currentlyPressedKeys[37]) { // Left cursor key ySpeed -= 1; } if (currentlyPressedKeys[39]) { // Right cursor key ySpeed += 1; } if (currentlyPressedKeys[38]) { // Up cursor key xSpeed -= 1; } if (currentlyPressedKeys[40]) { // Down cursor key xSpeed += 1; } }Powyższa funkcja jest wywoływana na bieżąco w funkcji
tick
obok drawScene
i animate
:
function tick() { requestAnimFrame(tick); handleKeys(); drawScene(); animate(); }
W tym tutorialu to już wszystko. Mam nadzieję, że wszystko jest zrozumiałe i już wiesz jak obsługiwać klawisze i stosować filtry. Następnym razem zaczniemy operować światłem. Do zobaczenia:)