2011-12-19

Resampler LocalGrid

Budujemy dwuwymiarową (może też być jednowymiarowa) siatkę wag filtra. Środek filtru jest pozycji (0,0). Stąd dla filtru o promieniu 3 siatka musi mieć wymiary 7x7. Odległości z których pobieramy wartości filtra to -3, -2, -1, 0, 1, 2, 3. Zakładając, że wartości brzegowe w naszej siatce to zera możemy wymiary siatki zmniejszyć o 2.

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