Zależy co rozumiesz przez "siatka voxelowa". Generalnie - tak, ale to dopiero początkowa funkcjonalność aplikacji, właściwie dodatek do tego co ma robić przede wszystkim.
1. Ładuję zdjęcia do tablicy 3d byte[,,] (każda komórka mówi o stopniu nieprzezroczystości, te zdjęcia to coś w stylu rentgena)
2. Dla każdego z chunków 8x8x8 generuję mesh (gdzie każdy voxel to jeden sześcian o ustalonej przezroczystości)
3. Chunki dodaję jako obiekty do sceny w Unity
Wygląda to tak:
Pierwsze to widok czaszki z góry, drugie to przekrój z boku. Kamera jest na razie całkowicie wolna, jest do tego suwak pozwalający wybrać jak blisko od kamery ma się zaczynać ewentualny przekrój.
Optymalizacja jest tutaj nietrywialna. Tak jak pisałem wcześniej, przy 32 zdjęciach 512x512 px dostajemy ponad 8 milionów voxeli. Każdy z nich to sześcian, tak więc 16 milionów wierzchołków / 96 milionów tris.
Nie byłby to problem dla Unity gdyby nie to że z bardzo rozsądnych powodów ilość wierzchołków na mesh jest limitowana do 64k.
Dlatego:
- obrazy ładuję wielowątkowo
- napisałem minimalistyczny shader obsługujący takie "rentgenowe" półprzejrzyste galarety
- nie generuję przezroczystych / prawie przezroczystych voxeli
- używam stworzonej z góry puli współdzielonych materiałów dla voxeli o takiej samej alphie (zamiast ustalania przezroczystości każdemu voxelowi z osobna)
- dzielę model na chunki 8x8 tak żeby mieć gwarancję że żaden nie przekroczy limitu
- od razu niszczę chunki które okazały się puste (jest ich trochę)
- ładuję asynchronicznie (zamiast zawiechy, użytkownik widzi na żywo jak model się buduje)
Obecnie ładowanie trwa ~10s, a załadowany model renderuje się z 40+ fps.
Problemem jest tutaj już overhead idący z Unity, którego mogę zredukować jedynie redukując ilość wywołań kosztownych funkcji Unity - a taką jest tworzenie obiektu na scenie (tutaj powtarzane kilkanaście tysięcy razy).
Biorę pod uwagę jeszcze jedną optymalizację: współdzielenie wierzchołków między voxelami z jednego chunka. Pozwoliłoby to na ok 7 krotne zredukowanie ich ilości, przez to umożliwiło korzystanie z większych chunków, a to z kolei korzystanie z mniejszej ilości chunków. Z drugiej strony trzeba by poświęcić dodatkowe obliczenia na sprawdzanie czy akurat potrzebne wierzchołki już istnieją i znajdywanie ich indeksów - ale to, jakkolwiek złożone od strony kodu, nie powinno stanowić wyzwania dla procesora.
Bloki unsafe znam, ale tutaj nic nie pomogą.
C# ogarnia dealokację pamięci bardzo dobrze, nie byłem tylko pewien czy bierze ją pod uwagę od razu po linijce kiedy obiekt był ostatni raz używany, czy dopiero na koniec bloku. W każdym razie ustawienie texture = null nie dało zauważalnych efektów (gc i tak jest odpalany dopiero w razie potrzeby).
Overhead przy czytaniu texture.width wynikał z tego że pod spodem siedział getter i przynajmniej kilka wywołań, w tym jedno C# -> C++.
@Fartuess edytowałem.