Silnik graficzny efektów



Podobne dokumenty
Plan wykładu. Akcelerator 3D Potok graficzny

GLKit. Wykład 10. Programowanie aplikacji mobilnych na urządzenia Apple (IOS i ObjectiveC) #import "Fraction.h" #import <stdio.h>

Systemy wirtualnej rzeczywistości. Komponenty i serwisy

Zastosowania Robotów Mobilnych

1 Temat: Vertex Shader

OpenGL : Oświetlenie. mgr inż. Michał Chwesiuk mgr inż. Tomasz Sergej inż. Patryk Piotrowski. Szczecin, r 1/23

Podstawy programowania skrót z wykładów:

Gry Komputerowe Laboratorium 4. Teksturowanie Kolizje obiektów z otoczeniem. mgr inż. Michał Chwesiuk 1/29. Szczecin, r

Programowanie gier komputerowych Tomasz Martyn Wykład 6. Materiały informacje podstawowe

Julia 4D - raytracing

1 Podstawy c++ w pigułce.

Grafika Komputerowa Wykład 5. Potok Renderowania Oświetlenie. mgr inż. Michał Chwesiuk 1/38

Podstawy języka C++ Maciej Trzebiński. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. Praktyki studenckie na LHC IVedycja,2016r.

Wykład 4. Rendering (1) Informacje podstawowe

Grafika Komputerowa Wykład 4. Synteza grafiki 3D. mgr inż. Michał Chwesiuk 1/30

Laboratorium grafiki komputerowej i animacji. Ćwiczenie V - Biblioteka OpenGL - oświetlenie sceny

Wprowadzenie do QT OpenGL

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Programowanie obiektowe

Smarty PHP. Leksykon kieszonkowy

Expo Composer Garncarska Szczecin tel.: info@doittechnology.pl. Dokumentacja użytkownika

Pisząc okienkowy program w Matlabie wykorzystujemy gotowe obiekty graficzne, lub możemy tworzyć własne obiekty dziedzicząc już zdefiniowane.

Budowa i generowanie planszy

Podstawy programowania, Poniedziałek , 8-10 Projekt, część 1

Język ludzki kod maszynowy

Karty graficzne możemy podzielić na:

1 Podstawy c++ w pigułce.

Delphi podstawy programowania. Środowisko Delphi

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

IMIĘ i NAZWISKO: Pytania i (przykładowe) Odpowiedzi

Matlab Składnia + podstawy programowania

Podstawy języka C++ Maciej Trzebiński. Praktyki studenckie na LHC IFJ PAN. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. M. Trzebiński C++ 1/16

Podstawy Programowania 2

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Spis treści. I. Skuteczne. Od autora... Obliczenia inżynierskie i naukowe... Ostrzeżenia...XVII

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

RENDERING W CZASIE RZECZYWISTYM. Michał Radziszewski

Wykład 8: klasy cz. 4

Bartłomiej Filipek

lekcja 8a Gry komputerowe MasterMind

Ustawienia materiałów i tekstur w programie KD Max. MTPARTNER S.C.

Szablony funkcji i szablony klas

Technologie Informacyjne

Spis treści. Księgarnia PWN: Roland Zimek - Swish Max3

Bartosz Bazyluk SYNTEZA GRAFIKI 3D Grafika realistyczna i czasu rzeczywistego. Pojęcie sceny i kamery. Grafika Komputerowa, Informatyka, I Rok

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Aplikacje w środowisku Java

TEMAT : KLASY DZIEDZICZENIE

Jak napisać program obliczający pola powierzchni różnych figur płaskich?

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Implementacja sieci neuronowych na karcie graficznej. Waldemar Pawlaszek

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 3. Karol Tarnowski A-1 p.

Temat 1. Podstawy Środowiska Xcode i wprowadzenie do języka Objective-C

Rozdział 4 KLASY, OBIEKTY, METODY

Programowanie Procesorów Graficznych

Podstawy Programowania Obiektowego

Czym jest wykrywanie kolizji. Elementarne metody detekcji kolizji. Trochę praktyki: Jak przygotować Visual Studio 2010 do pracy z XNA pod Windows

PRZEWODNIK PO PRZEDMIOCIE

2. Tablice. Tablice jednowymiarowe - wektory. Algorytmy i Struktury Danych

Ćwiczenie 4 - Podstawy materiałów i tekstur. Renderowanie obrazu i animacji

KLASA UCZEN Uczen imię, nazwisko, średnia konstruktor konstruktor Ustaw Wyswietl Lepszy Promowany

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

Spis treści. Księgarnia PWN: Roland Zimek - SWiSH Max2 i SWiSH Max3. Wprowadzenie... 9

Allegro5 3/x. Przykład wklejamy go do dev'a zamiast kodu domyślnego dal programu z allegro i kompilujemy.

Programowanie w języku Python. Grażyna Koba

Podstawy wykorzystania bibliotek DLL w skryptach oprogramowania InTouch

CUDA Median Filter filtr medianowy wykorzystujący bibliotekę CUDA sprawozdanie z projektu

GRAFIKA CZASU RZECZYWISTEGO Wstęp do programowania grafiki czasu rzeczywistego.

JAVA?? to proste!! Autor: wojtekb111111

TEST KOŃCOWY DLA KLASY III GIMNAZJUM- POGRAMOWANIE. Szkoła Podstawowa Nr 5. im. Księcia Mazowieckiego Siemowita IV w Gostyninie.

Język JAVA podstawy. wykład 2, część 1. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

D O K U M E N T A C J A

Oświetlenie obiektów 3D

Grafika komputerowa i wizualizacja

Karta graficzna karta rozszerzeo odpowiedzialna generowanie sygnału graficznego dla ekranu monitora. Podstawowym zadaniem karty graficznej jest

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Techniki wizualizacji. Ćwiczenie 10. System POV-ray tworzenie animacji

Programowanie obiektowe

Laboratorium 1 Temat: Przygotowanie środowiska programistycznego. Poznanie edytora. Kompilacja i uruchomienie prostych programów przykładowych.

Ćwiczenie 1. Przygotowanie środowiska JAVA

1. Prymitywy graficzne

Java. język programowania obiektowego. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak

Programowanie Strukturalne i Obiektowe Słownik podstawowych pojęć 1 z 5 Opracował Jan T. Biernat

Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych. Ćwiczenie 3 stos Laboratorium Metod i Języków Programowania

Pliki. Informacje ogólne. Obsługa plików w języku C

Grafika Komputerowa Wykład 6. Teksturowanie. mgr inż. Michał Chwesiuk 1/23

Część 4 życie programu

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Matlab Składnia + podstawy programowania

Zajęcia nr 2 Programowanie strukturalne. dr inż. Łukasz Graczykowski mgr inż. Leszek Kosarzewski Wydział Fizyki Politechniki Warszawskiej

Pierwsze kroki z easy Soft CoDeSys Eaton Corporation. All rights reserved.

Modelowanie i wstęp do druku 3D Wykład 1. Robert Banasiak

Grafika 3D na przykładzie XNA 3.1

Animowana grafika 3D. Opracowanie: J. Kęsik.

Przekształcenia geometryczne. Dorota Smorawa

Autodesk 3D Studio MAX Teksturowanie modeli 3D

Modelowanie rynków finansowych z wykorzystaniem pakietu R

Programowanie, część III

Transkrypt:

Politechnika Warszawska Wydział Elektroniki i Technik Informacyjnych Instytut Informatyki Rok akademicki 2008/2009 Praca dyplomowa inżynierska Stanisław Ignacy Gąsiorowski Silnik graficzny efektów Opiekun pracy: dr inż. Tomasz Martyn

Streszczenie Niniejsza praca opisuje projekt oraz implementację silnika graficznego efektów. Umożliwia on łatwe tworzenie efektów graficznych oraz zarządzanie nimi za pomocą wygodnego interfejsu użytkownika. Silnik został napisany w języku C++. Wykorzystuje bibliotekę OpenGL do generowania grafiki oraz języki CG i GLSL do pisania shaderów. Słowa kluczowe: silnik graficzny, grafika 3d, efekty, interfejs użytkownika, OpenGL, shadery. Title: 3D engine for effects Abstract This thesis describes the project and implementation of a 3d engine for effects. The engine offers an easy process of creating effects and managing them using a comfortable user interface. The engine was written in C++ programming language. It uses OpenGL library for generating graphics and CG and GLSL for writing shaders. Keywords: graphics engine, 3d graphics, effects, user interface, OpenGL, shaders.

