W końcu udało mi się zaimplementować ładnie wyglądające cienie dla światła słonecznego. Użyłem dwóch technik: Variance Shadow Mapping w połączeniu z warstowymi mapami (zwanymi często Cascade albo Parallel-Split). Efekt jest moim zdaniem znakomity. Kilka uwag implementacyjnych:

- nasz frustum widzenia musimy podzielić na kilka mniejszych. Ja zdecydowałem się na podział (mniej więcej) logarytmiczny -- przy widoku z pierwszej osoby b. ważne są szczegółowe cienie będące bardzo blisko, więc pierwsza warstwa musi obejmować możliwie niewielki obszar.
- najprostszym sposobem na wyznaczenie kolejnych macierzy dla cienia jest kolejno:
- wyznaczenie frustuma w world space
- obliczenie jego AABB
- stworzenie macierzy LookAt od pozycji światła do środka boxa
- przetransformowanie boxa przez powyższą macierz
- stworzenie macierzy rzutowania obejmującą przetransformowany box
- używamy dynamic branching aby liczyć zacienienie jedynie dla jednego lub dwóch obszarów (interpolując między nimi)
- VSM: piękna technika i jak na swoje efekty dosyć tania. Wszystko, co jest nam potrzebne to zapis do tekstury koloru w formacie float2. Zapisujemy liniową głębię (w przypadku światła słonecznego to po prostu współrzędna .z przetransformowanego wierzchołka) oraz jej kwadrat. Następnie teksturę rozmywamy kilka razy (to jest ogromna zaleta VSM, ponieważ zwyczajnych SM nie możemy sobie od tak blurować). Potem obliczanie zacienienia możemy wykonać taką funkcją:
float VSM(sampler2D texture, float4 coords) { float dist_to_light = coords.z; float2 moments = tex2D(texture, (coords.xy * 0.5 + 0.5)).ra; const float vsm_epsilon = 0.00001f; float lit_factor = (dist_to_light <= moments.x); float E_x2 = moments.y; float Ex_2 = moments.x * moments.x; float variance = min(max(E_x2 - Ex_2, 0) + vsm_epsilon, 1); float m_d = (moments.x - dist_to_light); float p_max = variance / (variance + m_d * m_d); return saturate(max(lit_factor, p_max) * 2 - 1); } - cienie uzyskane za pomocą VSM są naprawdę "miękkie" i wyglądają zdecydowanie lepiej niż PCF. W niektórych miejscach wyglądają nawet bardziej na użycie SSAO niż zwykłych map cieni: ;)

- niestety, nie ma róży bez kolców -- VSM ma jedną wadę, a mianowicie light bleeding. W dużej mierze eliminuje go przeskalowanie i saturate na zwracanej wartości, lecz pod pewnymi kątami nadal widać artefakty.
- na najdalszej warstwie cienia nie warto już robić VSM - zwykły PCF 2x2 załatwia sprawę.
Dodatkowo, nauczyłem się, że nie warto polegać na cgGLSetManageTextureParameters. Przy sporym żonglowaniu rendertargetami CG gubiła się i "zapominała" odpinać tekstury, przez co oczywiście nie można było do nich renderować.
Na zakończenie chciałbym podziękować Adamowi Sawickiemu za udostępnienie źródeł swojego silnika. Lektura jego kodu związanego z macierzami cieni była bardzo pouczająca. Bardzo pomocna była też wskazówka udzielona przez Krzysztofa Kluczka na forum Warsztatu -- zawierała ona trick z skalowaniem wyniku zacieniowania.