原文链接:【UE4 C++】运行时虚拟纹理 RVT 踩坑实录 | 程序员阿Tu
最近在研究道路贴合地形绘制的问题,特别适合使用 UE 的 RVT ,这篇文章除了 RVT 的入门使用外,也探索了完全使用 C++ 进行 RVT 使用的可行性。
快速上手入门
RVT(Runtime Virtual Texture) 是 4.23 版本加入的新内容,因为概念比较多,推荐先看视频快速理解。这里推荐官方的视频教程,第二部分讲的就是 RVT 。同时我也转载了一个 YouTube 上讲的不错的教程,结合这两个教程可以很快的上手 RVT 功能。
RVT 功能需要预先开启并重启 UE ,这一步可能会等待一段时间,之后就可以开始实战了。
入门精炼
RVT 的过程可以用胶卷照相机拍照这一过程作为类比,可以更快速的理解 RVT 的核心思想。
Runtime Virtual Texture类比为相机胶卷底片,用于记录被相机拍下的内容Runtime Virtual Texture Volume类比为相机,用于放置相机并调整拍摄的区域Virtual Texture Material或Material中的Runtime Virtual Texture Output结点作为拍摄内容,告诉相机需要将区域内的哪些内容纳入拍摄Material中的Runtime Virtual Texture Sample结点可以实时获取拍摄内容。Actor或Landscape搭配Material,就可以展示拍摄内容。
核心内容主要在第四步,通过 Sample 获取内容后和自身逻辑进行融合,从而可以将两部分内容同时进行展示。
这部分根据教程,在 UE4 的 Editor 中可视化操作十分方便。但我需要 RVT 的区域非常多,无法一一枚举操作,因此下面主要是探索代码动态使用 RVT 。
纯代码使用 RVT
在实践的过程中发现,基于 4.26 版本,不修改源码无法达到纯代码使用 RVT 的目的,因此下面的内容包含了修改少量源码的操作。
1. Runtime Virtual Texture Volume
翻看源码,这部分是属于 ARuntimeVirtualTextureVolume,继承自 AActor。其中有一个 URuntimeVirtualTextureComponent 负责持有 RVT ,这个 Component 在 4.26 版本已经改为 public,但其持有的 URuntimeVirtualTexture 是 protected,只开放了 Get 方法,没有 Set 方法。
翻看 URuntimeVirtualTextureComponent,class 上包含了 ENGINE_API 标记,因此这部分有两种方式去进行修改:
- 不修改源码,创建子类继承自
URuntimeVirtualTextureComponent,添加Set方法访问 VirtualTexture,这种方法需要替换 Volume 的RootComponent和VirtualTextureComponent - 修改源码,直接在
URuntimeVirtualTextureComponent上增加Set方法
#include "VT/RuntimeVirtualTextureVolume.h"
#include "Components/RuntimeVirtualTextureComponent.h"
ARuntimeVirtualTextureVolume* volume = GetWorld()->SpawnActor<ARuntimeVirtualTextureVolume>();
volume->GetRootComponent()->SetMobility(EComponentMobility::Movable);
// 调整 transform
volume->SetActorLocation();
volume->SetActorRotation();
volume->SetActorScale3D();
2. Runtime Virtual Texture
翻看源码,这部分是属于 URuntimeVirtualTexture,继承自 UObject,因此可以直接 NewObject 实例化,但其中控制 Virtual Texture Tile 的相关参数也都是 protected ,但同样有 ENGINE_API 标记,如果需要调整也可以使用上一步的两种方案。
#include "VT/RuntimeVirtualTexture.h"
URuntimeVirtualTexture* rvt = NewObject<URuntimeVirtualTexture>(this, TEXT("RVT"));
volume->VirtualTextureComponent->SetVirtualTexture(rvt);
3. 写入 RVT 的 StaticMesh
这一步有代码和材质两个部分需要操作:
- 代码上,需要设置 StaticMesh 确定写入的 RVT ,
StaticMeshComponent上开放了RuntimeVirtualTextures数组完成这个动作。另外,可以通过设置VirtualTextureRenderPassType决定StaticMesh 是否还要被渲染到屏幕上,对于只需要写入 RVT 而不需要渲染到屏幕上的设置为Never,两个都需要的设置为Always。
AStaticMeshActor* mesh = GetWorld()->SpawnActor<AStaticMeshActor>();
mesh->GetStaticMeshComponent()->RuntimeVirtualTextures.Add(rvt);
mesh->GetStaticMeshComponent()->VirtualTextureRenderPassType = ERuntimeVirtualTextureMainPassType::Never;
- 材质上,需要在 StaticMesh 使用的 Material 中,将需要写入的内容,传给
Runtime Virtual Texture Output。
4. 展示 RVT 的 StaticMesh
用于展示的这部分如果同时需要展示自身,那么也需要根据上一步进行操作。
同时,展示的 StaticMesh 的 Material 中需要将 RVT 与自身进行融合,就需要使用 Runtime Virtual Texture Sample 获取 RVT 内容,这个结点内需要指定采样的 RVT ,但我们的 RVT 是代码实时生成的,因此就遇到了最棘手的问题:如何将 RVT 传到 Material 中。
虽然 UMaterialInstanceDynamic 有开放 SetTextureParameterValue 等一系列可以根据 ParamaterName 向 Material 传递内容的方法,但目前并没有开放传递 RVT 的方法。
翻看源码,UMaterialInstance 中是有 protected 的SetRuntimeVirtualTextureParameterValueInternal 方法的,但 UMaterialInstanceDynamic 没有将其开放。同时,UMaterialInstance 的 class 描述中没有 ENGINE_API 的标记,因此子类化 UMaterialInstanceDynamic 调用 Internal 方法会有链接问题。
因此,这里必须修改源码,在 UMaterialInstanceDynamic 上开放一个新的方法调用 Internal 方法。
void UMaterialInstanceDynamic::SetRuntimeVirtualTextureParameterValue(FName ParameterName, class URuntimeVirtualTexture* Value)
{
FMaterialParameterInfo ParameterInfo(ParameterName);
SetRuntimeVirtualTextureParameterValueInternal(ParameterInfo, Value);
}
全部设置完成后,就可以使用代码构建 RVT 了,整个流程已经在 Runtime 下验证通过。
但同时使用的 RVT 数量有限制,目前调试下来最多可以同时使用 7 个,至于原因需要通读一下源码了再回来补上了。