Spis treści 1 Wprowadzenie... 7 1.1 OpenGL - opis technologii... 7 1.2 Inspiracja dla silnika z interfejsem... 8 1.3 Cel pracy... 9 2 Silnik efektów... 10 2.1 Idea... 10 2.2 Możliwości... 10 2.3 Budowa... 11 2.4 Efekty... 11 2.5 Wtyczki idea i DLL... 12 3 Implemetacja... 13 3.1 Założenia sprzętowe... 13 3.2 Budowa... 13 3.2.1 Interpreter... 13 3.2.2 Konsola... 15 3.2.3 Zbiór funkcji matematycznych... 15 3.2.3.1 Wektory... 15 3.2.3.2 Macierze... 17 3.2.3.3 Kwaterniony... 18 3.2.4 System obsługi shaderów... 19 3.2.5 Mechanizm przechowywania scen... 22 3.2.5.1 Geometria... 23 3.2.5.2 Światła... 27 3.2.5.3 Kamery... 29 3.2.5.4 Kości... 30 3.2.6 Mechanizm obsługi materiałów... 30 3.2.7 Mechanizm wczytywania tekstur... 33 3.2.8 System animacji... 33 3.2.9 System zarządzania sceną i pamięcią... 35 3.2.10 Mechanizm sterowania światłami i cieniami... 36 3.2.11 Mechanizm wczytywania scen z plików... 36 3.2.12 Mechanizm renderujący... 36 3.2.13 Mechanizm zarządzania buforami... 38 3.2.14 Silnik przetwarzania efektów... 41 3.3 Tworzenie klatki... 42 3.4 Efekty... 43 3.4.1 Efekty jako klasy... 43 3.4.2 Efekty w plikach DLL... 44 3.5 Interfejs... 44 3.5.1 Początki... 44

3.5.2 Składowe okna... 45 3.5.3 Panel efektów... 48 3.6 Optymalizacja... 49 3.6.1 Możliwości optymalizacji... 49 3.6.2 Optymalizacje w silniku... 50 3.7 Wydajność... 51 4 Interfejs... 52 4.1 Okna... 52 4.2 Panel efektów... 55 5 Podsumowanie... 58 Bibliografia... 60

1 Wprowadzenie 1.1 OpenGL - opis technologii OpenGL jest otwartą biblioteką graficzną, co oznacza, że nie jest własnością jednej firmy i tylko przez tę firmę rozwijaną, lecz nad jej rozwojem czuwa konsorcjum, nazwane OpenGL Working Group. Konsorcjum to jest częścią Khronos Group, zarządzającej otwartymi standardami. Wcześniej biblioteką zarządzało konsorcjum OpenGL Architecture Review Board (ARB), złożone z wielu firm związanych z grafiką, takich jak NVidia, ATI, Hawlett-Packard czy IBM. W 2006 roku podjęta została decyzja o przyłączeniu do Khronos Group [1]. Pierwotnie OpenGL zostało stworzone przez Silicon Graphics (SGI) dla swoich systemów graficznych generujących grafikę, m.in. dla filmów. OpenGL to biblioteka API pozwalająca wyświetlać grafikę 3D, jak również 2D [2]. Od roku 1992, kiedy biblioteka ta została wprowadzona, stała się najczęściej używaną i wspieraną biblioteką graficzną. Zawdzięcza to m.in. właśnie jej otwartości, co oznacza, że każdy może wprowadzać do niej własne rozszerzenia, które potem mogą być zatwierdzone przez konsorcjum ARB i wprowadzone do standardu. Kolejną cechą OpenGL decydującą o jej popularności jest niezależność od platformy. Cecha ta niemal od samego początku zaowocowała pojawieniem się implementacji biblioteki na wielu platformach, takich jak Windows, Linux, Mac, czy dzisiaj w telefonach komórkowych i konsoli PLAYSTATION 3 [3]. OpenGL jest tylko specyfikacją, a nie implementacją biblioteki. Powoduje to, że każdy dostawca sprzętu, a konkretnie sterownika do danego sprzętu musi zaimplementować bibliotekę samemu, co daje możliwości pełnego wykorzystania danego sprzętu. Oznacza to, że OpenGL może być i jest używany w domowych komputerach PC, jak również w super komputerach, czy nawet w klastrach komputerów. Możliwości sprzętowego przyśpieszania biblioteki są nieograniczone. Powoduje to jednak pewne problemy, które zawsze się zdarzają przy bibliotekach dostępnych na wiele platform, takie jak nieidentyczne działanie pewnych funkcji na różnych platformach [2]. Należy tutaj również wspomnieć o rozszerzeniach, które każda firma może wprowadzić do własnej implementacji. Specyfikacja OpenGL określa sposób dodawania rozszerzeń do biblioteki. Działające już rozszerzenia są opisane w tzw. OpenGL Extension Registry, którym cały czas zarządza Silicon Graphics [2]. Pozwala to na stworzenie wydajniejszych rozwiązań dla konkretnych platform, jednak pociąga za sobą uboczne skutki dla programistów używających biblioteki OpenGL. Muszą oni pisać kilka wersji aplikacji dla każdego docelowego systemu, jeśli chcą wykorzystać pełną moc danego sprzętu. Dziś liderami na rynku grafiki trójwymiarowej na komputerach osobistych są NVidia oraz AMD, które wprowadzają swoje własne rozszerzenia, działające tylko na ich najnowszych kartach graficznych. Powoduje to, że twórcy (m.in. gier komputerowych), używający grafiki trójwymiarowej, muszą pisać dwie wersje swojego kodu, które wykorzystają moc kart graficznych obu producentów. Największym konkurentem OpenGL jest DirectX firmy Microsoft. Jest to również biblioteka graficzna, jednak zarządzana w całości przez jedną firmę i dostępna tylko na ~ 7 ~

1.2 Inspiracja dla silnika z interfejsem platformie Windows. W starszych wersjach DirectX pisanie programów było uciążliwe, ponieważ biblioteka miała mało przejrzysty interfejs, podczas gdy OpenGL miało od zawsze interfejs bardzo łatwy i przyjemny. Od wersji DirectX 9.0 znacznie go jednak usprawniono, a w wersji 10.0 jest on wygodny dla programistów. DirectX udostępnia takie same możliwości jak OpenGL. Dzięki popularności systemu Windows na komputerach osobistych, oraz temu, że większość gier komputerowych jest pisana na ten system, to właśnie DirectX wyznacza standardy i wprowadza nowe możliwości. Jednak OpenGL nie jest gorszy. Znamiennym przykładem jest użycie OpenGL w demie technologicznym dla prezentacji najnowszych możliwości, które są dostępne w DirectX 10 na kartach graficznych NVidia [4]. 1.2 Inspiracja dla silnika z interfejsem Aby powstała grafika komputerowa potrzebne są dwa elementy: programista oraz artysta. Czasem programista jest artystą, jednak zdarza się to stosunkowo rzadko. Programista tworzy oprogramowanie czyli narzędzie, które daje możliwości artyście, aby ten stworzył dzieło. Dotychczas często było tak, że artysta mówił programiście, co chciałby osiągnąć, a programista pisał kod, który daną grafikę stworzy i wyświetli. Możliwości techniczne pozwalały pisać programy tylko na sztywno, tzn. jeśli artysta stwierdził, że dany program nie generuje takiej grafiki, o jaką mu chodziło, musiał znów prosić programistę o zmiany. Bardzo potrzebne były programy, które dawałyby artystom narzędzia do samodzielnego tworzenia różnego rodzaju grafiki i takie programy powstały. Np. program 3D Studio Max pozwala tworzyć sceny trójwymiarowe, oświetlać scenę, generować obraz oraz nakładać na niego rozmaite filtry. Jednak każda modyfikacja sceny, oświetlenia, czy właśnie filtrów wymaga ponownego wygenerowania obrazu, a to zajmuje dużo czasu. Natomiast artyści tworzący gry komputerowe lub w miarę proste animacje 3D generowane w czasie rzeczywistym, na przykład na potrzeby wizualizacji, czyli prezentacji jakiegoś modelu (np. jachtu), musieli działać w ścisłym związku z programistami. Dzisiejszy rozwój sprzętu do przetwarzania grafiki komputerowej, tj. kart graficznych, sprawił, że właśnie takie programy jak gry komputerowe, czy programy wizualizacyjne, nie muszą być pisane na sztywno. Program może udostępniać artyście narzędzia do samodzielnego kierowania procesem generowania grafiki. Artysta mógłby za pomocą przyjaznego interfejsu ustawiać parametry obrazu, dodawać filtry (np. rozmycie obrazu, wyostrzenie, zmiana kolorów), co dałoby mu dużą kontrolę nad generowanym obrazem. Nie musiałby czekać na programistę, aż ten napisze, np. by obraz został rozmyty filtrem Gaussa; sam mógłby ten filtr nałożyć i od razu zobaczyć efekt działania. Dzisiejsze karty graficzne pozwalają nałożyć na wygenerowany obraz wiele filtrów i efektów, które będą działały w czasie rzeczywistym, nawet w pełnej rozdzielczości Full HD. W roku 1999 firma NVidia wypuściła na rynek kartę graficzną GeForce 256, która jako pierwsza mogła sama prowadzić obliczenia związane z wierzchołkami [5]. Był to pierwszy duży krok w trwającym do dziś rozwoju możliwości kart graficznych. Od tamtego czasu karty graficzne zaczęły przejmować coraz większą część zadań powierzanych dotychczas procesorom głównym. ~ 8 ~

