Ptex源码学习笔记-1

时间:2023-03-09 08:19:37
Ptex源码学习笔记-1

  Ptex是Walt Disney Animation Studios开发的纹理映射工具。在看一个叫appleseed的渲染器时看到他支持这种纹理,所以就查看一下,发现比较轻量,所以就想趁此机会学习下。

  Ptex开源代码主要包含该格式的定义、IO、缓存和一些常见filter。头文件Ptexture.h定义了纹理的数据结构和所有操作的API,类定义都是抽象类,ctor和dtor都不是public,生成对象需要通过其他接口来获取实例,或者调用静态函数进行操作,释放对象不能直接调用dtor,统一由release函数delete或返回缓存,大多数对象可以跨线程共用,所以需要ref count,release时大多只是减ref。另外定义了一个用来管理对象的指针类PtexPtr,可以封装所有对象,并在超出作用域后自动release。

主要数据结构:

  1. struct Res,定义纹理的分辨率,按像素的log2存储,长度为8位int,其ctor可以用16位int作为参数,分高低8位储存uv,最小单元(texel)分辨率为1*1(ulog2 = vlog2 = 0),支持tile划分。
  2. FaceInfo,定义面信息,主要包括面的分辨率、相邻面和相邻边(对应相邻面的边ID),相邻面为32位int数组,一个面有4个相邻面,-1表示该相邻面为空,相邻边为8位uint,每个长度为2位,合并为8位保存,相邻面的遍历按边的ID从底部逆时针进行,边的左右顶底在该头文件的enum定义中(三角形没定义),如果相邻面是细分面(有多个面在当前面的同一边),则以逆时针遍历第一个遇到的面为整个相邻面的ID。

读取header信息:

  纹理信息保存在PtexTexture对象中,该对象不能由自己生成,只能通过静态open函数(PtextReader)或PtexCache接口生成。首先,锁mutex,将ptex文件以二进制形式打开(不转译回车和换行字符)并设置buffer,然后读取header,保存有各种校验信息和大小数目参数,以此对读取的文件进行校验,然后读取extended header,header中可以定义extended header的大小,struct ExtHeader的大小为40字节,所以在读取时应该以最小的为准,然后计算各个数据的起始位置,调用读取函数,包括:

  1. 读取面信息。读取过程分三步,首先从压缩的文件中读取FaceInfo,每次读取header中定义的faceinfosize或着16384字节(faceinfosize > BlockSize)为一个block,然后生成rfaceid(先按面在内存中的储存顺序分配id,然后以面的uv分辨率最小的那个作为依据降序排列面的顺序,该顺序就是rfaceid,面为常量则分辨率当作1),并建立rfaceid和faceid的映射关系,最后更新已使用的内存数。
  2. 读取面的常量像素值。读取过程分三步,第一和三步同上,第二步为alpha通道的预乘处理,先判断alpha通道的位置来确定需要预乘的通道,然后根据不同的像素存储类型归一化alpha值,最后将归一化的alpha值与需要预乘的通道的像素值相乘。
  3. 读取层次信息。读取过程分三步,第一和三步同上,第二步为层次定位,依次储存每一层次的开始位置。
  4. 读取编辑数据。读取过程分三步,首先确定编辑位置,然后根据编辑的类型读取,分为面数据和元数据两类,先读取对应的编辑header,然后保存编辑内容,编辑面数据还需要读取面的常数像素值,最后更新内存使用记录。

获取纹理数据:

  大部分数据可以通过简单返回获得,下面主要看几个比较复杂的数据:

  1. 获取元数据。元数据分meta data和large meta data两类,其大小分别定义在header和extheader中。两者的获取类似,都是建立kv映射,key为string表示名字,value为entry存储元数据,entry内保存了数据位置作为key,直接从内存将所需数据拷贝到指定位置,区别在于lmd的entry没有保存解压后的数据,而是数据的位置,只有在载入并引用后才能访问,有一个flag标识lmd。
  2. 获取一个面的纹理数据。有四种获取方式,获取最高分辨率的纹理、获取指定分辨率的纹理、获取硬盘上保存的最高分辨率的纹理、获取硬盘上保存的指定分辨率的纹理。前两种是把数据拷贝到指定buffer里,后两种是返回facedata的指针,在获取指定分辨率的纹理时,如果mipmap有对应的分辨率就直接从mipmap获取,否则就用最近的分辨率生存新的纹理。第一种:首先获取面的分辨率,然后调用第二种函数。第二种:使用PtexPtr管理对象,调用第四种函数获取facedata指针,如果是常数,则直接用该像素充填buffer;如果不是常数且分tile,则按tile(v major遍历)充填buffer,如果tile为常数,则直接用该像素充填tile的第一行,然后用第一行充填其他行,如果tile不是常数,则根据stride大小决定拷贝方式,如果stride等于行长度,则直接整体拷贝相应数据到tile,否则按行拷贝,如果不是常数且不分tile,则根据stride大小决定拷贝方式。第三种:获取level 0后根据level获取facedata,如果level没有被读取过,则初始化level,如果face没有和level建立关系,则根据face data header的编码方式初始化指定facedata并保存。第四种:主要处理mipmap,如果设置的分辨率不需要reduction则直接获取level 0的facedata,如果是对称reduction,则用u方向的reduction作为levelid直接查找facedata,如果不能查到,则需要动态reduction,首先在cache里查,如果没有则生存新的reduction,根据reduction的方向(u或v)来reduce,生存后存入reduction cache并返回facedata。
  3. 获取一个面的纹理的一个texel数据。有两种获取方式,获取最高分辨率纹理的texel、获取指定分辨率纹理的texel。两种方式基本类似,首先获取实际通道数,然后获取facedata,然后就可以得到像素值,然后根据第一通道位置来设置像素保存的偏移量,最后保存,如果像素值不是float,则转成float保存。二者的区别在获取facedata,第二种需要根据reduction来确定mipmap的level获取相应facedata。