创建mip纹理链
(1)
我們要做的是,根據原始紋理T0創建一系列的紋理(通常使用平均濾波):T1、T2…Tn,其中每個紋理的大小都是前一個紋理的1/4,即長度和寬度減半,如圖12.40所示。
要根據前一個mip紋理計算當前紋理中紋素的值,可以使用平均濾波器,即在RGB空間中,計算紋素(x,y)、(x+1,y)、(x+1,y+1)和(x,y+1)的平均值,然后將結果寫入到當前紋理中,如圖12.41所示。
| (點擊查看大圖)圖12.40? 根據原始紋理創建Mip紋理鏈 |
| (點擊查看大圖)圖12.41? 用于創建mip紋理的平均濾波器 |
創建Mip紋理鏈時需要遵守一些規則。首先,所有紋理都必須是方形的,且邊長為2的冪。這樣,可以在兩個軸上按相同的比例縮小,Mip紋理鏈末尾的最后一個紋理總是1×1的。另一個約定是,原始紋理為mip等級0,然后依次為mip等級1、2、3、4…n。例如,如果原始紋理的大小為256×256,則mip鏈中各個紋理的大小如表12.4所示。
表12.4?原始紋理為256×256時,各個Mip紋理的大小
| Mip紋理 | 紋理大小 | Mip紋理 | 紋理大小 |
| T0 | 256×256 | T5 | 8×8 |
| T1 | 128×128 | T6 | 4×4 |
| T2 | 64×64 | T7 | 2×2 |
| T3 | 32×32 | T8 | 1×1 |
| T4 | 16×16 | ? | ? |
除原始紋理外,mip等級數為log2T0的邊長。
在這個例子中,為log2256 = 8。要計算總紋理數,只需將上述結果加1。因此計算總紋理數的公式為:log2T0的邊長+1。在這個例子中,總紋理數為9。
(2)
這里紋理要占用多少內存呢?圖12.42是實際的mip紋理,其中每個紋理的大小都是前一個紋理的1/4。由于每次都將紋理縮小到原來的1/4,因此占用的內存很少。下面計算在前一個例子中,紋理需要占用多少內存。
| 圖12.42? 實際的mip紋理 |
初始紋理的大小為256×256,需要占用2562個字。Mip紋理鏈需要的內存量如下:
要計算x為原始紋理所需內存量的多少倍,將其除以2562:
?
?
換句話說,加上mip紋理時,每個紋理需要的內存量只增加33%,代價很小,收益卻很高。
知道如何創建mip紋理以及它們占用的內存量后,需要拿出一種將其加入到引擎中的方法。作者苦思悶想,考慮過再次修改物體/多邊形數據結構,最后終于柳暗花明:只需將紋理指針指向mip紋理鏈而不是單個紋理即可。然而,在每個多邊形中設置一個Mipmapping標記,并在渲染多邊形時,將紋理指針視為執行位圖數組的指針,而不是單個位圖的指針。這樣,便可以使用原來的數據結構。請看圖12.43,以理解這里討論的方法。
| ?? |
| 圖12.43? 支持mip紋理鏈的紋理指針數組 |
?
需要對紋理進行Mipmapping時,原來的texture指針指向0級別mip紋理。據此分兩步創建mip紋理鏈:首先,分配一個位圖指針數組,并將原來的texture指針指向該數組;然后為每個mip紋理分配內存、生成mip紋理,并將位圖指針數組中的每個元素指向相應mip紋理的位圖。看起來令人迷惑,其實并非如此。
這種方案存在的唯一問題是,我們強行將物體/多邊形的位圖圖像指針指向了一個位圖指針數組。因此,必須非常小心,避免不知道紋理已被mipmap化的函數將0級紋理視為普通紋理;而知道紋理已被mipmap化的函數必須從mip紋理鏈中選擇正確的紋理。
(3)
int Generate_Mipmaps(BITMAP_IMAGE_PTR source, // 原始紋理 BITMAP_IMAGE_PTR *mipmaps, // 指向mip紋理數組的指針 float gamma) // gamma修正因子 { // 這個函數創建一個mip紋理鏈 // 調用該函數時,mipmap指向原始紋理 // 該函數退出時,mipmap指向一個指針數組,其中包含指向各個mip級紋理的指針 // 另外,該函數返回mip等級數,如果發生錯誤,則返回-1 // 最后一個參數gamma用于提高mip紋理的亮度,因為平均濾波器會降低亮度 // 1.01通常是不錯的選擇 // 該參數大于1.0時,將提高亮度;小于1.0時將降低亮度;為1.0時沒有影響 BITMAP_IMAGE_PTR *tmipmaps; // 局部變量,指向指針數組的指針 // 第一步:計算mip等級數 int num_mip_levels = logbase2ofx[source->width] + 1; // 為指針數組分配內存 tmipmaps = (BITMAP_IMAGE_PTR *)malloc(num_mip_levels * sizeof(BITMAP_IMAGE_PTR) ); // 將元素0指向原始紋理 tmipmaps[0] = source; // 設置寬度和高度(它們相同) int mip_width = source->width; int mip_height = source->height; // 使用平均濾波器生成各個mip紋理 for (int mip_level = 1; mip_level < num_mip_levels; mip_level++) { // 計算下一個mip紋理的大小 mip_widthmip_width = mip_width / 2; mip_heightmip_height = mip_height / 2; // 為位圖對象分配內存 tmipmaps[mip_level] = (BITMAP_IMAGE_PTR)malloc(sizeof(BITMAP_IMAGE) ); // 創建用于存儲mip紋理的位圖 Create_Bitmap(tmipmaps[mip_level],0,0, mip_width, mip_height, 16); // 讓位圖可用于渲染 SET_BIT(tmipmaps[mip_level]->attr, BITMAP_ATTR_LOADED); // 遍歷前一個mip紋理,使用平均濾波器創建當前mip紋理 for (int x = 0; x < tmipmaps[mip_level]->width; x++) { for (int y = 0; y < tmipmaps[mip_level]->height; y++) { // 需要計算4個紋素的平均值,這些紋素在前一個mip紋理中的位置如下: // (x*2, y*2)、(x*2+1, y*2)、(x*2,y*2+1)、(x*2+1,y*2+1) // 然后將計算結果寫入到當前mip紋理的(x, y)處 float r0, g0, b0, // 4個樣本紋素的R、G、B分量 r1, g1, b1, r2, g2, b2, r3, g3, b3; int r_avg, g_avg, b_avg; // 用于存儲平均值 USHORT *src_buffer = (USHORT *)tmipmaps[mip_level-1]->buffer, *dest_buffer = (USHORT *)tmipmaps[mip_level]->buffer; // 提取每個紋素的R、G、B值 _RGB565FROM16BIT( src_buffer[(x*2+0) + (y*2+0)*mip_width*2] , &r0, &g0, &b0); _RGB565FROM16BIT( src_buffer[(x*2+1) + (y*2+0)*mip_width*2] , &r1, &g1, &b1); _RGB565FROM16BIT( src_buffer[(x*2+0) + (y*2+1)*mip_width*2] , &r2, &g2, &b2); _RGB565FROM16BIT( src_buffer[(x*2+1) + (y*2+1)*mip_width*2] , &r3, &g3, &b3); // 計算平均值,并考慮gamma參數 r_avg = (int)(0.5f + gamma*(r0+r1+r2+r3)/4); g_avg = (int)(0.5f + gamma*(g0+g1+g2+g3)/4); b_avg = (int)(0.5f + gamma*(b0+b1+b2+b3)/4); // 根據5.6.5格式,對R、G、B值進行截取 if (r_avg > 31) r_avg = 31; if (g_avg > 63) g_avg = 63; if (b_avg > 31) b_avg = 31; // 寫入數據 dest_buffer[x + y*mip_width] = _RGB16BIT565(r_avg,g_avg,b_avg); } // end for y } // end for x } // end for mip_level // 讓mipmaps指向指針數組 *mipmaps = (BITMAP_IMAGE_PTR)tmipmaps; // 成功返回 return(num_mip_levels); } // end Generate_Mipmaps(4)
該函數接受三個參數,它們有些棘手。第一個參數是位圖指針source,可以是指向任何有效BITMAP_IMAGE對象的指針,切記它是一個指針。第二個參數要復雜些:
這是一個指向BITMAP_IMAGE指針的指針。假設物體(或多邊形)有一個texture指針,它指向一個BITMAP_IMAGE。現在的問題是,當mip紋理函數生成所有的mip紋理時,我們要將該指針指向mip紋理鏈本身,為此,需要知道該指針的地址,以便能夠修改它,因此需要一個** BITMAP_IMAGE。這就是需要一個指向指針的指針作為參數的原因。我們將讓該參數指向mip紋理指針數組。
這個函數提供了這樣的靈活性:可以將同一個對象作為參數source和mipmap。對于source參數,將其設置為指向該對象的指針;對于參數mipmaps,需要將其設置為指向該對象的指針的地址,這樣函數才能對其進行修改。假設您這樣調用該函數:
其中obj的類型為OBJECT4DV2_PTR,它有一個texture字段,該字段是一個指向BITMAP_IMAGE的指針(BITMAP_IMAGE_PTR)。仔細查看上述調用可以發現:我們將該指針作為第一個參數,同時將其地址作為第二個參數,因此對第二個參數進行修改時,將修改obj->texture指向的實際值,從而丟失它指向的原始數據。但事實上,并不會丟失任何數據,我們將把obj->texture指向的原始位圖用作mip紋理鏈中的第一個紋理。在刪除mip紋理鏈時,我們重新將該指針指向原始位圖。圖12.44說明了整個處理過程。
| (點擊查看大圖)圖12.44? 讓obj.texture指向mip紋理鏈和重新指向T0 |
回到mip紋理生成函數--它創建mip紋理鏈:為紋理指針數組分配內存;然后計算下一個mip等級的大小,為該mip等級分配內存,在RGB空間使用平均濾波器來創建該mip等級。這一過程不斷重復,直到mip等級的大小為1×1為止,然后函數結束。該函數的最后一個參數(默認值為1.01)用于設置gamma值。使用平均過濾器來不斷縮小圖像時,其亮度會逐漸降低;因此通常使用gamma因子來提高每個mip等級的亮度,確保所有mip等級的亮度一致。為說明這一點,作者編寫了一個演示程序,名為DEMOII12_10.CPP|H。用戶可以選擇不同的紋理圖,該程序將動態地創建mip紋理,并顯示它們,如圖12.45所示。另外,用戶還可以修改gamma值,以查看其影響。該程序的控制方式如下:
| 圖12.45? mip紋理實時生成程序的屏幕截圖 |
總結
- 上一篇: 10年都够用!传比亚迪在非洲购买了6座锂
- 下一篇: 苹果iPad在中国突然崩了:出货量暴跌近