1.3 Cel pracy Najważniejszym wynalazkiem były shadery. Są to małe programy, które przetwarzają wiele małych porcji danych. Takimi danymi są przeważnie wierzchołki oraz piksele, stąd wzięły się nazwy Vertex Shader oraz Pixel Shader [6]. W starszych kartach graficznych istniały tylko dwa bufory ramki, do których obrazy były renderowane. Jeden z nich wyświetlał gotową klatkę, a w drugim tworzona była nowa. W ten sposób uniknięto migotania obiektów na ekranie. Nowe karty graficzne pozwalają tworzyć tych buforów o wiele więcej. Dzięki temu można prowadzić wielowarstwowe renderowanie całkowicie poza ekranem, w sposób niewidoczny dla użytkownika, a dopiero wynikowy obraz wyprowadzić na ekran. Przy tak wielu buforach można tworzyć ciągi przetwarzania obrazu, tzn. wejściowy obraz załadować do pierwszego bufora, następnie przekształcić i zapisać do drugiego, znów zastosować przekształcenia graficzne i zapisać do kolejnego bufora itd. Widać tutaj, że pojawia się możliwość manipulowania kolejnością przekształceń. 1.3 Cel pracy Celem pracy jest napisanie programu silnika efektów, który pozwalałby w łatwy sposób zarządzać potokiem graficznym, umożliwiając dodawanie filtrów oraz efektów post-processingu do wyrenderowanej, czyli wygenerowanej, sceny. Pozwalałoby to artyście samemu określać, jak będzie wyglądał obraz na wyjściu. Dla przykładu, artysta pracujący nad grą komputerową, może przy pomocy takiego silnika nałożyć na obraz gry filtry, które najlepiej będą pasowały do danej sytuacji (np. obraz, gdy bohater jest ranny, powinien być czerwony oraz rozmyty). Efekt działania danego filtru będzie widoczny od razu. Przyjazny i łatwy interfejs umożliwia łączenie efektów w ciągi, jeden za drugim, dzięki czemu nakładanie wielu efektów jest łatwe. Silnik ma być również rozszerzalny, tzn. pisanie kolejnych efektów nie powinno wymagać kompilowania całego silnika. W rozdziale 2 opisano czym jest silnik efektów i jakie funkcje ma spełniać. W rozdziale 3 opisana jest implementacja silnika stworzonego w ramach niniejszej pracy. Rozdział 4 to opis interfejsu pokazujący jak używać tego programu. W ostatnim rozdziale znajduje się podsumowanie. ~ 9 ~

2 Silnik efektów 2.1 Idea Silnik efektów nie jest tym samym, co silnik graficzny, ponieważ skupia się na tytułowych efektach, a nie musi zawierać takich rzeczy, jak np. zarządzanie podziałem przestrzeni sceny, aby wyświetlać tylko to, co jest akurat widoczne. Silnik efektów ma spełniać funkcje zarządzania filtrami oraz efektami nakładanymi na wygenerowany obraz. Ponieważ niektóre efekty nie operują wyłącznie na już wygenerowanym obrazie, lecz mogą także uczestniczyć w jego tworzeniu (jak np. wolumetryczna mgła wymaga wyrenderowania sceny z punktu widzenia światła, co nie jest już tylko filtrem post-processingu) zatem silnik musi renderować samemu sceny, a także pozwalać na w miarę dowolne zarządzanie sceną, czyli siatkami obiektów oraz światłami. Oczywiście silnik musi posiadać mniej lub bardziej rozbudowane możliwości wczytywania scen oraz ich wyświetlania razem z teksturowaniem i oświetlaniem. 2.2 Możliwości Główna siła silnika efektów polega na udostępnianiu interfejsu graficznego użytkownika do łatwego zarządzania efektami, łączenia ich w ciągi, kontrolowania ich parametrów, a także zapisywania i odczytywania stworzonych ciągów do/z plików. Silnik sam zarządza obrazami przesyłanymi między efektami. Dla przykładu, jeśli w graficznym interfejsie ustawimy, że wyrenderowana scena ma zostać rozmyta, a następnie rozmyty obraz ma być odwrócony, silnik powinien sam zadbać o przesłanie do efektu rozmywającego obrazu wyrenderowanej sceny. Następnie ma on uruchomić efekt rozmycia, po czym przesłać rozmyty obraz do efektu odwrócenia, a otrzymany od niego obraz wyświetlić na ekran. Dodatkowo silnik powinien, w miarę możliwości, próbować optymalizować ułożenie efektów, ich kolejność oraz próbować zmniejszać ilość przesyłanych danych, gdyż przesłanie obrazu powoduje przesył dużej ilości danych, szczególnie jeśli ma odbywać się wiele razy w ciągu generowania każdej klatki. Silnik może również próbować optymalizować same efekty, np. jeśli wiadomo, że po efekcie rozmycia jest efekt odwrócenia obrazu, o wiele wydajniej będzie wyrenderować odwrócony, rozmyty obraz. Oczywiście, nic nie stoi na przeszkodzie by silnik efektów rozbudować o funkcje zarządzania sceną, detekcję kolizji, fizykę, dźwięk, czyniąc go tym samym pełnoprawnym silnikiem graficznym, czy nawet silnikiem gry. Silnik efektów powinien być rozszerzalny, tzn. efekty nie powinny być na stałe zapisane w programie, tylko wczytywane w formie wtyczek. Dzięki temu taki silnik nie będzie się ograniczał tylko do z góry zaplanowanych efektów, ale każdy użytkownik z niewielką zdolnością programowania będzie mógł napisać własny efekt i dołączyć go do silnika. ~ 10 ~

2.3 Budowa 2.3 Budowa Każdy silnik graficzny musi posiadać następujące składniki [7]: renderer, który tworzy dwuwymiarowy obraz na podstawie opisu podanej trójwymiarowej sceny; system zarządzania światłami; system zarządzania kamerami; mechanizm wczytywania scen z jakiegoś źródła (np. z pliku); mechanizm zarządzania sceną jest on ściśle powiązany z mechanizmem zarządzania pamięcią. Te dwa mechanizmy odpowiadają za wysyłanie do renderera sceny, która ma zostać wyświetlona. Oznacza to, że muszą one zadbać by niewidoczne obiekty nie zostały wysłane do wyświetlenia, co by powodowało zmniejszenie wydajności; system animacji odpowiadający za animowanie obiektów w czasie oraz deformowanie siatek obiektów. Następujące elementy nie są wymagane w silniku graficznym, jednak w zależności od zastosowania silnika mogą być konieczne: interfejs użytkownika; mechanizm odtwarzania dźwięków; silnik symulacji fizyki. Silnik efektów skupia się na tworzeniu atrakcyjnych wizualnie obrazów i nie wymaga by wszystkie wymienione wyżej elementy były w pełni obecne. I tak, np. silnik fizyczny oraz mechanizm dźwiękowy, nie są konieczne. Natomiast rozbudowany interfejs użytkownika, który zapewni możliwość zarządzania efektami, jest potrzebny. 2.4 Efekty Efekty są to takie funkcje, które modyfikują obraz bądź go generują. W szczególności efektem jest procedura renderująca całą scenę do pewnego bufora ramki (ang. framebuffer), a także efektem jest procedura przyjmująca taki bufor i wyświetlająca go na ekran. Przeważnie efektami są procedury aplikujące filtry do obrazu, np. filtr rozmycia czy wyostrzenia. Jednak niektóre efekty wykonują więcej funkcji, np. renderują mapę kubiczną z punktu widzenia światła, aby potem na obraz nałożyć wolumetryczną mgłę. Efekty można więc nazwać blokami funkcyjnymi, podobnie jak w przetwarzaniu sygnałowym. Efekty posiadają wejścia oraz wyjścia. Wejściami i wyjściami są tekstury, czyli obrazy. Dany efekt przekształca tekstury wejściowe i produkuje tekstury wyjściowe. Przesyłane tekstury nie muszą być obrazami, lecz mogą to być mapy głębokości bądź prostokątne bufory liczb (które można interpretować oraz zapisać jako obrazy). Efekty mogą również dołączać dodatkowe informacje, jak np. efekt renderowania obrazu może dołączyć informacje o położeniu kamery. ~ 11 ~

