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