【转载】【UE4 C++】运行时虚拟纹理 RVT 踩坑实录

1,293 阅读4分钟

原文链接:【UE4 C++】运行时虚拟纹理 RVT 踩坑实录 | 程序员阿Tu

最近在研究道路贴合地形绘制的问题,特别适合使用 UE 的 RVT ,这篇文章除了 RVT 的入门使用外,也探索了完全使用 C++ 进行 RVT 使用的可行性。

快速上手入门

RVT(Runtime Virtual Texture) 是 4.23 版本加入的新内容,因为概念比较多,推荐先看视频快速理解。这里推荐官方的视频教程,第二部分讲的就是 RVT 。同时我也转载了一个 YouTube 上讲的不错的教程,结合这两个教程可以很快的上手 RVT 功能。

[中文直播]第 18 期 | Virtual Texture(虚拟纹理)的理解和应用 | Epic 李文磊

【UE4】RVT 运行时虚拟纹理入门介绍 | 程序员阿Tu

RVT 功能需要预先开启重启 UE ,这一步可能会等待一段时间,之后就可以开始实战了。

入门精炼

RVT 的过程可以用胶卷照相机拍照这一过程作为类比,可以更快速的理解 RVT 的核心思想

  1. Runtime Virtual Texture 类比为相机胶卷底片,用于记录被相机拍下的内容
  2. Runtime Virtual Texture Volume 类比为相机,用于放置相机并调整拍摄的区域
  3. Virtual Texture MaterialMaterial 中的 Runtime Virtual Texture Output 结点作为拍摄内容,告诉相机需要将区域内的哪些内容纳入拍摄
  4. Material 中的 Runtime Virtual Texture Sample 结点可以实时获取拍摄内容
  5. ActorLandscape 搭配 Material ,就可以展示拍摄内容

核心内容主要在第四步,通过 Sample 获取内容后和自身逻辑进行融合,从而可以将两部分内容同时进行展示。

这部分根据教程,在 UE4 的 Editor 中可视化操作十分方便。但我需要 RVT 的区域非常多,无法一一枚举操作,因此下面主要是探索代码动态使用 RVT

纯代码使用 RVT

在实践的过程中发现,基于 4.26 版本,不修改源码无法达到纯代码使用 RVT 的目的,因此下面的内容包含了修改少量源码的操作。

1. Runtime Virtual Texture Volume

翻看源码,这部分是属于 ARuntimeVirtualTextureVolume,继承自 AActor。其中有一个 URuntimeVirtualTextureComponent 负责持有 RVT ,这个 Component 在 4.26 版本已经改为 public,但其持有的 URuntimeVirtualTextureprotected,只开放了 Get 方法,没有 Set 方法。

翻看 URuntimeVirtualTextureComponent,class 上包含了 ENGINE_API 标记,因此这部分有两种方式去进行修改:

  • 不修改源码,创建子类继承自 URuntimeVirtualTextureComponent,添加 Set 方法访问 VirtualTexture,这种方法需要替换 VolumeRootComponentVirtualTextureComponent
  • 修改源码,直接在 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

用于展示的这部分如果同时需要展示自身,那么也需要根据上一步进行操作。

同时,展示的 StaticMeshMaterial 中需要将 RVT 与自身进行融合,就需要使用 Runtime Virtual Texture Sample 获取 RVT 内容,这个结点内需要指定采样的 RVT ,但我们的 RVT 是代码实时生成的,因此就遇到了最棘手的问题:如何将 RVT 传到 Material 中。

虽然 UMaterialInstanceDynamic 有开放 SetTextureParameterValue 等一系列可以根据 ParamaterNameMaterial 传递内容的方法,但目前并没有开放传递 RVT 的方法。

翻看源码,UMaterialInstance 中是有 protectedSetRuntimeVirtualTextureParameterValueInternal 方法的,但 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 个,至于原因需要通读一下源码了再回来补上了。