2.5 Wtyczki idea i DLL 2.5 Wtyczki idea i DLL Efekty mają być dołączane do silnika bez potrzeby kompilacji samego silnika. Także pisanie efektów powinno być możliwe bez posiadania całego kodu źródłowego silnika. Do tego celu najlepiej zastosować mechanizm wtyczek. Polega on na tym, że każdy efekt jest umieszczony w oddzielnym pliku wtyczce które to pliki są wczytywane przez silnik już w czasie działania. Silnik pobiera dane z wtyczek, takie jak nazwa efektu oraz konkretna implementacja efektu. Wtyczka podczas swojego działania ma do dyspozycji cały wachlarz funkcji udostępnianych przez silnik. Mechanizm wtyczek najlepiej zaimplementować używając plików DLL, czyli dynamic link library. Oznacza to, że dany plik DLL jest biblioteką funkcji, które będą dołączone do programu w czasie jego działania. Mechanizm plików DLL pozwala na udostępnianie procedur i wymianę ich między dołączaną biblioteką a programem [8]. ~ 12 ~

3 Implemetacja 3.1 Założenia sprzętowe Stworzony silnik wymaga do działania komputera klasy PC wyposażonego w kartę graficzną obsługującą Shader Model 3.0. Może to być na przykład karta z serii: NVidia GeForce 6x00, 8x00, GTX 2xx. Ze względu na użycie jako głównego do pisania shaderów języka NVidia CG, silnik działa najlepiej na kartach NVidii, jednak teoretycznie powinien działać również na kartach firmy AMD/ATI. Obsługiwany jest również język OpenGL Shading Language (GLSL), który jest niezależny od platformy. Pozostałe komponenty komputera nie odgrywają tak dużej roli jak karta graficzna. Dlatego do poprawnego działania silnika w zupełności wystarczy jednordzeniowy procesor, odpowiadający mocą np. procesorowi AMD Athlon 64 3000+, a także przynajmniej 1 GB pamięci RAM. 3.2 Budowa Na opracowany silnik składa się wiele elementów, m.in.: interpreter, konsola, zbiór funkcji matematycznych, system obsługi shaderów, mechanizm przechowywania scen, mechanizm obsługi materiałów, mechanizm wczytywania tekstur, system animacji, system zarządzania sceną i pamięcią, mechanizm sterowania światłami i cieniami, mechanizm wczytywania scen z plików, mechanizm renderujący, mechanizm zarządzania buforami, silnik przetwarzania efektów. 3.2.1 Interpreter Aby ułatwić zarządzanie silnikiem został opracowany interpreter języka skryptowego podobnego do C, dzięki któremu można sterować parametrami silnika i sposobem działania. Za pomocą pliku ze skryptem można ustawić wszystkie parametry, pełni więc on funkcje pliku konfiguracyjnego. Jest on jednak bardziej złożony i pozwala zarządzać całą pracą silnika. Dla przykładu, wykrycie rozdzielczości oraz dopasowanie do niej parametrów nie wymagają zaimplementowania tego w skompilowanym kodzie silnika, tylko jest możliwe do wykonania za pomocą skryptu. Pozwala to na łatwiejsze zarządzanie ~ 13 ~

3.2 Budowa silnikiem, bez potrzeby ciągłej rekompilacji, a także daje możliwości konfiguracji i zarządzania dla osób, które dostaną wersję silnika bez źródeł. Przykładowy skrypt konfiguracyjny wygląda następująco: // dołączenie pliku nagłówkowego zawieracjącego deklaracje // dostępnych funkcji #include "globals.sh" // ustawienie rozdzielczości renderowania Main_Screen_Width = 700; Main_Screen_Height = 400; // ustawienie rozdzielczości wynikowego wyświetlania na ekran End_Screen_Width = 700; End_Screen_Height = 400; // użycie HDR w zależności od wykrytych możliwości karyty graficznej if (ishdravailable() == 1) use_hdr = 1; else use_hdr = 0; // inicjacja całego silnika Init_All(); // odczytanie materiałów z pliku ReadMaterials("materials.txt"); // deklaracja sceny scene3d scene; // odczytanie sceny z pliku LoadScene(scene, "scena.txt"); // załadowanie mapy dla jednego ze świateł LoadCubeMap(0, "spot\\"); // ustawienie przełącznika tak by animacja była wyłączona animation = 0; // ustawienie czasu na 0.0 settime(0.0); // ustawienie głownej sceny SetMainScene(scene); // włączenie cienii shadows = 1; // załadowanie efektów z pliku LoadEffects("mgla.eff"); // głowna pętla wywołująca procedurę go() while (1)go(); Główną procedurą jest go(), która wywołuje procedurę zaimplementowaną w silniku, obsługującą zdarzenia systemu Windows oraz wszystkie czynności potrzebne w celu wyświetlenia pojedynczej klatki (więcej w rozdziale 3.3). ~ 14 ~

3.2 Budowa Interpreter posiada typy zmiennych wbudowanych: void, int, float oraz string, a także obsługuje takie elementy składni języka C, jak: instrukcje warunkowe if, pętle for oraz while, wyrażenia matematyczne, funkcje i procedury. 3.2.2 Konsola W celu ułatwienia zarządzania działającym silnikiem stworzono konsolę, za pomocą której można sterować wieloma parametrami pracy silnika, a także wywoływać różne funkcje. Konsola służy także jako rejestr zdarzeń (ang. log), w którym to logu zapisywane są wszelkie informacje o przebiegu pracy silnika. Konsolę można otworzyć w dowolnym momencie działania programu za pomocą klawisza tyldy. Konsola była wzorowana na rozwiązaniu zastosowanym w grze Quake 2 [9]. Konsola interpretuje wpisywane polecenia. Najpierw tekst polecenia dzielony jest na tokeny, odseparowane białymi znakami (z wyjątkiem wyrażeń objętych cudzysłowami, gdzie cały cudzysłów jest traktowany jako jeden token). Następnie wywoływana jest funkcja, której odpowiada pierwszy token w poleceniu, a jej parametrami są kolejne tokeny. Poszczególne polecenia mogą być oddzielane znakiem średnika. Konsola obsługuje również skróty (ang. alias), dzięki którym można długi ciąg poleceń zapisać jako krótki skrót w następujący sposób: alias skrot polecenie par1 par2; polecenie2 a b c a następnie wywołać tylko za pomocą polecenia skrot. Konsola jest wykorzystywana do obsługi klawiszy. Każdemu klawiszowi można przypisać (ang. bind) określone polecenie konsoli, np. bind w toggle shadows 1 0 przypisze klawiszowi w polecenie przełączania (ang. toggle) flagi określającej czy mają być włączone cienie między 1 (true), a 0 (false). 3.2.3 Zbiór funkcji matematycznych Dla potrzeb silnika opracowany został przydatny zbiór klas dla obsługi wektorów dwu-, trzy- i cztero- elementowych oraz macierzy 3x3 i 4x4, a także kwaternionów. 3.2.3.1 Wektory Ponieważ silnik operuje w grafice 3D najczęściej jest używany wektor trójelementowy opisany w klasie CVektor: ~ 15 ~

