Internet pełen jest opisów, tutoriali i przykładowych kodów pokazujących, jak implementować różne efekty graficzne. Zakodowanie ich pojedynczo zazwyczaj nie jest więc problemem, o ile mamy jako takie pojęcie o grafice czasu rzeczywistego, bibliotece DirectX/OpenGL i programowaniu w ogóle.
Znacznie większym problemem jest połączenie kilku(nastu/dziesięciu) efektów tak, by było one zaaplikowane w jednym momencie do tej samej sceny. Ze względu na to, że każdy pojedynczy efekt może wymagać kodu w bardzo różnych miejscach potoku graficznego (chociażby w samej aplikacji oraz w kodzie shaderów), zintegrowanie wszystkich tych fragmentów nie wydaje się sprawą prostą.
Ostatnio aczkolwiek zajmowałem się praktycznym rozwiązywaniem tych kwestii; było to łączenie różnych rodzajów oświetlenia z cieniami generowanymi techniką shadow depth mapping i efektami postprocessingu w rodzaju depth of field. Pozwolę więc sobie podzielić kilkoma uwagami na ten temat. To może jeszcze nie są rady, jak dobrze zaprojektować architekturę silnika 3D, ale mały framework pewnie można o nie oprzeć ;] A zatem:
DrawPrimitive
czy DrawSubset
są w tej samej funkcji co Begin/EndScene
? W rzeczywistym kodzie zapewne tak nie będzie, bo dana scena będzie na pewno renderowana wielokrotnie.WORLD
(lub MODELVIEW
w OpenGL), bo nasza scena będzie renderowana kilka razy w potencjalnie różnych widokach (kamery, światła, obiektu odbijającego otoczenie, itp.). Dodatkowo mogą być nam potrzebne punkty w różnych przestrzeniach, np. w układzie widoku obserwatora i widoku od konkretnego światła naraz. Wreszcie, nie należy zapominać o prawidłowym przekształcaniu wektorów normalnych. W sumie więc sekcja deklaracji pliku z shaderami może wyglądać np. tak:
Są tutaj jeszcze dwie sprawy warte zaznaczania. Po pierwsze, obiekty rysujące się na scenie muszą wiedzieć, gdzie ustawiać swoją macierz lokalnego przekształcenia. We wszystkich używanych shaderach nazwa odpowiedniej stałej (tutaj ObjectTransform
) musi być taka sama; najlepiej też żeby mapowała się na te same rejestry stałych cn
. Naturalnie kod renderujący obiekty musi też “wiedzieć”, żeby korzystać właśnie z niej zamiast z macierzy przekształceń z fixed pipeline – czyli np. wywoływać effect->SetMatrix("ObjectTransform", &mat);
zamiast device->SetTransform (D3DTS_WORLD, &(currWorld * mat));
w przypadku DirectX).
Po drugie, nie trzeba “dla efektywności” przekazywać do shadera iloczynów macierzy, jeśli używamy także ich poszczególnych czynników. Można bowiem zupełnie bezkarnie mnożyć je na początku kodu shadera:
Kompilator wydzieli ten kod w postaci tzw. preshadera i zapewni, że będzie on wykonywany tylko raz (a nie dla każdego wierzchołka/piksela).
"ShadowMap"
, "DepthMap
, "Scene"
itp.Ogólnie trzeba przyznać, że implementowanie wielu efektów działających naraz w tej samej scenie to zagadnienie złożone i dość trudne. Chociaż więc starałem się podać kilka porad na ten temat, to w rzeczywistości niezbędne jest tutaj spore doświadczenie z różnymi rodzajami efektów, zarówno w teorii jak i praktyce.
Generalnie do tych celów wystarcza prosty menedżer oparty np. na słowniku identyfikującym poszczególne RT za pomocą nazw: “ShadowMap”, “DepthMap, “Scene”
Operacje na słownikach i/lub stringach są fajne dopóki masz zapas mocy CPU. W praktyce ścieżki renderowaine hardcoded są sporo szybsze niż “uniwersalny silnik”.
float4x4 CameraWorldRotation jest bez sensu. Do przekształcania wektorów przez macierz (w przeciwieństwie do punktów), czyli bez uwzględniania translacji, wystarczy wykorzystać podmacierz 3×3 danej macierzy, tzn.:
float3 normalWorld = mul(normalLocal, (float3x3)cameraWorld));
“wywołań Begin/EndScene będzie na pewno więcej niż jedno.”
Używanie więcej niż jednej pary Begin/EndScene w pojedyńczym przebiegu pętli realtime (klatce) też trochę nie ma sensu. Każda operacja może zawrzeć się między nimi i nie trzeba wiele razy ich wywoływać :)
“float4x4 CameraWVP = mul(CameraObjectWorld, mul(CameraView, CameraProjection));”
A to już dosyć zły pomysł. Ogólnie, w tej sytuacji mnożenie przez siebie trzech macierzy w shaderze to najgorsze, co możesz z nimi zrobić. :) A kompilator HLSL (jak sprawdzałem w SDK z Marca 09) nie potrafi tego zoptymalizować :)
@Charibo: Hmm byłem przekonany, że trzeba je wywoływać dla każdego RT. Jeśli nie, tym lepiej ;)
Wywoływanie wielu BeginScene i EndScene na klatkę to jest błąd!!! Dokumentacja wyraźnie mówi w opisie funkcji BeginScene:
“There should be one IDirect3DDevice9::BeginScene/IDirect3DDevice9::EndScene pair between any successive calls to present”
O, istotnie. Poprawiłem już odpowiedni fragment w notce – dzięki za to sprostowanie!