MIPMap 类
// MIPMap Declarations
typedef enum {
TEXTURE_REPEAT,
TEXTURE_BLACK,
TEXTURE_CLAMP
} ImageWrap;
template <typename T> class MIPMap {
public:
// MIPMap Public Methods
MIPMap() { pyramid = NULL; width = height = nLevels = 0; }
MIPMap(uint32_t xres, uint32_t yres, const T *data, bool doTri = false,
float maxAniso = 8.f, ImageWrap wrapMode = TEXTURE_REPEAT);
~MIPMap();
uint32_t Width() const { return width; }
uint32_t Height() const { return height; }
uint32_t Levels() const { return nLevels; }
const T &Texel(uint32_t level, int s, int t) const;
T Lookup(float s, float t, float width = 0.f) const;
T Lookup(float s, float t, float ds0, float dt0,
float ds1, float dt1) const;
private:
// MIPMap Private Methods
struct ResampleWeight;
ResampleWeight *resampleWeights(uint32_t oldres, uint32_t newres) {
Assert(newres >= oldres);
ResampleWeight *wt = new ResampleWeight[newres];
float filterwidth = 2.f;
for (uint32_t i = 0; i < newres; ++i) {
// Compute image resampling weights for _i_th texel
float center = (i + .5f) * oldres / newres;
wt[i].firstTexel = Floor2Int((center - filterwidth) + 0.5f);
for (int j = 0; j < 4; ++j) {
float pos = wt[i].firstTexel + j + .5f;
wt[i].weight[j] = Lanczos((pos - center) / filterwidth);
}
// Normalize filter weights for texel resampling
float invSumWts = 1.f / (wt[i].weight[0] + wt[i].weight[1] +
wt[i].weight[2] + wt[i].weight[3]);
for (uint32_t j = 0; j < 4; ++j)
wt[i].weight[j] *= invSumWts;
}
return wt;
}
float clamp(float v) { return Clamp(v, 0.f, INFINITY); }
RGBSpectrum clamp(const RGBSpectrum &v) { return v.Clamp(0.f, INFINITY); }
SampledSpectrum clamp(const SampledSpectrum &v) { return v.Clamp(0.f, INFINITY); }
T triangle(uint32_t level, float s, float t) const;
T EWA(uint32_t level, float s, float t, float ds0, float dt0, float ds1, float dt1) const;
// MIPMap Private Data
bool doTrilinear;
float maxAnisotropy;
ImageWrap wrapMode;
struct ResampleWeight {
int firstTexel;
float weight[4];
};
BlockedArray<T> **pyramid;
uint32_t width, height, nLevels;
#define WEIGHT_LUT_SIZE 128
static float *weightLut;
};
类的作用:
(看《PBRT_V2 总结记录 <49> ImageTexture》可以知道,在ImageTexture::Evaluate() 中 利用uv坐标直接 调用 mipmap->Lookup 来查找图片的值,也提及到 mipmap->Lookup 中会做过滤。
那么 MIPMap 提供了2个方法来进行过滤,第一种是 trilinear,第二种是 elliptically weighted averaging。
这个MIPMap 与 OpenGL中Texture中的MIpMap十分相似,也是用一个金字塔来管理图片,在金字塔最底层是原图,每上升一层,分辨率缩小一半,金字塔的好处(看蓝色的) MIpMap 是一个模板类,ImageWrap 与 OpenGL的 texture wrap mode 也十分类似,只要是当uv坐标超过[0,1]的时候,怎么处理 )
The MIPMap class implements two methods for efficient texture filtering with spatially
varying filter widths. The first, trilinear interpolation, is fast and easy to implement and
has been widely used for texture filtering in graphics hardware. The second, elliptically
weighted averaging, is slower and more complex, but returns extremely high-quality results.
Figure 10.1 shows the aliasing errors that result from ignoring texture filtering and
just bilinearly interpolating texels from the most detailed level of the image map. Figure
10.10 shows the improvement from using the triangle filter and the EWA algorithm
instead.
To limit the potential number of texels that need to be accessed, both of these filtering
methods use an image pyramid(金字塔) of increasingly lower-resolution prefiltered versions of the
original image to accelerate their operation. The original image texels are at the bottom
level of the pyramid, and the image at each level is half the resolution of the previous
level, up to the top level, which has a single texel representing the average of all of the
texels in the original image. This collection of images needs at most 1/3 more memory
than storing the most detailed level alone and can be used to quickly find filtered values
over large regions of the original image. The basic idea behind the pyramid(金字塔) is that if a
large area of texels needs to be filtered a reasonable approximation is to use a higher level
of the pyramid and do the filtering over the same area there, accessing many fewer texels.
MIPMap is a template class and is parameterized by the data type of the image texels. pbrt
creates MIPMaps of both RGBSpectrum and float images; float MIP maps are used for
representing directional distributions of intensity from goniometric light sources, for
example. The MIPMap implementation requires that the type T support just a few basic
operations, including addition and multiplication by a scalar.
The ImageWrap enumerant, passed to the MIPMap constructor, specifies the desired behavior
when the supplied texture coordinates are not in the legal [0, 1] range.
1. 构造函数
// MIPMap Method Definitions
template <typename T>
MIPMap<T>::MIPMap(uint32_t sres, uint32_t tres, const T *img, bool doTri,
float maxAniso, ImageWrap wm) {
doTrilinear = doTri;
maxAnisotropy = maxAniso;
wrapMode = wm;
T *resampledImage = NULL;
if (!IsPowerOf2(sres) || !IsPowerOf2(tres)) {
// Resample image to power-of-two resolution
uint32_t sPow2 = RoundUpPow2(sres), tPow2 = RoundUpPow2(tres);
// Resample image in $s$ direction
ResampleWeight *sWeights = resampleWeights(sres, sPow2);
resampledImage = new T[sPow2 * tPow2];
// Apply _sWeights_ to zoom in $s$ direction
for (uint32_t t = 0; t < tres; ++t) {
for (uint32_t s = 0; s < sPow2; ++s) {
// Compute texel $(s,t)$ in $s$-zoomed image
resampledImage[t*sPow2+s] = 0.;
for (int j = 0; j < 4; ++j) {
int origS = sWeights[s].firstTexel + j;
if (wrapMode == TEXTURE_REPEAT)
origS = Mod(origS, sres);
else if (wrapMode == TEXTURE_CLAMP)
origS = Clamp(origS, 0, sres-1);
if (origS >= 0 && origS < (int)sres)
resampledImage[t*sPow2+s] += sWeights[s].weight[j] *
img[t*sres + origS];
}
}
}
delete[] sWeights;
// Resample image in $t$ direction
ResampleWeight *tWeights = resampleWeights(tres, tPow2);
T *workData = new T[tPow2];
for (uint32_t s = 0; s < sPow2; ++s) {
for (uint32_t t = 0; t < tPow2; ++t) {
workData[t] = 0.;
for (uint32_t j = 0; j < 4; ++j) {
int offset = tWeights[t].firstTexel + j;
if (wrapMode == TEXTURE_REPEAT) offset = Mod(offset, tres);
else if (wrapMode == TEXTURE_CLAMP) offset = Clamp(offset, 0, tres-1);
if (offset >= 0 && offset < (int)tres)
workData[t] += tWeights[t].weight[j] *
resampledImage[offset*sPow2 + s];
}
}
for (uint32_t t = 0; t < tPow2; ++t)
resampledImage[t*sPow2 + s] = clamp(workData[t]);
}
delete[] workData;
delete[] tWeights;
img = resampledImage;
sres = sPow2;
tres = tPow2;
}
width = sres;
height = tres;
// Initialize levels of MIPMap from image
nLevels = 1 + Log2Int(float(max(sres, tres)));
pyramid = new BlockedArray<T> *[nLevels];
// Initialize most detailed level of MIPMap
pyramid[0] = new BlockedArray<T>(sres, tres, img);
for (uint32_t i = 1; i < nLevels; ++i) {
// Initialize $i$th MIPMap level from $i-1$st level
uint32_t sRes = max(1u, pyramid[i-1]->uSize()/2);
uint32_t tRes = max(1u, pyramid[i-1]->vSize()/2);
pyramid[i] = new BlockedArray<T>(sRes, tRes);
// Filter four texels from finer level of pyramid
for (uint32_t t = 0; t < tRes; ++t)
for (uint32_t s = 0; s < sRes; ++s)
(*pyramid[i])(s, t) = .25f *
(Texel(i-1, 2*s, 2*t) + Texel(i-1, 2*s+1, 2*t) +
Texel(i-1, 2*s, 2*t+1) + Texel(i-1, 2*s+1, 2*t+1));
}
if (resampledImage) delete[] resampledImage;
// Initialize EWA filter weights if needed
if (!weightLut) {
weightLut = AllocAligned<float>(WEIGHT_LUT_SIZE);
for (int i = 0; i < WEIGHT_LUT_SIZE; ++i) {
float alpha = 2;
float r2 = float(i) / float(WEIGHT_LUT_SIZE - 1);
weightLut[i] = expf(-alpha * r2) - expf(-alpha);
}
}
}
作用:
(在构造函数里面resize image,并且把 MIPMap 每一层的数据都计算好,存储到 BlockedArray<T> **pyramid; 中)
In the constructor, the MIPMap copies the image data provided by the caller, resizes the
image if necessary to ensure that its resolution is a power of two in each direction, and
initializes a lookup table used by the elliptically weighted average filtering method in
Section 10.4.4. It also records the desired behavior for texture coordinates that fall outside
of the legal range in the wrapmode argument.
细节
a. uint32_t sPow2 = RoundUpPow2(sres), tPow2 = RoundUpPow2(tres);
作用:
(传入的图片的分辨率不是2的次幂的话,就直接向上升成2的次幂)
Implementation of an image pyramid is somewhat easier if the resolution of the original
image is an exact power of two in each direction; this ensures that there is a straightforward relationship between the level of the pyramid and the number of texels at that level.
If the user has provided an image where the resolution in one or both of the dimensions
is not a power of two, then the MIPMap constructor starts by resizing the image up to the
next power-of-two resolution greater than the original resolution before constructing the
pyramid.
b. ResampleWeight *sWeights = resampleWeights(sres, sPow2);
作用:
(因为图片分辨率要 resize, resampleWeights 的作用就是,记录每一个新的 texel 关联哪4个旧的 texel 索引,4个旧的texel对应的权值是多少, 注意一下,假如是U轴的,得到的4个点都是水平的,假如是V轴的,得到的4个点是垂直的)
Reconstructing the original image function and sampling it at a new texel’s position is
mathematically equivalent to centering the reconstruction filter kernel at the new texel’s
position and weighting the nearby texels in the original image appropriately. Thus, each
new texel is a weighted average of a small number of texels in the original image.
The MIPMap::resampleWeights() method determines which original texels contribute to
each new texel and what the values are of the contribution weights for each new texel.
It returns the values in an array of ResampleWeight structures for all of the texels in a
1D row or column of the image.
Figure 10.12: The computation to find the first texel inside a reconstruction filter’s support is slightly
tricky. Consider a filter centered around continuous coordinate 2.75 with width 2, as shown here. The
filter’s support covers the range [0.75, 4.75], although texel zero is outside the filter’s support: adding
0.5 to the lower end before taking the floor to find the discrete texel gives the correct starting texel,
number one.
Starting from this first contributing texel, this function loops over four texels, computing
each one’s offset to the center of the filter kernel and the corresponding filter weight. The
reconstruction filter function used to compute the weights, Lanczos(), is the same as the
one in LanczosSincFilter::Sinc1D().
struct ResampleWeight {
int firstTexel;
float weight[4];
};
ResampleWeight *resampleWeights(uint32_t oldres, uint32_t newres) {
Assert(newres >= oldres);
ResampleWeight *wt = new ResampleWeight[newres];
float filterwidth = 2.f;
for (uint32_t i = 0; i < newres; ++i) {
// Compute image resampling weights for _i_th texel
float center = (i + .5f) * oldres / newres;
wt[i].firstTexel = Floor2Int((center - filterwidth) + 0.5f);
for (int j = 0; j < 4; ++j) {
float pos = wt[i].firstTexel + j + .5f;
wt[i].weight[j] = Lanczos((pos - center) / filterwidth);
}
// Normalize filter weights for texel resampling
float invSumWts = 1.f / (wt[i].weight[0] + wt[i].weight[1] +
wt[i].weight[2] + wt[i].weight[3]);
for (uint32_t j = 0; j < 4; ++j)
wt[i].weight[j] *= invSumWts;
}
return wt;
}
c.
T *resampledImage = NULL;
if (!IsPowerOf2(sres) || !IsPowerOf2(tres)) {
// Resample image to power-of-two resolution
uint32_t sPow2 = RoundUpPow2(sres), tPow2 = RoundUpPow2(tres);
// Resample image in $s$ direction
ResampleWeight *sWeights = resampleWeights(sres, sPow2);
resampledImage = new T[sPow2 * tPow2];
// Apply _sWeights_ to zoom in $s$ direction
for (uint32_t t = 0; t < tres; ++t) {
for (uint32_t s = 0; s < sPow2; ++s) {
// Compute texel $(s,t)$ in $s$-zoomed image
resampledImage[t*sPow2+s] = 0.;
for (int j = 0; j < 4; ++j) {
int origS = sWeights[s].firstTexel + j;
if (wrapMode == TEXTURE_REPEAT)
origS = Mod(origS, sres);
else if (wrapMode == TEXTURE_CLAMP)
origS = Clamp(origS, 0, sres-1);
if (origS >= 0 && origS < (int)sres)
resampledImage[t*sPow2+s] += sWeights[s].weight[j] *
img[t*sres + origS];
}
}
}
delete[] sWeights;
// Resample image in $t$ direction
ResampleWeight *tWeights = resampleWeights(tres, tPow2);
T *workData = new T[tPow2];
for (uint32_t s = 0; s < sPow2; ++s) {
for (uint32_t t = 0; t < tPow2; ++t) {
workData[t] = 0.;
for (uint32_t j = 0; j < 4; ++j) {
int offset = tWeights[t].firstTexel + j;
if (wrapMode == TEXTURE_REPEAT) offset = Mod(offset, tres);
else if (wrapMode == TEXTURE_CLAMP) offset = Clamp(offset, 0, tres-1);
if (offset >= 0 && offset < (int)tres)
workData[t] += tWeights[t].weight[j] *
resampledImage[offset*sPow2 + s];
}
}
for (uint32_t t = 0; t < tPow2; ++t)
resampledImage[t*sPow2 + s] = clamp(workData[t]);
}
delete[] workData;
delete[] tWeights;
img = resampledImage;
sres = sPow2;
tres = tPow2;
}
作用:
(上次的这一段代码,作用就是计算 resize后的 image的所有texel值。
最主要思路,先判断图片的大小是否是2的次幂,如果不是,那么就进行resize,怎么resize呢,
首先判断 new image 的 一行 的每一个 texel 关联 old image 的 哪4个水平上的texel,这4个texel的权重分别是什么,那么new image 的每一个texel会保存4个索引和4个权重, 对应的,new image 的一列 的每一个 texel 关联 old image 的哪4个 垂直上的texel,这4个texel的权重分别是什么,也会保存索引和权重。这时候 每一个 new image的 每一个texel 的信息都计算好了,就可以进行resize了,先计算new image的每一行的 新的 texel 值,再计算 每一列的 新的 texel 值,值得注意的是,计算每一列的值得时候,会把之前计算好的每一行的值都考虑,这样,new image 的所有的 texel都计算好了)
The MIPMap uses a separable reconstruction filter for this task; recall from Section 7.7
that separable filters can be written as the product of one-dimensional filters: f (x, y) =
f (x)f (y). One advantage of using a separable filter is that if we are using one to resample
an image from one resolution (s , t) to another (s‘ , t’), then we can implement the
resampling as two one-dimensional resampling steps, first resampling in s to create an
image of resolution (s‘ , t) and then resampling that image to create the final image
of resolution (s’ , t‘). Resampling the image via two 1D steps in this manner simplifies
implementation and makes the number of texels accessed for each texel in the final image
a linear function of the filter width, rather than a quadratic one.
d.
nLevels = 1 + Log2Int(float(max(sres, tres)));
pyramid = new BlockedArray<T> *[nLevels];
// Initialize most detailed level of MIPMap
pyramid[0] = new BlockedArray<T>(sres, tres, img);
作用:
(pyramid[0]是 金字塔 最 底下 那一层 )
The base level of the MIP map, which holds the original data (or the resampled data, if
it didn’t originally have power-of-two resolutions), is initialized by the default Blocked
Array constructor.
e.
const T &MIPMap<T>::Texel(uint32_t level, int s, int t)
template <typename T>
const T &MIPMap<T>::Texel(uint32_t level, int s, int t) const {
Assert(level < nLevels);
const BlockedArray<T> &l = *pyramid[level];
// Compute texel $(s,t)$ accounting for boundary conditions
switch (wrapMode) {
case TEXTURE_REPEAT:
s = Mod(s, l.uSize());
t = Mod(t, l.vSize());
break;
case TEXTURE_CLAMP:
s = Clamp(s, 0, l.uSize() - 1);
t = Clamp(t, 0, l.vSize() - 1);
break;
case TEXTURE_BLACK: {
static const T black = 0.f;
if (s < 0 || s >= (int)l.uSize() ||
t < 0 || t >= (int)l.vSize())
return black;
break;
}
}
PBRT_ACCESSED_TEXEL(const_cast<MIPMap<T> *>(this), level, s, t);
return l(s, t);
}
作用:
(这个方法 就是 给 一个 level 和uv坐标,就给你返回 第level层的 对应uv坐标的 texel 值,)
MIPMap::Texel() returns a reference to the
texel value for the given discrete integer-valued texel position. As described earlier, if an
out-of-range texel coordinate is passed in, this method effectively repeats the texture over
the entire 2D texture coordinate domain by taking the modulus of the coordinate with
respect to the texture size, clamps the coordinates to the valid range so that the border
pixels are used, or returns a black texel for out-of-bounds coordinates.
f.
for (uint32_t i = 1; i < nLevels; ++i) {
// Initialize $i$th MIPMap level from $i-1$st level
uint32_t sRes = max(1u, pyramid[i-1]->uSize()/2);
uint32_t tRes = max(1u, pyramid[i-1]->vSize()/2);
pyramid[i] = new BlockedArray<T>(sRes, tRes);
// Filter four texels from finer level of pyramid
for (uint32_t t = 0; t < tRes; ++t)
for (uint32_t s = 0; s < sRes; ++s)
(*pyramid[i])(s, t) = .25f *
(Texel(i-1, 2*s, 2*t) + Texel(i-1, 2*s+1, 2*t) +
Texel(i-1, 2*s, 2*t+1) + Texel(i-1, 2*s+1, 2*t+1));
}
作用:
(这里就是创建每一层的 image 数据,而是 加权平均 上一层的 4个 texel,每一个texel的权值都是0.25)
For nonsquare images, the resolution in one direction must be clamped to one for the
upper levels of the image pyramid, where there is still downsampling to do in the larger
of the two resolutions. This is handled by the following max() calls:
The MIPMap uses a simple box filter to average four texels from the previous level to find
the value at the current texel.