3.2 Budowa class CVektor public: float x, y, z; CVektor() x = 0; y = 0; z = 0;}; CVektor(float ax, float ay, float az) x = ax; y = ay; z = az;}; CVektor(CVektor4 &vec); }; void operator = (const CVektor &vek); void operator = (const CVektor4 &vek); void operator = (const float m) x = m; y = m; z = m;}; CVektor operator * (const CVektor &vec); // Cross CVektor operator - (const CVektor &vec); float operator % (const CVektor &vec); // Dot CVektor operator + (const CVektor &vec); bool operator == (const CVektor &vec); CVektor operator - (); void operator += (const CVektor &v) x += v.x; y += v.y; z += v.z;}; void operator += (const CVektor4 &v); void operator -= (const CVektor &v) x -= v.x; y -= v.y; z -= v.z;}; void operator /= (const CVektor &v) x /= v.x; y /= v.y; z /= v.z;}; void operator += (const float m) x += m; y += m; z += m;}; void operator -= (const float m) x -= m; y -= m; z -= m;}; void operator *= (const float m) x *= m; y *= m; z *= m;}; void operator /= (const float m) x /= m; y /= m; z /= m;}; CVektor operator + (const float m) return CVektor(x+m, y+m, z+m);}; CVektor operator - (const float m) return CVektor(x-m, y-m, z-m);}; CVektor operator * (const float m) return CVektor(x*m, y*m, z*m);}; CVektor operator / (const float m) return CVektor(x/m, y/m, z/m);}; void Normalize(); float Length(); float operator[] (const int i); void Rotate(float angle, const CVektor &p); Najważniejsze funkcje tej klasy: Klasa obsługuje operatory dodawania i odejmowania wektorów za pomocą operatorów + i. Iloczyn wektorowy jest zaimplementowany pod operatorem mnożenia *, natomiast iloczyn skalarny pod operatorem %. Klasa posiada również metodę Normalize(), która normalizuje wektor (tzn. sprowadza jego długość do wartości 1), a także metodę zwracającą aktualną jego długość Length(). Zaimplementowana jest również metoda obrotu wektora wokół zadanej osi, reprezentowanej przez wektor wychodzący z punktu [0,0,0] w kierunku równoległym do danej osi. Wykonuje to metoda void Rotate(float angle, const CVektor &p). Wektory dwu- i czteroelementowe posiadają podobne funkcje i nie ma potrzeby ich tu dokładnie opisywać są one zaimplementowane w klasach CVektor2 i CVektor4. ~ 16 ~

3.2 Budowa 3.2.3.2 Macierze Macierz 4x4 jest zaimplementowana w klasie CMatrix: class CMatrix public: CMatrix(); CMatrix(const TMatrix& mat); CMatrix(const CMatrix& mat); static CMatrix getzero(); static CMatrix getscale(float sx, float sy, float sz); static CMatrix gettranslation(float tx, float ty, float tz); static CMatrix getrotationx(float degrees); static CMatrix getrotationy(float degrees); static CMatrix getrotationz(float degrees); static CMatrix getrotation(const CVektor& axis, const float degrees); static CMatrix getidentity(); CMatrix& zero(); CMatrix& identity(); CMatrix& scale(float sx, float sy, float sz); CMatrix& translate(float tx, float ty, float tz); CMatrix& rotatez(float degrees); CMatrix& rotatey(float degrees); CMatrix& rotatex(float degrees); CMatrix& rotate(const CVektor& axis, const float degrees); CMatrix& transpose(); CMatrix& inverse(); TMatrix m; }; CMatrix& operator =(const CMatrix &b); CMatrix& operator*= (const CMatrix &b); friend CMatrix operator* (const CMatrix &a, const CMatrix &b); friend CMatrix operator* (const CMatrix &a, const float b); friend CVektor operator* (const CMatrix &a, const CVektor &b); friend CVektor operator* (const CMatrix &a, const CVektor *b); friend CVektor4 operator* (const CMatrix &a, const CVektor4 &b); friend CVektor4 operator* (const CMatrix &a, const CVektor4 *b); Najważniejsze funkcje: getscale(float sx, float sy, float sz) zwraca macierz dla skalowania każdego wymiaru przez daną wartość, gettranslation(float tx, float ty, float tz) zwraca macierz dla przesunięcia o wektor [tx, ty, tz], getrotationx(float degrees) zwraca macierz dla obrotu wokół osi X o zadany kąt, getrotationy(float degrees) zwraca macierz dla obrotu wokół osi Y o zadany kąt, ~ 17 ~

3.2 Budowa getrotationz(float degrees) zwraca macierz dla obrotu wokół osi Z o zadany kąt, getrotation(const CVektor& axis, const float degrees) zwraca macierz dla obrotu wokół zdanej osi o zadany kąt, getidentity() zwraca macierz jednostkową, identity() ustanawia macierz macierzą jednostkową, scale(float sx, float sy, float sz) aplikuje przekształcenie skalujące do macierzy, translate(float tx, float ty, float tz) aplikuje przesunięcie o wektor [tx, ty, tz], rotatex(float degrees) aplikuje rotację wokół osi X o zadany kąt, rotatey(float degrees) aplikuje rotację wokół osi Y o zadany kąt, rotatez(float degrees) aplikuje rotację wokół osi Z o zadany kąt, rotate(const CVektor& axis, const float degrees) aplikuje rotację wokół zadanej osi o zadany kąt, transpose() wykonuje transpozycję macierzy, inverse() wykonuje odwrócenie macierzy. Operacja mnożenia macierzy jest zaimplementowana za pomocą operatora mnożenia *. 3.2.3.3 Kwaterniony Kwaterniony reprezentowane są przez klasę: class CQuaternion public: CQuaternion() x = y = z = 0.0f; w = 1.0f; } CQuaternion(float X, float Y, float Z, float W)...} void CreateMatrix(float *pmatrix); void CreateFromMatrix(float *pmatrix, int rowcolumncount); CQuaternion Slerp(CQuaternion &q1, CQuaternion &q2, float t); }; float x, y, z, w; Metody: CreateMatrix tworzy macież 4x4 obrotu odpowiadającą danemu kwaternionowi, CreateFromMatrix tworzy kwaternion z podanej macierzy obrotu, ~ 18 ~

3.2 Budowa Slerp zwraca interpolację po powierzchni sfery podanych kwaternionów, ze współczynnikiem wagowym t < 0; 1>. 3.2.4 System obsługi shaderów Jednym z problemów jakie pojawiły się w trakcie pracy było to, że każdy shader musiał być na sztywno wpisany w kod programu. Wynika to stąd, że każdy program shadera zawierać może dowolne nazwy zmiennych, używać różnych jednostek teksturujących oraz być napisanym w różnym języku bądź dla różnego profilu sprzętowego. Również problemem było to, że przy takim założeniu dla shaderów nie dało się stworzyć materiałów, które łatwo by można przypisywać do obiektów i je modyfikować. Każdy materiał musiał mieć swój shader wpisany w silnik, np. shader dla powierzchni kamiennej, drewnianej, dla wody czy metalu. Ideą było stworzenie mechanizmu do łatwiejszej obsługi shaderów. Jego założeniem jest, aby do stworzenia shadera w silniku wymagana była tylko ścieżka do pliku z shaderem oraz informacja w jakim języku jest napisany. Dzięki takiemu systemowi tworzenie nowych materiałów byłoby bardzo proste; wystarczyłoby podać ścieżkę do shadera oraz wskazać tekstury, którymi obłożony będzie obiekt. Również tworzenie efektów, w których kodzie często wymagane jest użycie shadera, byłoby prostsze, ponieważ dzięki takiemu mechanizmowi do zastosowania shaderów byłoby potrzebne wpisanie zaledwie dwóch czy trzech linii w kodzie źródłowym efektu. Opracowana implementacja obsługuje dwa najbardziej popularne języki shaderów, czyli CG firmy NVidia [10], a także GLSL należący do standardu OpenGL [11]. Każdy shader składa się z dwóch sub-shaderów: części cieniującej wierzchołki oraz części cieniującej piksele. Są to dwa odrębne programy. Do obsługi shaderów napisana została klasa CShader: class CShader public: GPUProgramType type; // dla GLSL GLhandleARB GLSLprogram; CShader()...} ~CShader()...} CVertexProgram *vertex_prog; CFragmentProgram *fragment_prog; }; void Bind(); void Disable(); Klasa ta zawiera w sobie wskaźniki do dwóch programów, będących pochodnymi klas: dla shadera wierzchołków CVertexProgram oraz dla shadera pikseli ~ 19 ~

