vault backup: 2024-02-21 19:07:59
This commit is contained in:
parent
eff20979fa
commit
56d69ef380
16
.obsidian/plugins/various-complements/data.json
vendored
16
.obsidian/plugins/various-complements/data.json
vendored
@ -182,6 +182,22 @@
|
||||
"lastUpdated": 1708493734216
|
||||
}
|
||||
}
|
||||
},
|
||||
"Rendering": {
|
||||
"Rendering": {
|
||||
"currentFile": {
|
||||
"count": 1,
|
||||
"lastUpdated": 1708508330502
|
||||
}
|
||||
}
|
||||
},
|
||||
"FUploadingVirtualTexture,用于从磁盘中流送上传": {
|
||||
"FUploadingVirtualTexture,用于从磁盘中流送上传": {
|
||||
"currentFile": {
|
||||
"count": 1,
|
||||
"lastUpdated": 1708510269638
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- UE4 Runtime Virtual Texture 实现机制及源码解析:https://zhuanlan.zhihu.com/p/143709152
|
||||
- UE Virtual Texture图文浅析:https://zhuanlan.zhihu.com/p/642580472
|
||||
- **UE Virtual Texture图文浅析**:https://zhuanlan.zhihu.com/p/642580472
|
||||
|
||||
## 相关概念
|
||||
- Virtual Texture:虚拟纹理,以下简称 VT
|
||||
@ -18,7 +18,72 @@ rating: ⭐
|
||||
- PageTable Texture:包含虚拟纹理页表数据的纹理资源,通过此纹理资源可查询 Physical Texture Page 信息。有些 VT 系统也叫 Indirection Texture,由于本文分析 UE4 VT 的内容,这里采用 UE4 术语。
|
||||
- PageTable Buffer:包含虚拟纹理页表数据内容的 GPU Buffer 资源。
|
||||
|
||||
相关类:
|
||||
### 地址映射
|
||||
地址映射在Virtual Texture是一个很重要的环节,就是如何将一个Virtual Texture的texel映射到Physical Texture的texel上,这里还需要适配当高分辨率的page没有加载的情况,需要得到已经加载的对应低分辨率的page地址。
|
||||
#### 四叉树映射
|
||||

|
||||
这里每个四叉树的节点的内容存的就是bias和scale,这样就可以将虚拟纹理的地址转换成物理纹理的地址了,假如没有找到,也可以用父节点的地址来得到低分辨率的。但是这里要找到对应的节点需要搜索这个四叉树,搜索的速度取决于树的高度,也就是mipmap的层级,在差的低mip的地址上效率会比较差。
|
||||
### Feedback Rendering
|
||||
在Virtual Texture中一个很重要的事情是要有一个可以决定加载哪些page的策略,这个策略就是要有一个叫Feedback Rendering的过程。这个可以设计为一个单独的pass,或者跟Pre-Z或者GBuffer同时。渲染生成的这张texture里面存的就是虚纹理的page坐标,mip level和可能的虚纹理id(用来应对多虚纹理的情况)。
|
||||

|
||||
|
||||
可以看到上图,由于page的变化在屏幕空间是比较低的,所以Feedback的RT是不需要全分辨率的,低分辨率渲染就可以了。对于半透明物体或者alpha test的物体,在Feedback Rendering的过程中只能当作是不透明物体来渲染,那样就会在屏幕像素上随机产生当前像素的可能结果。与之相类似的,如果一个屏幕像素用到了两个page,也会是随机出现一种在最后的结果RT上。这样虽然可以让所有需要的page都加载,但是,可能会遇到另外一个问题,那就是可能会发生这一帧加载的page,下一帧的时候被卸载掉了,然后再下一帧又要加载,这样会导致物理纹理一直在置换,即便屏幕像素并未改变,物理纹理的page也无法稳定下来。为了解决这个问题,需要设计一个调色板,对于半透明物体,间隔出现全透明或者不透明,对于多page的情况,则需要设计为间隔出现不同page的结果,这样就能同时加载所有page,并且保持稳定。但是,如果出现了多层半透明物体的叠加或者多个page的情况,如何设计一个合理的调色板变成了一个问题。这里可以考虑为每个像素匹配一个linked list,这个需要额外的硬件支持,叫structured append and consume buffers。
|
||||
|
||||
接着就是对这个Feedback的结果进行分析了,分析需要将Feedback的结果读回CPU,这里可以使用compute shader解析这个结果,然后输出到一个更简单的buffer上去:
|
||||

|
||||
|
||||
这样可以使回读操作更快,处理page更新也能更快。对于如何更新page,也需要策略,我们需要尽量不阻塞执行,异步的加载page,但是对于完全不存在任何一个mip的page,我们还是需要同步加载防止显示出错。在异步的过程中,我们需要对需要加载page设置优先级,比如需要加载的mip level和已经存在的mip level相差越大的优先级越高,被越多像素要求加载的page的优先级越高,这里需要设计一个完整的加载策略。
|
||||
|
||||
### Texture Poping
|
||||
由于page是异步加载的,这是有延时的,当加载的mip比当前显示的差很远的时候,我们渲染会使用新加载的更清晰的mip,这样我们会看到非常明显的跳变。假如我们用了软实现的Tri-linear Filtering,那么当加载的mip level跟当前显示的mip level相差很大的时候,需要做一个delay,等待中间的mip page的加载,然后再去更新。对于没有Tri-linear Filtering的实现,就得逐渐更新page,使得过度平滑。一个可能的方法是,upsample低分辨率的mip,直到高分辨率的mip加载。但是,这样仍然会出现跳变,由于采样的位置其实发生了突变。
|
||||

|
||||
|
||||
上图可以看到,当分辨率增加2倍之后,结果会发生很大的不同。解决的方案是,先把upsample的低分辨率page加载到一个物理纹理的page,当高分辨率的加载好了,插值过度那个物理纹理的page,这样采样的位置没有发生改变,只是每个像素的颜色在渐变,就不会有跳变出现了。
|
||||
|
||||
# UE5VirtualTexture相关实现
|
||||
为读向往大佬文章的学习笔记。
|
||||
|
||||
## VT 系统概述
|
||||
从原理上来说,VT 系统主要由 2 大阶段构成,VT 数据准备和 VT 运行时采样阶段。
|
||||
1. VT 数据准备阶段:
|
||||
1. 生成 VT feedback 数据
|
||||
2. 生成 VT 纹理数据,并更新到指定 VT Physical Texture 对应的 Page
|
||||
3. 根据 feedback 数据生成并更新 PageTable 数据
|
||||
2. VT 运行时采样阶段:
|
||||
1. 根据屏幕空间坐标以及相关信息生成 VT Physical Texture UV
|
||||
2. 对 VT Physical Texture 执行采样
|
||||
|
||||
UE4 的 RVT 基本上也是按照这个原理和流程来实现的,本文就按照这个顺序来详细讲解。在讲解之前,为了便于后续理解,先来了解下 UE5 RVT 的实现机制。
|
||||
|
||||
## UE5 RVT 实现机制概述
|
||||
IVirtualTexture 是 UE5 VT 最重要的接口,它是如何产生 VT 数据的接口,主要有两个抽象函数
|
||||
- RequestPageData,请求页面数据
|
||||
- ProducePageData,产生页面数据
|
||||
|
||||
在UE5中其子类有:
|
||||
- FLightmapPreviewVirtualTexture
|
||||
- FNullVirtualTextureProducer
|
||||
- FRuntimeVirtualTextureProducer
|
||||
- FUploadingVirtualTexture
|
||||
- FVirtualTextureAddressRedirect
|
||||
- FVirtualTextureLevelRedirector
|
||||
|
||||
对于 RVT 来说,实现此接口的是 FRuntimeVirtualTextureProducer,也就是作为运行时产生 Page 纹理数据的类,对于 SVT 来说,实现此接口的是 FUploadingVirtualTexture,用于从磁盘中流送上传 Page 纹理数据。
|
||||
FVirtualTextureSystem 是全局单件类,包含了 UE5 VT 系统中大部分核心逻辑和流程,驱动这个系统工作的是 Update 函数,分别在 PC/Console Pipeline 的 FDeferredShadingSceneRenderer::Render 和 Mobile Pipeline 的 FMobileSceneRenderer::Render 中调用.
|
||||
|
||||
在 VT 中只有 Diffuse 是远远不够的,在实际的着色过程中经常需要其它的纹理数据来进行光照计算,比如 Normal、Roughness、Specular 等等,UE4 的 RVT 使用了 Layer 的概念,每个 Layer 代表不同的 Physical Texture,在 UE4 中可以支持底色(Diffuse)、法线(Normal)、Roughness(粗糙度)、高光度(Specular)、遮罩(Mask)等不同内容的 VT,这些数据以 Pack 的方式保存在多张 Physical Texture 的不同通道中,在使用时通过 Unpack 以及组合的形式解算出来进行光照计算。这些 Physical Texture 的寻址信息保存在同一个 VT 上的 PageTable Texture 的不同颜色通道中,下文会详细描述。
|
||||
|
||||
UE4 RVT 中所使用的 GPU 资源有以下 3 种:
|
||||
- PageTable Buffer 用于在 CPU 端只写的 PageTable 数据。
|
||||
- PageTable Texture 用于在采样 VT 时获取指定 VT Physical Texture Page 数据,此资源数据不是在 CPU 端填充,而是由 PageTable Buffer 通过 RHICommandList 在 GPU 上填充。
|
||||
- VT Physical Texture 实际存储纹理数据的资源,通过 VT feedback 从磁盘或运行时动态生成纹理数据,并在 VT 数据准备阶段中更新到 VT Physical Texture 中。
|
||||
|
||||
其中 VT Physical Texture 资源包含在 FVirtualTexturePhysicalSpace 类中,PageTable Buffer/Texture 包含在 FVirtualTextureSpace 类中。
|
||||
|
||||
FVirtualTextureSystem的会提交请求最终会调用**FRuntimeVirtualTextureProducer::ProducePageData()** ,最后会在**FRuntimeVirtualTextureFinalizer::Finalize()** 中 调用 **RuntimeVirtualTexture::RenderPages()** 函数渲染到 VT Physical Texture 上。
|
||||
|
||||
## UE5中相关类
|
||||
- **FVirtualTextureSystem**:单例类,用于全局控制VT流程。
|
||||
- URuntimeVirtualTexture(UObject)
|
||||
- FRuntimeVirtualTextureRenderResource
|
||||
- UVirtualTexture(UObject)
|
||||
@ -377,6 +442,90 @@ void SampleRVT_{ParameterName}(in float3 WorldPosition, out bool bInsideVolume,
|
||||
}
|
||||
```
|
||||
|
||||
RVT的UV计算逻辑在VirtualTextureWorldToUV()中:
|
||||
```glsl
|
||||
float2 VirtualTextureWorldToUV(in float3 WorldPos, in float3 Origin, in float3 U, in float3 V)
|
||||
{
|
||||
float3 P = WorldPos - Origin;
|
||||
return saturate(float2(dot(P, U), dot(P, V)));
|
||||
}
|
||||
```
|
||||
从代码可以看出,根据当前像素的世界空间位置以及 RVT Volume 原点(Volume 左下角)、Volume 边界大小的 UV 范围(经过世界旋转变换的 XY 轴乘以 Volume 缩放-即 Volume 大小-的倒数,这些计算在 **URuntimeVirtualTexture::Initialize()** 中完成),求出当前像素在 RVT 中的 UV 坐标。
|
||||
|
||||
TextureComputeVirtualMipLevel() 函数计算 RVT 的 mipLevel,为了实现较好的混合效果,这里根据当前帧 Id 生成交错的随机 noise 扰动 level。
|
||||
```cpp
|
||||
int TextureComputeVirtualMipLevel(
|
||||
in out VTPageTableResult OutResult,
|
||||
float2 dUVdx, float2 dUVdy, float MipBias,
|
||||
float2 SvPositionXY,
|
||||
VTPageTableUniform PageTableUniform)
|
||||
{
|
||||
OutResult.dUVdx = dUVdx * PageTableUniform.SizeInPages;
|
||||
OutResult.dUVdy = dUVdy * PageTableUniform.SizeInPages;
|
||||
|
||||
// Always compute mip level using MipLevelAniso2D, even if VIRTUAL_TEXTURE_ANISOTROPIC_FILTERING is disabled
|
||||
// This way the VT mip level selection will come much closer to HW mip selection, even if we're not sampling the texture using anisotropic filtering const float ComputedLevel = MipLevelAniso2D(OutResult.dUVdx, OutResult.dUVdy, PageTableUniform.MaxAnisoLog2);
|
||||
|
||||
const float GlobalMipBias = GetGlobalVirtualTextureMipBias();
|
||||
#if VIRTUAL_TEXTURE_MANUAL_TRILINEAR_FILTERING
|
||||
const float Noise = 0.f;
|
||||
#else
|
||||
const float Noise = GetStochasticMipNoise(SvPositionXY);
|
||||
#endif
|
||||
|
||||
const float MipLevel = ComputedLevel + MipBias + GlobalMipBias + Noise;
|
||||
const float MipLevelFloor = floor(MipLevel);
|
||||
OutResult.MipLevelFrac = MipLevel - MipLevelFloor;
|
||||
|
||||
return (int)MipLevelFloor + int(PageTableUniform.vPageTableMipBias);
|
||||
}
|
||||
```
|
||||
|
||||
TextureLoadVirtualPageTableInternal 函数代码如下:
|
||||
```cpp
|
||||
void TextureLoadVirtualPageTableInternal(
|
||||
in out VTPageTableResult OutResult,
|
||||
Texture2D<uint4> PageTable0, Texture2D<uint4> PageTable1,
|
||||
VTPageTableUniform PageTableUniform,
|
||||
float2 UV, int vLevel)
|
||||
{
|
||||
OutResult.UV = UV * PageTableUniform.SizeInPages;
|
||||
|
||||
const uint vLevelClamped = clamp(vLevel, 0, int(PageTableUniform.MaxLevel));
|
||||
uint vPageX = (uint(OutResult.UV.x) + PageTableUniform.XOffsetInPages) >> vLevelClamped;
|
||||
uint vPageY = (uint(OutResult.UV.y) + PageTableUniform.YOffsetInPages) >> vLevelClamped;
|
||||
|
||||
OutResult.PageTableValue[0] = PageTable0.Load(int3(vPageX, vPageY, vLevelClamped));
|
||||
OutResult.PageTableValue[1] = PageTable1.Load(int3(vPageX, vPageY, vLevelClamped));
|
||||
|
||||
#if VIRTUAL_TEXTURE_MANUAL_TRILINEAR_FILTERING
|
||||
// Second page table for trilinear.
|
||||
const uint vLevelClamped2 = clamp(vLevel + 1, 0, int(PageTableUniform.MaxLevel));
|
||||
const uint vPageX2 = (uint(OutResult.UV.x) + PageTableUniform.XOffsetInPages) >> vLevelClamped2;
|
||||
const uint vPageY2 = (uint(OutResult.UV.y) + PageTableUniform.YOffsetInPages) >> vLevelClamped2;
|
||||
|
||||
OutResult.PageTableValue[2] = PageTable0.Load(int3(vPageX2, vPageY2, vLevelClamped2)); OutResult.PageTableValue[3] = PageTable1.Load(int3(vPageX2, vPageY2, vLevelClamped2));
|
||||
// Alternate requests to both mip levels
|
||||
if ((View.FrameNumber & 1u) == 0u)
|
||||
{ vLevel += 1; vPageX = vPageX2; vPageY = vPageY2; }#endif
|
||||
|
||||
// PageTableID packed in upper 4 bits of 'PackedPageTableUniform', which is the bit position we want it in for PackedRequest as well, just need to mask off extra bits
|
||||
OutResult.PackedRequest = PageTableUniform.ShiftedPageTableID;
|
||||
OutResult.PackedRequest |= vPageX;
|
||||
OutResult.PackedRequest |= vPageY << 12;
|
||||
|
||||
// Feedback always encodes vLevel+1, and subtracts 1 on the CPU side.
|
||||
// This allows the CPU code to know when we requested a negative vLevel which indicates that we don't have sufficient virtual texture resolution. const uint vLevelPlusOneClamped = clamp(vLevel + 1, 0, int(PageTableUniform.MaxLevel + 1));
|
||||
OutResult.PackedRequest |= vLevelPlusOneClamped << 24;
|
||||
}```
|
||||
|
||||
这个函数主要 2个作用,一是生成用于寻址 VT Physical Texture 的 PageTableValue,另一个是生成 feedback Request 数据,具体有以下几个步骤:
|
||||
|
||||
1. 根据 UV 寻址模式修正虚拟纹理坐标
|
||||
2. 根据当前 VT 的 Page 数量和上一步修正过的虚拟纹理坐标计算出 VT 坐标对应的 Page 坐标。
|
||||
3. 通过 Page 坐标加上 Page 的 XY 偏移,再根据 mipLevel,计算出 PageTable Texture 的 UV 坐标,然后使用这个 UV 坐标和 mipLevel 采样 PageTable Texture 得到在 Physical Texture 上的信息,保存在 PageTableValue 中,在接下来的流程中使用。
|
||||
4. 将第 3 步计算好的 PageTable Texture 的 Page 坐标和 mipLevel 保存在 VTPageTableResult 中,最后通过 StoreVirtualTextureFeedback 函数写入到 VT feedback Buffer 中。
|
||||
|
||||
VT还存在一个反馈机制,具体可以参考:[[#Pass1的补充VirtualTextureFeedback]]
|
||||
```c++
|
||||
/** GPU fence pool. Contains a fence array that is kept in sync with the FeedbackItems ring buffer. Fences are used to know when a transfer is ready to Map() without stalling. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user