创建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纹理实时生成程序的屏幕截图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/432202.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle RAC学习笔记:基本概念及入门

oracle 10g real application clusters introduction 1、什么是cluster一个cluster是由两个或是多个独立的、通过网络连接的servers组成的。几个硬件供应商多年以来提供了Cluster性能的各种需求。一些Clusters仅仅为了提供高可用性的&#xff0c;在当前活动的node发生故障时…

微信公众平台新增语义理解接口

微信公众平台语义理解接口正式对外开放。通过语义接口&#xff0c;接收用户发送的自然语言请求&#xff0c;让系统理解用户的说话内容。 微信语义理解接口提供从用户自然语言输入到结构化解析的技术实现&#xff0c;使用先进的自然语言处理技术给开发者提供一站式的语义解析方案…

Jetty 的工作原理以及与 Tomcat 的比较

http://www.ibm.com/developerworks/cn/java/j-lo-jetty/Jetty 目前的是一个比较被看好的 Servlet 引擎&#xff0c;它的架构比较简单&#xff0c;也是一个可扩展性和非常灵活的应用服务器&#xff0c;它有一个基本数据模型&#xff0c;这个数据模型就是 Handler&#xff0c;所…

最大子图形问题

CODEVS1159最大全0子矩阵 题目描述 Description 在一个0,1方阵中找出其中最大的全0子矩阵&#xff0c;所谓最大是指O的个数最多。思路&#xff1a;这个题最朴素的n^6的算法&#xff0c;超时美美的。。。然后想优化&#xff0c;从一个点向上方、左方、右方扩展&#xff0c;首先更…

仿真程序驱动视景问题

1. 坐标对应&#xff0c;东北天还是东南天 2. 跑道角度 3. 单位&#xff0c;厘米&#xff0c;米

Android--Facebook Login without LoginButton

1.引入facebook 工程lib&#xff0c;在Manifest中声明com.facebook.LoginActivity&#xff0c;facebook_app_id <activityandroid:name"com.facebook.LoginActivity"android:label"string/app_name"android:theme"android:style/Theme.Translucent…

【Direct3D游戏开发】——DirectInput 让世界动起来

其实我们是可以通过Windows消息和API取得键盘或者鼠标或者其他设备的输入信息&#xff0c;但这有个等待windows消息传送的延时&#xff0c;笔者试过直接在消息回调函数中相应键盘的上下左右消息去使场景中的模型进行旋转&#xff0c;感觉有明显的延时。这对于游戏玩家来说简直是…

UE4多线程

UE4中最基础的模型就是FRunnable和FRunnableThread&#xff0c;FRunnable抽象出一个可以执行在线程上的对象&#xff0c;而FRunnableThread是平台无关的线程对象的抽象。后面的篇幅会详细讨论这些基础设施。 1. FRunnable UE4为我们抽象FRunnable的概念&#xff0c;让我们指定…

使用.net备份和还原数据库

使用.net备份和还原数据库 原文:使用.net备份和还原数据库CSDN网友的提问http://community.csdn.net/Expert/TopicView3.asp?id4929678C#实现SQLSERVER2000数据库备份还原的两种方法: 方法一&#xff08;不使用SQLDMO&#xff09;&#xff1a;//////备份方法///SqlConnection …

初识JavaScript,感觉整个人都不好了。。。

学习web前端的开发已经将近一个月了&#xff0c;开发中的三个大兄弟——“html”、“css”、“JavaScript”&#xff0c;小哥我已经深入接触了前两位&#xff0c;并与他俩建立的深厚的友谊。在编写过程中&#xff0c;不能说达到各位大神的程度&#xff0c;但是对各个标签、若干…

ue4-控制台执行方法

1. 引擎单例派生类可直接调用方法 以下类的派生类中可以通过在方法上标记 UFUNCTION(Exec) 直接调用方法 Pawns, Player Controllers, Player Input, Cheat Managers, Game Modes, Game Instances, overriden Game Engine classes, and Huds should all work by just adding t…

通过回调函数阻止进程创建(验证结束,方案完全可行)

&#xff08;此方案完全可行&#xff0c;只是我忘掉了一步&#xff09; 虽然Vista之后版本有进程创建回调函数的Ex版&#xff0c;而且Ex版可以拦截进程创建&#xff0c; 但是由于在Ex版回调函数内用第三个参数的最后一个元素来阻止进程创建的话&#xff0c;可能会出现弹框&…

UE4 多线程使用tip

在GameThread线程之外的其他线程中&#xff0c;不允许做一下事情 1. 不要 spawning / modifying / deleting UObjects / AActors 2. 不要使用定时器 TimerManager 3. 不要使用任何绘制接口&#xff0c;例如 DrawDebugLine 4. 如果想在主线程中异步处理&#xff08;也就是分帧…

linux shell if

linux_if 参数 shell 编程中使用到得if语句内判断参数 –b 当file存在并且是块文件时返回真 -c 当file存在并且是字符文件时返回真 -d 当pathname存在并且是一个目录时返回真 -e 当pathname指定的文件或目录存在时返回真 -f 当file存在并且是正规文件时返回真 -g 当由pathname指…

UE4异步操作总结

虚幻本身有提供一些对异步操作的封装&#xff0c;这里是对这段时间接触到的“非同步”的操作进行的总结。 当前使用的UE4版本为4.18.2。 在虚幻的游戏制作中&#xff0c;如果不是特殊情况一般不会有用到线程的时候。但是由于实际上虚幻内部是有着许多线程机制的。 例如通常的…

JavaScript模式读书笔记 第3章 字面量和构造函数

1&#xff0c;对象字面量 -1&#xff0c;Javascript中所创建的自定义对象在任务时候都是可变的。可以从一个空对象开始&#xff0c;根据需要增加函数。对象字面量模式可以使我们在创建对象的时候向其添加函数。<script>//定义空对象var dog {};//对空对象添加方法dog.na…

如何在UE4中创建线程

FRunnable和FRunnableThread方法对于大多数问题来说无疑是一个可行的解决方案。 但是&#xff0c;在创建许多任务时&#xff0c;您可能会达到CPU可以处理的并发上限&#xff0c;此时并发线程实际上会在争用CPU时间时相互阻碍。 然后可能值得查看FQueuedThreadPool以限制任务可用…

[Leveldb源码剖析疑问]-block_builder.cc之Add函数

Add函数是给一个Data block中添加对应的key和value,函数源码如下,其中有一处不理解: L30~L34是更新last_key_的,不理解这里干嘛不直接last_key_ key.ToString(); 写成 // Update state last_key_.resize(shared); last_key_.append(key.data() shared, non_shared); assert(S…

UE4多线程任务系统详解

首先&#xff0c;了解一下该系统重要的数据类型. 1. FQueuedThreadPool&#xff1a;虚基类&#xff0c;队列线程池, FQueuedThreadPoolBase继承自FQueuedThreadPool&#xff0c; FQueuedThreadPoolBase维护了一个TArray<IQueuedWork*> QueuedWork(需要被执行的工作)…