3.2 Budowa CFragmentProgram. Klasy te dziedziczą z klasy CGPUProgram, która udostępnia interfejs do zarządzania programem: class CGPUProgram public: GPUProgramType type; char *filename; virtual void SetParameter1f(const char *name, float v)... } virtual void SetParameter2fv(const char *name, float *v)... } virtual void SetParameter3fv(const char *name, float *v)... } virtual void SetParameter4fv(const char *name, float *v)... } virtual void Load(CShader *parent, const char *filename, const char **args, const char *source)... } virtual void Bind()... } virtual void Disable()... } }; ~CGPUProgram()...} Zmienna type określa typ shadera, np. shader wierzchołków w języku CG. Z kolei w zmiennej filename jest zapisana nazwa pliku, z którego odczytany był shader. Metody SetParameter służą do przekazywania parametrów do shadera, odpowiednio zmiennej typu float, wektora dwóch zmiennych, trzech oraz czterech. Metoda Load() służy do wczytania programu z pliku. Metoda Bind() służy do włączenia danego shadera, a metoda Disable() do wyłączenia. Shadery odczytywane są z plików, w których zapisane są ścieżki do dwóch programów (dla wierzchołków i pikseli). W plikach tych mogą być również zapisane ścieżki do tych samych programów, ale zapisanych w innym języku, co ma ułatwić przenośność kodu między różnymi platformami. Przykładowy plik wygląda następująco: <vertex source="vp_diffuse_specular.vp" system="vp40"> </vertex> <fragment source="fp_diffuse_specular.fp" system="fp40"> </fragment> Opisane są tutaj źródła programów wierzchołków oraz pikseli, oraz określony system w jakim dany program jest napisany. Możliwe systemy to: ~ 20 ~

3.2 Budowa vp40 profil wierzchołków w wersji 4, język CG, fp40 profil pikseli w wersji 4, język CG, vp30 profil wierzchołków w wersji 3, język CG, fp30 profil pikseli w wersji 3, język CG, vp20 profil wierzchołków w wersji 2, język CG, fp20 profil pikseli w wersji 2, język CG, glsl profil wierzchołków lub pikseli w języku GLSL. Odpowiednie klasy obsługujące programy w danych językach dziedziczą z klas CCgVertexProgram oraz CCgFragmentProgram. Do zarządzania shaderami stworzona została klasa ShaderManager: class ShaderManager public: virtual void BindShader(CShader *shader); virtual void DisableShader(); virtual void CgBindModelView(); virtual void CgSetTexture(int tex, GLuint tex_id, GLuint target=gl_texture_2d); virtual void CgSetTextureTarget(int tex, GLuint target=gl_texture_2d); virtual void CgEnableTextures(); virtual void CgDisableTextures(); virtual void CgSetVertexParameter1f(const char *_name, float v); virtual void CgSetFragmentParameter1f(const char *_name, float v); virtual void CgSetVertexParameter2fv(const char *_name, float *v); virtual void CgSetFragmentParameter2fv(const char *_name, float *v); virtual void CgSetVertexParameter3fv(const char *_name, float *v); virtual void CgSetFragmentParameter3fv(const char *_name, float *v); virtual void CgSetVertexParameter4fv(const char *_name, float *v); virtual void CgSetFragmentParameter4fv(const char *_name, float *v); virtual void CgSetVertexMatrix(const char *_name, CGGLenum matrix, CGGLenum transform); virtual void CgSetMatrix4(const char *_name, float *v); virtual CShader* LoadShader(const char *filename, char *use_system=null, const char **args=null); static ShaderManager& getinstance() #ifndef _WINDLL if (!instance)instance = new ShaderManager; #endif return *instance; } static void setinstance(shadermanager *_instance) instance = _instance; ~ 21 ~

3.2 Budowa } private: ShaderManager() }; ShaderManager(const ShaderManager&) }; static ShaderManager *instance; }; Do zarządzania shaderami należy używać wyłącznie tej klasy. Udostępnia ona następujące metody: BindShader służy do włączenia podanego shadera, DisableShader służy do wyłączenia działającego shadera, CgBindModelView powoduje przekazanie do shadera aktualnej macierzy przekształceń, CgSetTexture ustawia teksturę o podanym ID do podanej jednostki teksturującej, CgEnableTextures włącza tekstury, CgDisableTextures wyłącza tekstury, CgSetVertexParameter przekazuje podany parametr do shadera wierzchołków, CgSetFragmentParameter przekazuje podany parametr do shadera pikseli, CgSetVertexMatrix przekazuje macierz przekształceń do shadera wierzchołków, CgSetMatrix4 przekazuje podaną macierz do shadera wierzchołków, LoadShader wczytuje shader z pliku oraz zwraca wskaźnik do niego. Przykładowo wczytanie shadera wygląda następująco: CShader *shader = shadermanager::loadshader( shader.shd ); Teraz włączenie shadera sprowadza się do prostego polecenia: shadermanager::bindshader(shader); a wyłączenie do: shadermanager::disableshader(); 3.2.5 Mechanizm przechowywania scen Każda scena składa się z obiektów, świateł, kamer oraz kości, będących częścią systemu animacji. Kości tworzą szkielet, na który nałożona jest skóra siatka wierzchołków. Poruszanie kośćmi powoduje deformację siatki. Jest to algorytm tzw. skinningu [12]. Scenę opisuje klasa CScene3D: ~ 22 ~

3.2 Budowa class CScene3D public: mesh3d mesh; swiatla_t swiatla; camery_t camery; int num_bones; bone_s *bones; CVektor ambient_light; CTree Tree; CScene3D()... } ~CScene3D()... } void LoadSIG(const char *filename); void Init(); void FreeBuf(); }; int GetObjectID(const char *name); 3.2.5.1 Geometria Scena jest wczytywana z plików eksportowanych z programu 3D Studio Max, za pomocą specjalnie napisanej wtyczki do tego programu. Geometria sceny zapisana jest w klasie mesh3d: class mesh3d vector <CObiekt*> Obiekty; CTextureSet *TextureSets; }; vector <CObiekt *> transparent_alpha01_objects; vector <CObiekt *> transparent_objects; vector <CObiekt *> animated_objects; vector <CObiekt *> skinned_objects; vector <CObiekt *> reflect_objects; Struktura ta opisuje obiekty znajdujące się w scenie. Wektor Obiekty zawiera wskaźniki do obiektów. Zmienna TextureSets jest tablicą zestawów tekstur, które nałożone są na obiekty. Wektor transparent_objects zawiera wskaźniki do obiektów, które są przezroczyste, tzn. że materiał nałożony na dany obiekt ma ustawioną flagę przezroczystości. Wektor transparent_alpha01_objects zawiera wskaźniki do obiektów, które są binarnie przezroczyste, co oznacza, że albo piksel obiektu będzie wyrenderowany, albo nie. Jest to użyteczne np. do renderowania trawy. Wektor animated_objects zawiera wskaźniki do animowanych obiektów. ~ 23 ~

3.2 Budowa Odpowiednio wektor skinned_objects zawiera wskaźniki do obiektów, które są kontrolowane przez kości. Wektor reflect_objects zawiera wskaźniki do obiektów, których materiał jest odbijający, tzn. że dla takich obiektów będzie wymagane wyrenderowanie specjalnej mapy kubicznej. Obiekty, reprezentujące trójwymiarowe siatki w scenie, są opisane przez klasę CObiekt: class CObiekt public: int num_faces; DECLARE_ARRAY(face_normal, CVektor); int num_ver; DECLARE_ARRAY(vertices, vertex_s); DECLARE_ARRAY(vertices_add, vertex_add_s); int num_ind_overall; vector<csurface> surfaces; int num_ind; GLuint *indices; PrimitiveGroup *primgroups; GLuint num_groups; DECLARE_ARRAY(shadow_vertices, shadow_vertex_s); int num_shadow_indices; DECLARE_ARRAY(shadow_indices, GLuint); int num_stripes; stripe_s *stripes; bool convex; plain_t *plains; int num_pla; char nazwa[100]; int mat_ref; int material; bool castshadows; bool skinned; int visible; Canim_object<int> anim_visible; bool is_self_illumination; float self_illumination[3]; bool transparent; int transparent_force_draw; bool reflect; float reflect_blur; ~ 24 ~

