Taką siatkę centrujemy w punkcie padania promienia i dla każdego piksela, na który nakłada się siatka dodajemy wartość koloru promienia ważoną wartością z siatki.
W przypadku supersamlingu wymiary siatki mnożymy przez pierwiastek z ilości promieni przewidywanych na piksel.
Bardzo ważne jest by uwzględnić wartości zwracane przez Sampler, tzn. zamiast ... 13.5, 14.5, 15.5, ... możemy dostać ... 13.49, 14.5, 15.51, ... (oczywiście z dużo mniejszym błędem). Tego rodzaju błędów nie da się uniknąć - zaokrąglanie liczb binarnych do postaci dziesiętnej.
W samym Resamplerze dodałem właściwość Subreslution, która dodatkowo zwiększa wymiary siatki. Jej zadaniem jest zwiększenie dokładności resamplowania, tak, by metoda ta w miarę zwiększania właściwości coraz bardziej przypominała resampler Exact, a coraz mniej Resizer. By kod mógł współdziałać zarówno z filtrem Box o promieniu 0.5 jak i z innymi filtrami o promieniach całkowitych Subreslution musi być nieparzyste. Inaczej wagi albo jednego albo drugiego filtru będą oscylować na granicy promienia w zależności od błędów numerycznych Samplera.
W analizie kodu najważniejsze jest dobre zrozumienie wszystkich $\pm\frac{1}{2}$, $\pm 1$, Floor, Ceiling, Round.
Kod:
public struct PixelInterpolator { private ColorFloat m_numerator; private double m_denumerator; /// <summary> /// Note: a_ray_color in SRGB space. /// </summary> /// <param name="a_ray_color"></param> /// <param name="a_filter_weight"></param> public void Collect(ColorFloat a_ray_color, double a_filter_weight) { m_numerator += a_ray_color * a_filter_weight; m_denumerator += a_filter_weight; } /// <summary> /// Note: color in SRGB space. /// </summary> public ColorFloat Color { get { if (m_denumerator == 0) return ColorFloat.Black; return m_numerator / m_denumerator; } } public override string ToString() { return String.Format("num: {0}, denum: {1}", m_numerator, m_denumerator); } } public abstract class InterpolateResampler : FilterResampler { protected PixelInterpolator[,] m_pixels; protected OverlayCorrector m_overlay_corrector; protected override ColorFloat ResamplePixel(int a_x, int a_y) { return Gamma.SRGBToLinear(m_pixels[a_x, a_y].Color); } internal override void RenderStart(RenderStartPhase a_phase) { if (a_phase == RenderStartPhase.PrepareObjectToRender) { m_pixels = new PixelInterpolator[Film.Width, Film.Height]; m_overlay_corrector = OverlayCorrector.Create( OverlayMethod.Mirror, Film.Width, Film.Height, Filter.Ray.Ceiling()); } } } public class LocalGridResampler : InterpolateResampler { private double[,] m_weights; [YAXNode] public int Subresolution = 3; private double m_scale; internal override void RenderStart(RenderStartPhase a_phase) { base.RenderStart(a_phase); if (a_phase == RenderStartPhase.PrepareObjectToRender) { var sampler = Film.Sampler as UniformSampler; if (sampler == null) throw new InvalidOperationException(); if (Subresolution % 2 == 0) throw new InvalidOperationException(); m_scale = Subresolution * sampler.Subresolution; PrepareWeights(); } } private void PrepareWeights() { List<List<PixelWeight>> horz_weights, vert_weights; ResizerResampler.PrecalculateWeights((int)m_scale, (int)m_scale, 1, 1, out horz_weights, out vert_weights, Filter, false); double[] weights = horz_weights[0].Select(pw => pw.Weight).ToArray(); Debug.Assert(weights.Length % 2 == 1); m_weights = new double[weights.Length, weights.Length]; for (int x = 0; x < weights.Length; x++) { double wx = weights[x]; for (int y = 0; y < weights.Length; y++) { double wy = weights[y]; m_weights[x, y] = wx * wy; } } } private double GetWeight(double a_dx, double a_dy) { int x = (a_dx * m_scale).Round(); x += (m_weights.GetLength(0) - 1) / 2; if (x < 0) return 0; else if (x >= m_weights.GetLength(0)) return 0; int y = (a_dy * m_scale).Round(); y += (m_weights.GetLength(1) - 1) / 2; if (y < 0) return 0; else if (y >= m_weights.GetLength(1)) return 0; return m_weights[x, y]; } internal override void SetRayColor(Vector2 a_pixel, ColorFloat a_color) { a_color = Gamma.LinearToSRGB(a_color); int start_x = (int)Math.Floor(a_pixel.X - Filter.Ray); int end_x = (int)Math.Ceiling(a_pixel.X + Filter.Ray) - 1; int start_y = (int)Math.Floor(a_pixel.Y - Filter.Ray); int end_y = (int)Math.Ceiling(a_pixel.Y + Filter.Ray) - 1; for (int y = start_y; y <= end_y; y++) { int cy = m_overlay_corrector.CorrectY(y); for (int x = start_x; x <= end_x; x++) { int cx = m_overlay_corrector.CorrectX(x); double w = GetWeight((x + 0.5) - a_pixel.X, (y + 0.5) - a_pixel.Y); m_pixels[cx, cy].Collect(a_color, w); } } #if DEBUG for (int y = start_y - 1; y <= end_y + 1; y++) { Debug.Assert(GetWeight((start_x + 0.5 - 1) - a_pixel.X, (y + 0.5) - a_pixel.Y).IsAlmostEquals(0)); Debug.Assert(GetWeight((end_x + 0.5 + 1) - a_pixel.X, (y + 0.5) - a_pixel.Y).IsAlmostEquals(0)); } for (int x = start_x; x <= end_x; x++) { Debug.Assert(GetWeight((x + 0.5) - a_pixel.X, (start_y + 0.5 - 1) - a_pixel.Y).IsAlmostEquals(0)); Debug.Assert(GetWeight((x + 0.5) - a_pixel.X, (end_y + 0.5 + 1) - a_pixel.Y).IsAlmostEquals(0)); } #endif } public override ResamplerType ResamplerType { get { return ResamplerType.LocalGrid; } } }
Brak komentarzy:
Prześlij komentarz