wtorek, 31 marca 2009

error: variable `Math::Quaternion x; has initializer but incomplete type

Bardzo ciekawy błąd. Klasa ma inicjalizator, ale typ nie kompletny. Cóż to może znaczyć ?
Sprawa jest dosyć prosta. Klasa kwaternionu jest klasą zaprzyjaźnioną z klasą macierzy. Czyli mamy mniej więcej taki zapis w pliku Matrix.hpp:
class Quaternion;

class
Matrix
{

friend class
Quaternion;
...
};
Jest to przydatne przy tworzeniu macierzy rotacji z kwaternionu.
No ale gdzie błąd ? Otóż w miejscu w którym korzystałem z kwaternionów był włączony plik Matrix.hpp, ale plik Quaternion.hpp już nie. Nie było potrzeby umieszczać tego nagłówka w macierzy, więc tego nie zrobiłem.

No ale skoro już mowa o kwaternionach, to znaczy, że robiłem coś z rotacjami. Tak, zrobiłem prosty kontroler kamery. Można sobie teraz polatać po scenie. Koniec z re kompilacją po byle zmianie położenia kamery :)

Zrobiłem też trochę cienie. Działają poprawnie światła spot i omni. Kierunkowych nawet nie sprawdzałem.

Dodałem też proste oświetlenie per pixel.
123


Cube shadow mapa średnio poprawnie wyświetla się na moim GF 7600. Jednak na kartach z serii 8xxx widać poprawnie jedną ze ścianek.

czwartek, 26 marca 2009

Oświetlenie punktowe i "coding standard"

Po pierwsze chwalenie się. Zrobiłem oświetlenie punktowe :) Wyszło przy okazji parę ciekawostek. Przede wszystkim samodzielnie wymyśliłem jak poprawnie znaleźć koordynaty dla takiej tekstury :), nie jest specjalnie skomplikowane, ale zawsze miło samemu coś odkryć, a nie tylko ctrl-c ctrl-v. Początkowo myślałem, że współrzędne powinny być normalizowane. Nie ma jednak takiej potrzeby.
Znormalizowane wspólrzędne

Zapomniałem, że w klasie tekstury nie było kodu tworzącego mapy sześcienne. Dalej okazało się, że macierz widoku jest robiona nie tak jak powinna. W efekcie cień poruszał się w innym kierunku niż obiekt. Ostatecznym problemem były "poszarpane" cienie.
Niedobra macierz projekcji

Przyczyną było nie schodzenie się obrazków na krawędziach, czyli źle zrobiona macierz projekcji.
Poprawny efekt


Po drugie, dziękuję Ci Motorolo za Twój coding standard. Dzięki niemu nie musiałem szukać tego błędu:
if (LightType_Directional = type)
Wskazał mi go kompilator, takim oto komunikatem: "non-lvalue in assignment"

Premature optimization is root of all evil 2

Jak się okazało efekt cieniowania nie był prawidłowy, ponieważ pokusiłem o pewną optymalizację. Przemnorzyłem macierz światła przez macierz Range. Miało to na celu przycięcie wynikowych współrzędnych do zakresu <0; 1>. Problem w tym, że wspołrzędną z też przycinałem, natomiast do shadow mapy trafiały "pełne" wartości. W efekcie porównywanie obu wartości nie bardzo dawało oczekiwane rezultaty.


Piksele oświetlone mają różne kolorki, w zależności od tego z jakim biasem zostały zaklasyfikowane.
1 - bias = 0
0.8 - bias = 0.001
0.6 - bias = 0.01
0.4 - bias = 0.1
0.2 - bias = 0.2
0 - cień
Masakra :)
Poprawiłem macierz range:

środa, 25 marca 2009

Premature optimization is root of all evil

Niby wiem wiem, że tak jest. Nawet nie dawno na forum warsztatu sugerowałem Lukaz'owi, żeby najpierw nauczył się programować i dopiero potem pomyślał o wydajności. I co, i sam się wpakowałem w optymalizowanie czegoś, co wciąż jest dla mnie nowością.

Chcę wykorzystać shadow mapping jako technikę oświetlenia w swoim silniku. Kod od strony silnika jest już gotowy. Zostało tylko napisać (raczej dostosować niż napisać) shadery.
1) Renderowanie shadow mapy. Trzeba zapisać wartość głębokości, czyli z / w. Sprytnie wymyśliłem, że można tu ładnie zaoszczędzić wykonując to dzielenie w vertex shaderze. Wartość powinna się ładnie interpolować.
W vertex shaderze dodałem parametr:
out float oDepth : TEXCOORD2
, oraz kod obliczający ten parametr:
oDepth = oPosition.z / oPosition.w;
Pixel shader był nowy:
void ps(in float depth : TEXCOORD2, out float3 oColor : COLOR)
{

oColor = float3(depth , depth , depth);
}

2) Renderowanie wynikowego obrazka. Tutaj vertex shader wzbogacił się o wyliczanie pozycji względem światła, a pixel shader zajął się wyznaczeniem koordynatów dla shadow mapy, dystansu od światła i stwierdzeniem czy cień jest czy go nie ma.
GeometriaProjekcja shadow mapyDystans do światła


Jak widać zawartość shadow mapy nie pokrywa się z dystansem. Dokładniej to mamy źle wyznaczoną shadow mapę. Czemu ?? Błąd pojawia się gdy któryś wierzchołek znajduję się za światłem, czyli ma ujemną współrzędną z. Nie wiem dokładnie czemu tak się dzieje, jednak wydaje się, że taki trójkąt ma niewłaściwie interpolowany parametr depth. Przeniosłem dzielenie z / w do pixel shadera, rozwiązało to problem.
Wynikowe światło

Efekt wciąż nie jest poprawny, ale jest progress :)

środa, 18 marca 2009

base class `class __gnu_cxx::new_allocator< Graphics::Actor*> has a non-virtual destructor - czyli co robić gdy release nie działa