3.2 Budowa Canim_object<float> anim_reflect_blur; float reflect_resolution; Canim_object<float> anim_reflect_resolution; CRenderTarget *EnvCube_RT; char parent_name[100]; CObiekt *parent; int num; float minx, miny, minz; float maxx, maxy, maxz; CVektor Center; CVektor BBoxCorners[8]; CVektor anim_center; CVektor anim_bboxcorners[8]; float radius; CVektor Pivot; CVektor position; CMatrix translation; CMatrix rotation; bool animated_pos, animated_rot; int num_frames_pos, num_frames_rot; klatka_t<cvektor> *keys_pos; klatka_t<cquaternion> *keys_rot; DECLARE_ARRAY(sas, int); int *new_sas; int num_edges; edge_s *edges; // parametry materialow vector <int> Vertex_Params_Overloaded; vector <Canim_object<int> > anim_vertex_params_overloaded; vector <int> Fragment_Params_Overloaded; vector <Canim_object<int> > anim_fragment_params_overloaded; vector <Shader_Param> Vertex_Params; vector <Canim_object<int> > anim_vertex_params_int; vector <Canim_object<float> > anim_vertex_params_float; vector <Shader_Param> Fragment_Params; vector <Canim_object<int> > anim_fragment_params_int; vector <Canim_object<float> > anim_fragment_params_float; int GetVertexParamID(const char *name, int type); int GetFragmentParamID(const char *name, int type); void SetObjectMaterial(int new_material); vector <Shader_Param> Additional_Params; bool used; ~ 25 ~

3.2 Budowa GLuint buffer_uid; GLuint indices_buffer_uid; GLuint shadow_buffer_uid; GLuint shadow_indices_buffer_uid; void MakeSas(); void FindBoundingBox(); void MakeIndices(); void MakeStripes(); void MinMaxBBox(); void MakeEdges(); void MakeShadowVertices(); void MakeVertexRadius(); void InitBuf(); void FreeBuf(); }; CObiekt(); ~CObiekt(); Klasa obiektu zawiera wiele pól, z których najważniejsze wymienione są poniżej. Zmienna num_faces zawiera liczbę trójkątów w obiekcie. Zmienna num_ver zawiera liczbę wierzchołków składających się na obiekt. Tablica vertices zawiera wszystkie wierzchołki obiektu. Każdy wierzchołek opisany jest stukturą vertex_s: struct vertex_s CVektor4 xyzw; CVektor normal; CVektor binormal; CVektor tangent; CVektor2 texcoord; }; Wektor czteroelementowy xyzw opisuje położenie wierzchołka w przestrzeni. Wektór trójelementowy normal opisuje kierunek wektora normalnego dla danego wierzchołka. Wektory tangent oraz binormal, razem z wektorem normalnym tworzą układ współrzędnych w danym wierzchołku, tzw. przestrzeń styczną (ang. tangent space), w skrócie TBN. Jest to układ współrzędnych, w którym oś X (oś stycznej ang. tangent), której kierunek w przestrzeni nadrzędnej zapisany jest w wektorze tangent, pokazuje kierunek wzrostu współrzędnej X koordynatów tekstury, a oś Y (oś binormalna wektor binormal) kierunek wzrostu koordynaty Y. Natomiast oś Z (oś normalnej wektor normal) pokazuje kierunek prostopadły do płaszczyzny tekstury czyli płaszczyzny trójkąta, który ma nałożoną daną teksturę [13]. Ma to zastosowanie w modelu bez cieniowania powierzchni zakrzywionych, gdyż inaczej te układy współrzędnych mogą być lekko przekrzywione w celu stworzenia wrażenia płynnego przejścia między trójkątami. Zmienna num_ind opisuje liczbę wskaźników w tablicy indices. Wskaźniki opisują, z jakich wierzchołków składają się trójkąty. To znaczy, że każde trzy wskaźniki są ~ 26 ~

3.2 Budowa indeksami w tablicy wierzchołków i dany trójkąt składa się właśnie z tych trzech wierzchołków. Tablica primgroups, której wielkość jest zapisana w zmiennej num_groups, zwiera tzw. grupy prymitywów. Znaczy to, że są tam indeksy wierzchołków tworzących trójkąty. Jednak są one tak ułożone, by renderowanie trójkątów było najwydajniejsze. Aby wyznaczyć odpowiednią kolejność ułożenia wskaźników przekazywanych do karty graficznej, używana jest darmowa biblioteka NvTriStip firmy NVidia. Kolejne ważne elementy to tablica shadow_vertices opisująca wierzchołki cieni oraz tablicę wskażników tworzących trójkąty shadow_indices. Każdy obiekt zawiera nazwę w zmiennej nazwa. Zmienna material określa numer materiału przypisanego do danego obiektu. Zmienna mat_ref określa, którego zestawu tekstur używa dany obiekt. Zmienna castshadows określa czy obiekt rzuca cienie, a zmienna skinned czy obiekt jest kontrolowany przez kości. Zmienna visible określa czy obiekt jest widoczny, zmienna transparent czy przezroczysty, a zmienna reflect czy odbijający. Zmienne buffer_uid, indices_buffer_uid, shadow_buffer_uid, shadow_indices_buffer_uid określają numery buforów dla odpowiednio: wierzchołków, wskaźników, wierzchołków bryły cienia, wskaźników bryły cienia. Pomocnicze tablice to m.in. edges, opisująca krawędzie każdego trójkąta oraz sas określająca trójkąty, z którymi styka się każdy trójkąt jedną krawędzią. Zawarte są również zmienne do obsługi animacji obiektu (system animacji oraz wyjaśnienia do zmiennych zawarte są w rozdziale 3.2.8). Każdy obiekt zawiera również zmienne do obsługi parametrów meteriału przypisanego do obiektu oraz animacji tych parametrów. Szerzej o materiałach napisano w rozdziale 3.2.6. 3.2.5.2 Światła Struktura swiatla_t opisuje światła w scenie. Wygląda ona następująco: struct swiatla_t int mmmswiatel; CSwiatlo *Swiatla; }; Zmienna ile_swiatel określa liczbę świateł w scenie, a tablica Swiatla zawiera wskaźniki do świateł. ~ 27 ~

3.2 Budowa Światło opisuje klasa CSwiatlo: class CSwiatlo public: char *typec; int type; CVektor node; CVektor tar_node; bool shadow; bool kat; bool attend; float attstart, attend; CVektor intens; float color[3]; float angle; CVektor4 circle_plane; float circle_radius; CVektor position; CVektor RotatAxis; float acosrotataxisz; bool diffuse; bool specular; bool volume_test; CVektor VolumeCorners[8]; CVektor VolumeNormal[6]; float VolumeD[6]; int num_ob; CObiekt **objects; int num; CSektor *sek; int static_silhouettes; vector <Silhouette*> silhouettes; bool animatedlight, animatedtarget; int num_frames_light, num_frames_target; klatka_t<cvektor> *keys_lig; klatka_t<cvektor> *keys_tar; void UpdateAxis(); bool ObjectInVolume(CObiekt *ob); bool SetScissor(CFrustum *frustum, CViewport *viewport, CVektor campos); void UpdateSilhouettes(CSektor *_sek); void Init(CScene3D *sc); void FreeSil(); }; CSwiatlo()...} ~CSwiatlo()...}; ~ 28 ~

3.2 Budowa Najważniejsze elementy to: zmienna type, określająca typ światła, wektor node, zawierający pozycję światła, a wektor tar_node pozycję celu światła (jeśli światło niepunktowe), zmienna shadow, określająca czy światło rzuca cienie, zmienna attend, oznaczająca promień tłumienia światła (od ang. attenuation end), wektor trójelementowy intens, określający intensywność światła dla każdej składowej RGB jego koloru, zmienne color, określająjące kolor światła, dla świateł stożkowych typu spot (tzn. w punkcie światła jest wierzchołek stożka światła) zmienna kat, określająca kąt przy wierzchołku stożka, zawarte są również zmienne używane do obsługi animacji światła i jego celu. 3.2.5.3 Kamery Kamery są zapisane w strukturze analogicznej do świateł: struct camery_t int ile_camer; CCamera *Camery; }; Kamera przechowywana jest w klasie CCamera: class CCamera public: CVektor position; CVektor tar_position; float fov; bool isortho; bool animatedcam, animatedtar, animatedfov; int num_frames_cam, num_frames_tar, num_frames_fov; klatka_t<cvektor> *keys_cam; klatka_t<cvektor> *keys_tar; klatka_t<float> *keys_fov; }; CCamera()...} ~CCamera()...} Najważniejsze elementy to: position określa położenie kamery, tar_position określa położenie celu kamery, czyli punktu, na który kamera patrzy, ~ 29 ~

