创建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对象的指针,切记它是一个指针。第二个参数要复杂些:

 
  1. BITMAP_IMAGE_PTR *mipmaps; 

这是一个指向BITMAP_IMAGE指针的指针。假设物体(或多边形)有一个texture指针,它指向一个BITMAP_IMAGE。现在的问题是,当mip纹理函数生成所有的mip纹理时,我们要将该指针指向mip纹理链本身,为此,需要知道该指针的地址,以便能够修改它,因此需要一个** BITMAP_IMAGE。这就是需要一个指向指针的指针作为参数的原因。我们将让该参数指向mip纹理指针数组。

这个函数提供了这样的灵活性:可以将同一个对象作为参数source和mipmap。对于source参数,将其设置为指向该对象的指针;对于参数mipmaps,需要将其设置为指向该对象的指针的地址,这样函数才能对其进行修改。假设您这样调用该函数:

 
  1. Generate_Mipmaps(obj->texture, (BITMAP_IMAGE_PTR *)&obj->texture,1.01); 

其中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个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值