UE 5.3 OpenVDB代码浅析
前言
UE官方支持VDB的最低版本是5.3, 用的是OpenVDB v8.1.0版本(此版本不支持NanoVDB, NanoVDB的支持得在 OpenVDB v9.0.0版本)
UE5.0、5.1有第三方插件unreal-vdb支持了OpenVDB及NanoVDB
接下来分析的是基于UE5.3官方支持的VDB功能
1. 导入&存储
1. USparseVolumeTextureFactory类, 导入或重导VDB, 包括UAnimatedSparseVolumeTexture及UStaticSparseVolumeTexture
2. 核心导入方法是ConvertOpenVDBToSparseVolumeTexture, 序列和静态VDB都用该方法处理导入
3. 每一个VDB文件都会对应一个FTextureData类实例, 用来缓存导入的数据
FSparseVolumeTextureDataProviderOpenVDB, 在Initialize方法里包装了FTextureData创建信息, 而后Create时生成FTextureData
1.1 页表和TileData数量设置
1.1.1 PageTable数量设置
页表的数量是分辨率内积直接得到, 而页表分辨率是默认构造FHeader时(用CreateInfo里的VirtualVolumeAABB等信息)计算, 每个页表元素是uint32
#define SPARSE_VOLUME_TILE_RES 16
PageTableVolumeResolution.xyz = VirtualVolumeSize.xyz / SPARSE_VOLUME_TILE_RES;
PageTableVolumeResolution.xyz = RoundUpToPowerOfTwo(PageTableVolumeResolution.xyz);
1.1.2 TileData数量设置
TileDataA及TileDataB, 每个TileData是uint8
TileData数量 = 非空Tile数(即有效页数)* 每PaddedTile所含的Voxel数量 * 每Voxel所占用uint8的数量
每个PaddedTile的Voxel数量为
NumVoxelsPerPaddedTile = (16+2*1)*(16+2*1)*(16+2*1);
也就是SPARSE_VOLUME_TILE_RES+上下左右前后各自补一个Voxel
每个Voxel占的大小基于VDB导入设置里属性来
FormatSize[2] = {GPixelFormats[Header.AttributesFormats[0]].BlockBytes, GPixelFormats[Header.AttributesFormats[1]].BlockBytes}
若如下图导入配置, 则FormatSize[0] = 1Byte, FormatSize[1] = 0Byte
Tips: 不支持三通道属性, 若面板每属性配置3通道值, 该属性将占用4通道空间
1.2 页表及TilesData的压缩, 及存储流式数据
FResource::Build方法中, 数据导入完成后, 先压缩数据进StreamableBulkData, 而后存储StreamableBulkData至StreamableMipLevels. (SparseVolumeTexture.cpp)
1.2.1 页表的压缩
CompressPageTable方法, 压缩页表数据进StreamableBulkData
分配两倍非空页表线性连续空间M、N, M每空间用32bit存储三维坐标(X/Y/Z分别占用11/11/10位), N每空间用32bit存储物理Tile的索引
1.2.2 TileData的压缩
CompressTiles方法, 压缩TileData进StreamableBulkData
先将默认FallbackValue从Vector4转成uint8值数组, 用于后续直接与PhysicalTileData数据比对, 过滤掉空Voxel
对非空Voxel全局索引按字(Word)编码成 Key = VIndex / 32, Value = 1u << VIndex % 32
const int64 WordIndex = TileIndex * SVT::NumOccupancyWordsPerPaddedTile + (VoxelIndex / 32);
OccupancyBits[AttributesIdx][WordIndex] |= 1u << (static_cast<uint32>(VoxelIndex) % 32u);
同时统计每Tile下非空Voxel数量
PrefixSums[AttributesIdx][TileIndex]++;
Tile非空体素统计及置位
压缩所有非空Voxel数据
压缩数据内存布局, 从左到右依次存储
OccupancyBits描述物理Volume全局体素非空置位表, 即非空的体素, 索引所在字(Word), 并对其相应bit置位, bit的数量等同于所有Voxel包括非空的数量
TileDataOffsets描述Voxel数量偏移, 俩临近TileDataOffset差表示前一个TileData的非空Voxel数量
TileData描述每Voxel的密度、温度等数据
OccupancyBits | TileDataOffsets | TileData | |||
---|---|---|---|---|---|
OccupancyBitsOffset[0] | OccupancyBitsOffset[1] | TileDataOffsetsOffset[0] | TileDataOffsetsOffset[1] | TileDataOffset[0] | TileDataOffset[1] |
OccupancyBitsSize[0] | OccupancyBitsSize[1] | TileDataOffsetsSize[0] | TileDataOffsetsSize[1] | TileDataSize[0] | TileDataSize[1] |
1.2.3 存储流式数据
FResources::Cache(SparseVolumeTexture.cpp)保存磁盘前, 进一步压缩BulkData
2. 纹理
2.1 数据更新至RHI
FStreamingManager::AddInternal方法(SparseVolumeTextureStreamingManager.cpp)中, 拷StreamableMipLevels数据, 通过RootTileUploader上传至CS, 以更新TileDataTextureRHI
2.2 渲染页表及Tile纹理
Shader文件 UpdateSparseVolumeTexture.usf
2.3 采样页表及Tile纹理
用ComputeShader, 渲染至LightTexture
采样HeterogeneousVolumeRadiance纹理, 混合至SceneColor
3. 其他
UE5.3 SVT的一些限制
页表纹理大小限制2GB内, SVT的X、Y值禁止超过32K, Z值禁止超过16K
这个通过上面的分析也好理解, 上面提到VDB的数据在UE5.3里是分Tile, 每Tile分辨率是
4. 小结
目前重心探索VDB的压缩存储方面, 具体渲染没过多深入, 一般镂空Voxel大概占比30%~50%
目前测试的数据不多, 动态的VDB暂没深究, 测试资产包网盘地址见 CloudPackVDB
压缩前[vdb] | 打包前[内部压缩]Mip0 | 打包后[打包压缩, uasset] | 压缩比 | |
---|---|---|---|---|
静态云样例0 | 19.2MB | 约10MB | 4.39MB | 22.86% |
样例, 原始数据19.2MB, 截图为TileData及PageTable数据压缩后, 打包压缩前
5. 参考
- OpenVDB说明文档
- UE5.3 Sparse Volume Texture