3.2 Budowa fov określa kąt pola widzenia kamery. Pozostałe zmienne służą do obsługi animacji. 3.2.5.4 Kości Kości przechowywane są w strukturze: struct bone_s char name[128]; CMatrix translation; CMatrix rotation; int parent; }; bool animated_pos, animated_rot; int num_frames_pos, num_frames_rot; klatka_t<cvektor> *keys_pos; klatka_t<cquaternion> *keys_rot; Macierze translation oraz rotation określają odpowiednio macierz przesunięcia oraz obrotu danej kości. Cztery ostatnie linie kodu zawieraja zmienne do obsługi animacji, o których mowa w rozdziale 3.2.8. 3.2.6 Mechanizm obsługi materiałów W trakcie opracowywania silnika pojawił się problem z materiałami. W początkowym etapie materiały nie istniały jako takie, zaś w silniku zaimplementowany był tylko typ w rodzaju powierzchnia, która rozpraszała światło oraz odbijała je, tzn. wyglądała jak metal. Potem pojawiła się potrzeba, by niektóre obiekty miały inną powierzchnię, np. liść, odbijający światło inaczej niż metal. Jeden uniwersalny shader nie mógł tego obsłużyć, a poza tym byłoby to znaczne ograniczenie funkcjonalności. Opracowany więc został mechanizm materiałów. Materiały opisane są w pliku tekstowym, ponieważ w ten sposób najłatwiej jest je modyfikować, a dodatkowo nie ma potrzeby kompilacji całego silnika, czy nawet jego fragmentu. Przykładowy materiał wygląda następująco: Material "Diffuse_Specular" Light true // czy swiatlo oddzialuje na material Bones false // czy wspiera animacje szkieletowa Transparent false // czy jest przezroczysty ~ 30 ~

3.2 Budowa Texture2D 0 Diffuse Texture2D 1 Normal TextureCUBE 2 NormalizationMap TextureCUBE 3 SpotLightMap Texture2D 4 "parallax_detail.dds" Texture2D 5 "parallax_detail.dds" Texture2D 6 "parallax_detail_grain.dds" Normals true TexCoord 0 Binormal 1 Tangent 2 Cam_PositionV Light_Position Light_Position2 Light_Attenuation Light_Intensity Shader "CamPos" "LightPos" "LightPos2" "Attenuation" "Intens" "diffuse_specular.shd" Vertex } Fragment bool is_diffuse = true bool is_specular = true float Parallax_size <-0.2-0.2> = 0.025 } } float Detail_size_1 <-0.02-0.02> = 0.00 float Detail_size_2 <-0.02-0.02> = 0.00 float Detail_coord_1 <0.0-110> = 1.00 float Detail_coord_2 <0.0-11110> = 10.00 float Detail_normal_1 <0.0-10.00> = 1.00 float Detail_normal_2 <0.0-10.00> = 1.00 Każdy obiekt ma przypisane tekstury, które są podzielone na główne kategorie: Diffuse tzw. tekstura rozpraszania określa podstawowy kolor obiektu, Specular tekstura odbić określa w jakim stopniu obiekt odbija światło, Transparent tekstura przezroczystości określa w jakim stopniu obiekt jest przezroczysty, Normal tzw. mapa normalnych określa ułożenie normalnych na powierzchni obiektu. Właściwości jakie można definiować w materiale to: Light (true, false) odpowiada, czy na materiał będą wpływały światła, jeśli tak, to oznacza, że podany w materiale shader jest przystosowany do obsługi świateł, Bones (true, false) mówi, czy obiekty obłożone tym materiałem będą kontrolowane przez system kości, ~ 31 ~

3.2 Budowa Transparent (true, false) mówi, czy materiał jest przezroczysty, Texture2D opisuje jaką teksturę dwuwymiarową dostanie jednostka teksturująca o podanym zaraz za tym napisem numerze. Tekstura może być wczytana z podanego pliku albo być jednego z rodzajów: o Diffuse dostarczona będzie tekstura diffuse, przypisana do obłożonego materiałem obiektu, gdzie każdy obiekt, opisany danym materiałem, może mieć inną teksturę, o Normal analogicznie dostarczona będzie mapa normalnych, o Background dostarczona będzie tekstura tła (dla obiektów przezroczystych); TextureCUBE opisuje, jaką teksturę kubiczną dostanie jednostka teksturująca. Możliwe typy to nazwa katalogu z zapisanymi sześcioma ścianami sześcianu lub: o NormalizationMap dostarczona będzie tekstura normalizująca, o EnvironmentMap dostarczona będzie tekstura odbić, o SpotLightMap dostarczona będzie tekstura odpowiadająca padaniu światła ze źródła typu spotlight; Normals (true, false) określa, czy do materiału mają być dostarczone normalne, TexCoord (numer) określa, na których koordynatach będą dostarczone koordynaty tekstury z obiektu, Binormal (numer) określa, na których koordynatach będą dostarczone binormalne, Tangent (numer) analogicznie określa, na które koordynaty dostarczyć tangent, Cam_PositionV (nazwa) określa, do jakiej zmiennej mają być dostarczone współrzędne kamery, Light_position (nazwa) określa, do jakiej zmiennej dostarczyć współrzędne światła, Light_attenuation (nazwa) określa, do jakiej zmiennej dostarczyć promień, poza którym światło zostanie całkowicie stłumione, Light_intensity (nazwa) określa do jakiej zmiennej dostarczyć intensywność światła, Shader (nazwa) określa ścieżkę do pliku z shaderem, Vertex i Fragment otwierają bloki, w których wymienione są zmienne, jakie będzie można kontrolować z poziomu interfejsu, a które będą przekazane do shaderów. Ważne jest, by nazwy zmiennych były identyczne, jak te w odpowiednim shaderze. Możliwe typy to: o bool NAZWA = WARTOŚĆ określa, że shader zawiera zmienną NAZWA i należy ją przekazywać do niego. Wartość domyślnie inicjowana przez WARTOŚĆ, o float NAZWA <MIN MAX> = WARTOŚĆ określa, że shader zawiera zmienną zmiennoprzecinkową NAZWA i jej wartość można ~ 32 ~

3.2 Budowa kontrolować w zakresie <MIN, MAX>, a domyślnie należy przypisać WARTOŚĆ. 3.2.7 Mechanizm wczytywania tekstur Aby silnik był w stanie bez problemów wczytywać dowolne sceny, potrzebny był przyjazny mechanizm, wczytujący tekstury do pamięci karty graficznej. Tekstury są zapisane na dysku w plikach w różnych formatach, z których najważniejsze to: jpeg, bmp, tga oraz dds. Ten ostatni jest to format przygotowany specjalnie dla grafiki trójwymiarowej, potrafiący przechowywać spakowaną grafikę w formacie, który bezpośrednio jest obsługiwany przez karty graficzne. Potrzebna była klasa do obsługi tekstur i w tym celu powstała klasa CTexture: class CTexture public: GLuint bind; int target; CTexture()...} CTexture(const char *filename, int _target, int wrap=gl_repeat, int min=gl_linear, int mag=gl_linear)...} ~CTexture(); bool Load(const char *filename, int _target, int wrap=gl_repeat, int min=gl_linear, int mag=gl_linear); void Bind(); CTexture& operator=(const CTexture &tex)...} }; void Unload(); Element bind służy do identyfikowania tekstury w pamięci jest on unikalny i nadawany przez mechanizmy OpenGL. target określa cel, tzn. czy tekstura jest dwuwymiarowa (GL_TEXTURE_2D), czy też kubiczna (GL_TEXTURE_CUBE). Najważniejszą metodą jest Load, która wczytuje tekstury z pliku. Procedura bada rozszerzenie pliku i w zależności od niego uruchamia odpowiedni mechanizm wczytujący. Pliki BMP wczytywane są za pomocą opracowanej procedury. Obsługa plików TGA zaczerpnięta została z rozwiązań dostępnych w Internecie. Pliki JPG są wczytywane za pomocą biblioteki jpeglib. Natomiast pliki DDS obsługiwane są za pomocą biblioteki nv_dds firmy NVidia. 3.2.8 System animacji Aby silnik mógł prezentować nie tylko statyczne sceny, potrzebny był system animacji, po to, by obiekty, światła, kamery, a także dowolne parametry, np. shaderów, czy ~ 33 ~