1. 程式人生 > >PBRT_V2 總結記錄 MIPMap 和 MIPMap.Constructor

PBRT_V2 總結記錄 MIPMap 和 MIPMap.Constructor

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.