Co robić gdy program nie działa poprawnie w wersji Release, w przeciwieństwie do wersji zbudowanej z ustawieniami Debug ? Jak zawsze trzeba pomyśleć. Opracowanie planu działania wymaga zastanowienia się nad tym czym różnią się te dwie wersje:
1) Kod. Czy możliwe jest aby do kompilacji trafił różny kod ?
2) Ustawienia kompilatora. Jakie są różnice w budowaniu tych wersji ?
3) Inne :) Czy coś innego mogło wpłynąć na wynikowy kod ?

Jak się już ma plana to można przystąpić do działania.

Ad 1. Tak to jest możliwe. Myślę tu o pre procesorze. Zaczynamy więc nasze działania od przeszukania kodu pod tym kątem. Warto też zajrzeć od ustawień projektu i sprawdzić czy któraś z wersji nie ma definiowanych symboli, bo tak przecież się z tego korzysta.

Ad 2. Jeśli mamy pewność, że nie ma różnicy w kodzie, zaczynamy podejrzewać, że kompilator ma buga. No bo ten sam kod a on go zbudować nie umie :)
Oczywiście w obu wypadkach stosuje się różne ustawienia kompilatora. Szczególnie istotne są dwie flagi -o i -g w przypadku gcc, albo zestaw flag /O i /G, /Z /Y, /RTC ... dla kompilatora Microsoftu.
Co robią flagi -g ? Robią kilka rzeczy. Przede wszystkim włączają informacje niezbędne debuggerowi do pracy, ale nie tylko. Kompilator będzie również dodawał inicjację zmiennych. W małym projekcie coś takiego dosyć łatwo znaleźć. Przy większych z pomocą przyjdzie nam kompilator ze swoimi flagami kontrolującymi ostrzeżenia: -Wall,
-Weffc++, -Wuninitialized, -W-missing-field-initializers. Ich zastosowanie może sprawić, że kod przestanie się kompilować, za to zostaną nam wytknięte wszystkie braki w kodzie.
Jeśli to nie pomaga, to zostały jeszcze flagi -o, odpowiadające za optymalizację. Z reguły release ma dosyć wysoki poziom -o3+, a debug ma -o0, czyli optymalizacje wyłączone. Kompilator podczas optymalizacji będzie stosował przeróżne sztuczki, również takie które są potencjalnie niebezpieczne. Zaczynamy, więc podnosić poziom optymalizacji dla wersji debug. W ten sposób powinno udać się wyśledzić błąd.

