Periodogram jest przydatny do oceny jakości samplerów. Idealnie wymagamy od samplera by próbki były rozmieszczone w miarę równo od siebie i losowo. W miarę równo po stronie FFT oznacza w miarę równo od siebie odległe piki, tym węższe (mniej rozmyte) czym równiej od siebie rozmieszone. Ponieważ mamy do czynienia z próbkami w 2D piki te powinny tworzyć okręgi, co oznacza, że jeśli weźmiemy próbkę A i kąty wszystkich próbek wokół niej rozmieszczonych to są one równomierny rozmieszczone.
Punkty do generowania perodiogramów musimy przeskalować. Normalnie mamy próbkę na piksel albo i więcej jeśli mamy subsampling. Obraz musi też być w miarę duży by wychwycić wzorzec częstotliwości w pojedynczym obrazie. Odstępami pomiędzy próbkami regulujemy odstępy pomiędzy okręgami na periodogramie. Trochę zabawy i można dobrać parametry jak należy.
Interesuje nas tylko kilka pierwszych okręgów, czym dalej tym bardziej znikają one w szumie i potrzeba więcej obrazów z większą ilością próbek.
Do generacji periodogramów służy osobna klasa renderera:
public class SamplesPeriodogramRenderer : Renderer
{
[YAXNode]
public int Repeats = 100;
[YAXNode]
public int ScaleSamples = 25;
[YAXNode]
public double ScaleValues = 0.01;
ColorArrayFloat m_periodogram;
public override void Render()
{
m_periodogram = new ColorArrayFloat(Film.Width, Film.Height);
for (int i = 0; i < Repeats; i++)
{
m_rects_to_update.Enqueue(Film.GetFilmRect());
DoUpdateCallback();
Scene scene = Scene.DeepClone();
scene.RenderOptions.Renderer =
Renderer.Create(RendererType.SamplesVisualizer);
(scene.RenderOptions.Renderer as SamplesVisualizerRenderer).Scale =
ScaleSamples;
scene.ActiveCamera.Film.Postprocessors.Add(new FFT2Transform());
if (i != 0)
(scene.ActiveCamera.Film.Resampler as ExactResampler).SuppressLog =
true;
if ((i % 5 == 0) && (i > 0))
Loggers.Raytracer.Info("Periodogram progress: {0}", i * Repeats /
100);
Renderer renderer = scene.CreateRenderer();
renderer.ShouldStop += () => DoShouldStop();
renderer.Render();
var part = renderer.Canvas.PostprocessedArray;
foreach (var p in part.Pixels())
m_periodogram[p.X, p.Y] += part[p.X, p.Y] * ScaleValues;
m_rects_to_update.Enqueue(Film.GetFilmRect());
DoUpdateCallback();
if (DoShouldStop())
break;
}
}
public override void UpdateBitmap(Bitmap a_bmp)
{
using (Graphics g = Graphics.FromImage(a_bmp))
{
Rectangle rect;
while (m_rects_to_update.TryDequeue(out rect))
{
Bitmap bmp = m_periodogram.GetBitmap(rect);
g.DrawImage(bmp, rect);
}
}
}
public override RendererType RendererType
{
get
{
return RendererType.SamplerPeriodogram;
}
}
}Jej zadaniem jest generować tylko punkty z powierzchni projekcji kamery, same promienie nie są śledzone. Same próbki są skalowane, by było widać odstępy między nimi. Skalowaniu podlegają też końcowe wartości kolorów na periodogramie, chodzi o to by nie był on zbyt jasny ani zbyt ciemny.
Za samą generację DFFT2 odpowiada postprocesor.



Dla mnie najważniejsze było potwierdzenie tego, że mój kod generuje rozkład Poissona, a nie coś innego. Moje periodogramy wyglądają tak jak inne, które można znaleźć w sieci, tak więc w kodzie raczej nie ma błędów. Same periodogramy wyglądają jak powinny, czyli w kodzie samplera Poisonna nie ma błędów.
W przyszłości zamierzam dodać samplowanie adaptatywne. Być może będę generować próbki tylko dla małego kwadratu i wykorzystywać je do całego renderowane obrazu (z jakimś mniej lub bardziej losowym mieszaniem). Obecnie renderowanie odbywa się kafelkami, zamierzam dla każdego kafelka osobno generować punkty do samplowania i zwalniać je jak nie są już potrzebne. Pozwoli to zaoszczędzić trochę pamięci, zwłaszcza przy samplowaniu adaptatywnym. Dla samplowania adaptatywnego będzie to chyba naturalna opcja dla zsamplowania jakiegoś obszaru z wyższą częstotliwością poprzez utworzenie nowej pary sampler/resampler. Z tym zwalnianiem to też nie taka prosta sprawa, dla Poisonna potrzebuje brzegowe próbki z sąsiednich kafelków, tak więc tak od razu nie możemy się pozbyć próbek z kafelków. Periodogram na pewno pozwoli mi stwierdzić, że wszystko działa.
Brak komentarzy:
Prześlij komentarz