Ad 3. Co innego może wpłynąć na wynikowy program ? Do głowy przychodzą mi tylko biblioteki. Może zdarzyć się tak, że włączane są różne ich wersje.

Co dalej ? Nie wiem, może porzucenie projektu, może zmiana zawodu ;)

EDIT: Drobna edycja tytułu.

wtorek, 17 marca 2009

Zliczanie referencji

Ehh, nadszedł ten dzień kiedy ostatecznie uznałem, że zliczanie referencji trzeba zrobić. Bufor tu, bufor tam. Potem ktoś ten bufor skasuje i pół aplikacji zostaje z wskaźnikiem na skasowany obiekt.
Zastosowane rozwiązanie jest oparte na wcześniejszym modelu zarządzania zasobami. Wzbogaciłem go jedynie o dodatkową klasę Handle i parę metod zliczających referencje.
Wszystkie elementy posługują się już uchwytami, a same zasoby póki co nie wychodzą poza menadżerów.

class Handle
{

friend class
Resources::Resource;
public
:
Handle(const Handle & arg);
Handle();
virtual
~Handle();

Handle & operator = (const Handle & arg);

std::string & GetName();
bool
IsCreated();

protected
:
Resource * getResource();
private
:
Handle(Resource * resource);

Resource * resource;
};


class Resource
{

friend class
Resources::ResourceFactory;
friend class
Resources::Handle;
public
:
std::string & GetName();
bool
IsCreated();
bool
Remove();
protected
:
Resource(const std::string & name, ResourceFactory & resourceFactory);
virtual
~Resource();

Handle getHandle();

void
setCreated(bool isCreated);
private
:
Resource();

void
add();
void
remove();

void
addRef();
void
removeRef();

std::string name;
ResourceFactory & resourceFactory;

bool
isCreated;
unsigned int
references;
};


typedef std::map<std::string, Resource *> ResourceMap;

class
ResourceFactory
{

friend class
Resource;
public
:
//No public interface.
protected:
ResourceFactory();
virtual
~ResourceFactory();

ResourceMap resources;
};

niedziela, 15 marca 2009

GUI

Jest progress, więc się chwalę. Zmieniłem metodę rysowania elementów z StrechtRect, na renderowanie geometrii. Wymagało to pewnych zmian w koncepcji, i ogromnej ilości poprawek przeróżnych błędów czających się w kodzie ;) Jednak opłaciło się:
* działa bez zauważalnego spadku fps.
* element może mieć dowolny kształt, np kulę.
* element może wysunąć się poza obszar ekranu bez żadnych zabiegów z mojej strony. StrechtRect strasznie marudziło w takiej sytuacji.

Gorzej tylko, że to znacznie skomplikowało budowę elementów i napisanie nowego będzie wymagać znajomości macierzy.

Oto 2 screeny. Na obu widać dwa okienka jedno pełnoekranowe z podpiętą kamerą, a drugie w rogu z podpiętym światłem. Niestety rysowanie bufora głębokości jeszcze nie działa, więc jest biała plama. Shader użyty przy liczeniu oświetlenia też nie robi do końca tego co powinien, więc mamy tylko przekopiowany kanał czerwony :)

czwartek, 12 marca 2009

GUI

Dawno nic nie napisałem. Jakoś nie było kiedy. Mogę tylko powiedzieć, że sporo napisałem. Zacząłem robić GUI. Idea jest prosta. GUI składa się z prostokątnych elementów, które reagują na polecenia typu kliknięcie myszką. Początkowo postanowiłem, wykorzystać do ich rysowania IDirect3DDevice9::StretchRect. Efekt wyszedł nawet całkiem przyjemny. Szybko się jednak okazało, że o ile użycie 32 bitowego render targetu rgba jako źródła obrazu nie stanowi problemu, to już r32f okazał nie akceptowalny dla tej metody. Konieczne jest wykonanie konwersji, a ta z kolej wymaga ściągnięcia danych do pamięci ram, przerobienia danych i odesłania ich do karty graficznej. Z 60 fps'ów zrobiło się 5.



Na obrazku widać dwa okienka (elementy). Pełnoekranowe używa kamery jako źródła obrazu, a to małe w rogu światła. Tylko, że shadow mapa nie była renderowana i wyszły śmieci :)