【UE5】CPU、GPU、分辨率、画质、抗锯齿、RHI、声音的获取与设置

在UE的开发中经常需要提供给用户一定的配置可调项,如:分辨率、画质、抗锯齿、RHI、声音等,同时也需要读取一些设备信息,如:CPU,GPU,系统分辨率等,以便根据设备配置自动设置程序配置。

下面记录一些我在开发这些功能的过程中遇到的一些问题及其解决方案。

1.获取CPU信息

在日常生活中大多数电脑都只有一个CPU,所以在UE5中获取CPU信息的放大比较简单,直接通过FPlatformMisc库即可获取相关信息。

1
2
3
4
5
6
void UGKitSystemFunLib::GetCPUInfo(FString& UID, FString& Brand, FString& Vebdor)
{
UID = FString::FromInt(FPlatformMisc::GetCPUInfo());
Brand = FPlatformMisc::GetCPUBrand();
Vebdor = FPlatformMisc::GetCPUVendor();
}

2.获取GPU信息

由于很多笔记本都可能配有集成显卡和独立显卡,出现一台设备有两张显卡的情况,所以GPU的信息获取要稍微麻烦一些。

目前我找到的有两种方法,一种是直接使用UE5封装的DXGI—RHI库,另一种则是直接使用Windows的DXGI库。

使用UE5的封装主要使用FPlatformMiscRHI两个库来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "GenericPlatform/GenericPlatformDriver.h"
#include "DynamicRHI.h"

// VendorId : GPU供应商的ID,
// DeviceDescription : GPU的型号描述,
// ProviderName : GPU的设备供应商名称,
// InternalDriverVersion : GPU的内部驱动版本号,
// UserDriverVersion : 面向用户的驱动版本号,
// DriverDate : 驱动的更新日期,
// RHIName : 当前RHI名称。
void USetttingsBPFunLib::GetGPUInfo(FString& VendorId, FString& DeviceDescription, FString& ProviderName, FString& InternalDriverVersion, FString& UserDriverVersion, FString& DriverDate, FString& RHIName)
{
FString GPUBrand = FPlatformMisc::GetPrimaryGPUBrand();
FGPUDriverInfo GPUINFO = FPlatformMisc::GetGPUDriverInfo(GPUBrand);
VendorId = FString::FromInt(GPUINFO.VendorId);
DeviceDescription = GPUINFO.DeviceDescription;
ProviderName = GPUINFO.ProviderName;
InternalDriverVersion = GPUINFO.InternalDriverVersion;
UserDriverVersion = GPUINFO.UserDriverVersion;
DriverDate = GPUINFO.DriverDate;
RHIName = GPUINFO.RHIName;
}

// DedicatedVideoMemory : VRAM 专用显存,
// DedicatedSystemMemory : 专用系统显存,在系统启动时专门预留给集成显卡使用的系统内存(RAM),操作系统无法使用,对于独立显卡这个值一般为0或很小,
// SharedSystemMemory : 共享系统内存,显卡可以从系统内存中动态借用的内存。
void USetttingsBPFunLib::GetGPUVideoMemory()
{
FString RHIName = GDynamicRHI->GetName();
FTextureMemoryStats Stats;
RHIGetTextureMemoryStats(Stats);
int DedicatedVideoMemory = Stats.DedicatedVideoMemory / (1024 * 1024 * 1024);
int DedicatedSystemMemory = Stats.DedicatedSystemMemory / (1024 * 1024 * 1024);
int SharedSystemMemory = Stats.SharedSystemMemory / (1024 * 1024 * 1024);

UE_LOG(LogGKit, Log, TEXT("RHIName:%s,DedicatedVideoMemory:%d,DedicatedSystemMemory:%d,SharedSystemMemory:%d"),
*RHIName, DedicatedVideoMemory, DedicatedSystemMemory, SharedSystemMemory);
}

使用RHI库还需要在.build.cs文件里添加RHI模块。

使用UE5的封装方便简洁,具备跨平台能力,但是功能不够强大,对于单显卡的情况能识别的很准确,但对于多显卡的情况会出现一些问题,FPlatformMisc获取到的GPU信息默认都是0号索引适配器,在拥有集成显卡和独立显卡的笔记本上,0号索引适配器基本都是集成显卡,这就导致获取到显卡信息都是集成显卡的,而RHI获取又是当前活跃显卡的显存,RHI又未提供获取当前活跃显卡型号等信息的接口,导致读取到的显卡信息不全,显卡型号和显存不匹配等问题。

使用Windows的DXGI库则需要额外引入第三方Lib文件及头文件,导致程序包包含两个DX库,增加程序包的体积,并且依赖于Windows平台,但是功能强大,可以识别包括集成显卡、独立显卡,微软虚拟显卡在内的全部显卡适配器信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
#include <dxgi.h>
#include <d3dcommon.h>
#pragma comment(lib, "dxgi.lib")
#include "Windows/HideWindowsPlatformTypes.h"
#endif

void UGKitSystemFunLib::GetGPUInfoByWindowsDXGI()
{
#if PLATFORM_WINDOWS
HRESULT hr;
IDXGIFactory1* DXGIFactory = nullptr;
IDXGIAdapter1* Adapter = nullptr;

hr = CreateDXGIFactory1(__uuidof(DXGIFactory), (void**)&DXGIFactory);
if (SUCCEEDED(hr) && DXGIFactory)
{
UINT AdapterIndex = 0;
while (DXGIFactory->EnumAdapters1(AdapterIndex, &Adapter) != DXGI_ERROR_NOT_FOUND)
{
if (Adapter)
{
DXGI_ADAPTER_DESC1 AdapterDesc;
Adapter->GetDesc1(&AdapterDesc);

FString Description = WCHAR_TO_TCHAR(AdapterDesc.Description);
int VendorId = static_cast<int>(AdapterDesc.VendorId);
int DeviceId = static_cast<int>(AdapterDesc.SubSysId);
int Revision = static_cast<int>(AdapterDesc.Revision);
int DedicatedVideoMemory = static_cast<int>(AdapterDesc.DedicatedVideoMemory / (1024 * 1024));
int DedicatedSystemMemory = static_cast<int>(AdapterDesc.DedicatedSystemMemory / (1024 * 1024));
int SharedSystemMemory = static_cast<int>(AdapterDesc.SharedSystemMemory / (1024 * 1024));
int AdapterLuid_HightPart = static_cast<int>(AdapterDesc.AdapterLuid.HighPart);
int AdapterLuid_LowPart = static_cast<int>(AdapterDesc.AdapterLuid.LowPart);
int Flags = static_cast<int>(AdapterDesc.Flags);

UE_LOG(LogGKit, Log, TEXT("Description:%s,VendorId:%d,DeviceId:%d,Revision:%d,DedicatedVideoMemory:%d,DedicatedSystemMemory:%d,SharedSystemMemory:%d,AdapterLuid_HightPart:%d,AdapterLuid_LowPart:%d,Flags:%d"),
*Description, VendorId, DeviceId, Revision, DedicatedVideoMemory, DedicatedSystemMemory, SharedSystemMemory, AdapterLuid_HightPart, AdapterLuid_LowPart, Flags);

Adapter->Release();
Adapter = nullptr;

}
AdapterIndex++;
}
DXGIFactory->Release();
DXGIFactory = nullptr;
}
#endif
}

同时需要在.build.cs文件下引入Windows的DXGI库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Windows 平台特定设置
if (Target.Platform == UnrealTargetPlatform.Win64)
{
// 添加 Windows SDK 库依赖
PublicSystemLibraries.AddRange(
new string[]
{
"dxgi.lib",
}
);

// 添加 Windows SDK 包含路径
PublicIncludePaths.AddRange(
new string[]
{
"C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/um",//d3dcommon.h所在目录
"C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/shared",//dxgi.h所在目录
}
);
}

我在自己的笔记本上做了测试,可以看一下打印信息:

1
2
3
4
[2026.01.05-06.16.13:925][  0]LogGKit: Description:NVIDIA GeForce GTX 1660 Ti,VendorId:4318,DeviceId:275062021,Revision:161,DedicatedVideoMemory:5966,DedicatedSystemMemory:0,SharedSystemMemory:8118,AdapterLuid_HightPart:0,AdapterLuid_LowPart:12106403,Flags:0
[2026.01.05-06.16.13:925][ 0]LogGKit: Description:Intel(R) UHD Graphics 630,VendorId:32902,DeviceId:275062021,Revision:0,DedicatedVideoMemory:128,DedicatedSystemMemory:0,SharedSystemMemory:8118,AdapterLuid_HightPart:0,AdapterLuid_LowPart:56595613,Flags:0
[2026.01.05-06.16.13:925][ 0]LogGKit: Description:Microsoft Basic Render Driver,VendorId:5140,DeviceId:0,Revision:0,DedicatedVideoMemory:0,DedicatedSystemMemory:0,SharedSystemMemory:8118,AdapterLuid_HightPart:0,AdapterLuid_LowPart:67723,Flags:2
[2026.01.05-06.16.13:925][ 0]LogLoad: Took 0.090074 seconds to LoadMap(/Game/NewWorld)

可以看到识别出了三个显示适配器信息:NVIDIA GeForce GTX 1660 TiIntel(R) UHD Graphics 630Microsoft Basic Render Driver

3.获取与设置分辨率

获取分辨比较常用的是使用UserGameSettings来获取:

1
2
3
4
5
6
7
8
#include "GameFramework/GameUserSettings.h"

void USetttingsBPFunLib::GetGameUserSettingsScreenResolution(int& Length, int& Width)
{
FIntPoint Resolution = GEngine->GetGameUserSettings()->GetScreenResolution();
Length = Resolution.X;
Width = Resolution.Y;
}

UserGameSettings是UE专门用来保存用户设置的对象,使用UserGameSettings可以快速的获取UserGameSettings.ini文件下保存分辨率信息,但是这获取的并不是操作系统设置的分辨率,而是UE程序设置的分辨率,分辨率会根据UE程序的设置而发生改变。

如果想要获取Windows操作系统设置的分辨率,即Windows设置里设置的分辨率,可以使用这个方法:

1
2
3
4
5
6
7
#include "Windows.h"

void USetttingsBPFunLib::GetSystemScreenResolution(int& Length, int& Width)
{
Length = GetSystemMetrics(SM_CXSCREEN);
Width = GetSystemMetrics(SM_CYSCREEN);
}

这个方法获取的分辨率不会随UE程序的更改而更改,只会随着Windows系统设置的分辨率而更改,同时依赖于Windows库。

UE也提供了蓝图节点GetSupportedFullscreenResolutions节点来获取当前设备支持的所有的分辨率。

设置分辨率我们要设置就是UE程序的显示分辨率了,所以可以直接使用UserGameSettings来设置:

1
2
3
4
void USetttingsBPFunLib::SetGameUserSettingsScreenResolution(FIntPoint Resolution)
{
GEngine->GetGameUserSettings()->SetScreenResolution(Resolution);
}

UserGameSettings分辨率的设置UE提供了蓝图接口,不需要自己暴露,可以直接使用。

分辨率与窗口模式之间的冲突

在分辨率与窗口模式混合使用的时候会出现一些问题,最经典的就是在使用全屏独占的窗口模式时UE程序会绕过Windows系统直接控制显示器硬件,这会导致一个问题,当我们设置了一个比当前系统主显示器分辨率低的分辨率应用并保存到UserGameSettings后,如当前系统主显示器分辨率为2560x1440,如果我们设置游戏分辨率到1600x900,保存并应用到UserGameSettings,然后我们重新启动游戏,进程会在初始化时自动读取并应用UserGameSettings保存的1600x900这个分辨率,如果此时的窗口模式是全屏独占的话,由于游戏进程会绕过Windows系统直接控制显示器硬件,所以游戏修改的分辨是直接修改的显卡输出到屏幕的分辨率,同时操作系统检测到显卡输出分辨的变化后同步更改系统设置中的当前分辨率,那如果此时我们再去读取Windows系统的分辨率,读取到的就并不是屏幕的实际分辨率2560x1440了,而是被游戏进程修改后的1600x900的分辨率了。那么如何解决这个问题呢?

在解决这个问题之前我们需要先了解几个知识点,分辨率、窗口模式和UserGameSettings的初始化时机。

分辨率

在计算机的画面渲染流程中分辨率有三种,屏幕硬件的分辨率,操作系统设置的分辨率和显卡输出到屏幕的分辨率。

屏幕硬件的分辨率就是屏幕显示面板的物理发光点的数量,是屏幕硬件固有的不可更改的属性。

操作系统的分辨率只的是操作系统渲染桌面画面的分辨率,这决定了在屏幕上能看到内容空间,例如一个2560x1440的屏幕,如果操作系统设定的分辨率是1600x900,那么屏幕中多出来的像素点就没有内容显示,就是黑色的,不过现在操作系统都支持自动画面缩放,会将1600x900的画面等比放大到2560x1440的大小,然后屏幕需要使用多个物理发光点来显示一个像素内容。

显卡输出到屏幕的分辨率就是操作系统处理后的画面传输给显卡做完渲染后再又显卡传输到屏幕的画面的分辨率,显卡是可以支持各种各样的分辨率大小及比例的,我们可以在显卡的控制面板里控制输出的分辨率。

三者的关系是操作系统生成一个界面,操作设置系统分辨率决定了这个界面逻辑分辨率,显卡接受到这个界面按照显卡设置的输出分辨率进行渲染并输出到屏幕,屏幕接收到信号就按照物理分辨率将信号点对点的显示出来。一般操作系统的分辨率会与显卡的输出分辨率自动进行同步,二者任何一个的修改都会同步到另一个的配置中。

窗口模式

窗口模式分为全屏独占(对应UE的Fullscreen)、窗口化全屏(对应UE的Windowed Fullscreen)、窗口化(对应UE的Windowed)三种模式。

全屏独占模式是以前的游戏程序最主要的全屏模式,现在一些比较吃性能的大型游戏依然会使用,比如UE的全屏模式,全屏独占模式的好处是具有非常优秀的沉浸感,并且游戏进程直接控制显示器硬件,少了一层操作系统的处理,响应速度更快,在高帧率情况下更是如此,由于引擎可以直接控制屏幕,游戏程序可以享受引擎的全屏优化。当然坏处也很明显,同样由于引擎直接控制屏幕,会导致游戏进程所在显示器的分辨率出现与操作系统设定的分辨率不一致的情况,有可能导致操作系统界面或其他软件显示异常,在多显示器的情况下会比较明显,并且在操作系统切换任务的时候,操作系统需要重新从游戏进程手中获取屏幕的控制权,会出现在切任务时出现短暂黑屏的情况。

窗口化全屏模式就是为了解决全屏独占模式出现切任务黑屏问题而出现的,窗口化全屏本质仍然是一个窗口,只将窗口的标题栏隐藏了并且强制全屏来模拟全屏独占模式,游戏进程渲染的画面依然需要在经过操作系统的处理后再传输到显示器上,屏幕的控制权始终在操作系统手里,所以在操作系统切换任务时本质是在窗口之间切换不会出现抢夺屏幕控制权的情况,也就不会出现黑屏了,那么代价就是窗口化全屏模式下运行的游戏进程由于多了一层操作系统的渲染需要消耗更多的性能,也不能享受引擎的全屏优化。不过在Win10、Win11上微软做了不少优化,使串口化全屏模式的性能已经十分接近全屏独占模式了,另一个代价就是游戏进程在窗口化全屏模式下修改分辨率可能会出现修改无效的情况,因为窗口化全屏本质依然是一个窗口,而窗口修改分辨率不是修改的屏幕分辨率,而是修改的窗口大小,但窗口化全屏会强制全屏,这就会导致可能会出现修改分辨率无效的情况,并且屏幕的控制权在操作系统手里,屏幕分辨率也理应以操作系统的分辨率为主。

窗口化模式就是最常见的软件渲染模式了,我们平常使用计算机,除了游戏几乎所有的软件都是使用的窗口化模式,就不过多介绍了。

UserGameSettings的初始化时机

UGameUserSettings对象是在引擎预初始化阶段注册的,在游戏模块初始化阶段ApplySettings应用的,在渲染系统就绪之后生效的,在游戏逻辑正式开始之后被最终确认的。

了解了这三个知识点之后我们就有三个解决方案:

其一,不读取操作系统的当前分辨率,去读取显卡输出的支持的所有的分辨率,取最大分辨率,显卡输出的分辨率是显卡支持的分辨率与屏幕硬件支持的分辨率的交集,这样获取的分辨率才是最真实的。这也是我想到的最好的解决方案了,UE的RHI接口提供了获取这个交集的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "DynamicRHI.h"

void USetttingsBPFunLib::GetAllSupportedResolutions(TArray<FIntPoint>& Resolutions)
{
FScreenResolutionArray ScreenResolutionArray;
if (RHIGetAvailableResolutions(ScreenResolutionArray, false))
{
for (const FScreenResolutionRHI& Resolution : ScreenResolutionArray)
{
FIntPoint ResPoint(Resolution.Width, Resolution.Height);
if (!Resolutions.Contains(ResPoint))
{
Resolutions.Add(ResPoint);
}
}
}
}

其二,就是使用窗口化全屏模式,这样显卡输出到屏幕的分辨率不会被修改,系统的分辨率也就不会被修改,读取到系统分辨率就是正确的,当然系统分辨率也需要匹配屏幕分辨率,读取到的才是真实屏幕分辨率。

其三,在UserGameSettings生效之前读取系统分辨率,这样读取到的也是正确的分辨率,但是这个方法不太好操作。

在全屏模式与分辨率的搭配使用中,我这里也出现了一些其他的问题。

在窗口化模式下设置分辨率与屏幕分辨率一致时窗口化失效

这是因为通过GameUserSettings::SetScreenResolution()来设置分辨率时,设置的是游戏渲染视口的分辨率,这不包含标准窗口的标题栏和边框的像素,所以当我们设置游戏渲染视口的分辨率到屏幕分辨率时,游戏渲染视口就已经把屏幕给占满了,所以标题栏可能被渲染在了屏幕之外,导致视觉上就好像是全屏了一样。

解决方案就是在窗口化模式下当设置的分辨率与屏幕分辨率一致时强制窗口最大化,这样窗口就会被强制约束在屏幕内了。代码比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
void USetttingsBPFunLib::MaxGameWindow()
{
UGameEngine* GameEngine = Cast<UGameEngine>(GEngine);
if (GameEngine)
{
TSharedPtr<SWindow> GameWindow = GameEngine->GameViewportWindow.Pin();
if (GameWindow.IsValid())
{
GameWindow->Maximize();
}
}
}

使用这个方法的好处是无论操作系统设置的DPI是多少,窗口都可以自适应最大化。

但是坏处也是显而易见的,这个方法会导致游戏渲染视口的实际分辨率会因为强制显示了标题栏和操作系统菜单栏或DPI的缘故导致与设置的分辨率不符。在对渲染视口分辨率没有要求很精确的控制的大部分情况下,这个方法都可以适用,对于有很严格控制渲染视口分辨率的情况则需要自己去主动根据不同情况计算所需要的分辨率了。

程序窗口适配Windows的F11强制全屏与窗口化

UE的窗口是有接收并处理Windows的F11的消息的,当我们按下F11强制全屏时GameUserSettings设置中的分辨率会被强制更改到屏幕分辨率,全屏模式会被强制更改为全屏独占模式,当按下F11强制窗口化时GameUserSettings设置中的分辨率会被强制更改到比屏幕分辨率小一级的分辨率,全屏模式会被强制更改为窗口模式。这些修改都是UE内部完成的,如果我们的设置是以GameUserSettings为主的话,就会出现我们自己写的设置界面中显示的分辨率与GameUserSettings和实际分辨率不一致的情况。

如果我们想要保持一致的话,我们就需要监控F11的调用,在全屏与窗口化时坐相应的处理,UE提供了一个全屏时调用的委托—UGameViewportClient::ToggleFullscreenDelegate,那么我们要做的就是往这个委托绑定一个回调函数即可:

1
2
3
4
5
6
7
8
void UUserConfigManagerBase::BindOnToggleFullscreen()
{
UGameViewportClient* ViewportClient = GEngine->GameViewport;
if (ViewportClient)
{
ViewportClient->OnToggleFullscreen().AddUObject(this, &UUserConfigManagerBase::OnToggleFullscreenCallback);
}
}

需要注意UGameViewportClient::ToggleFullscreenDelegate委托只适用于使用Windows的F11强制全屏时才会被调用,使用UserGameSettings设置全屏与窗口时不会调用。

当然一般的游戏是禁止使用F11全屏的,这里引擎也提供了直接配置的选项,在Edit/Project Settings/Engine/Input/Bindings/Speech Mappings/Advanced下有两个配置,分别是Alt Enter Toggles FullscreenF11 Toggles Fullscreen,二者分别对应Alt + Enter键强制全屏,与F11强制全屏,全部取消勾选即可禁用。

分辨率适应Windows高DPI缩放

有些电脑的屏幕可能分辨率很高,但屏幕本身很小,像2k屏的笔记本,屏幕分辨率是2560x1440但屏幕本身尺寸只有1920x1080的大小,此时Windows系统可能会推荐150%的缩放DPI,对于不同的DPI,引擎取到的分辨率可能不是实际系统实际显示的分辨率,这会导致有时在切软件时或是初始化时程序分辨率不正确。对于这个问题UE5已经自带了解决方案,我们只需要勾选Edit/Project Settings/Engine/User Interface/DPI Scaling/Allow High DPI in Game Mode,在游戏模式中允许高DPI,这样引擎会自动计算高DPI下的正确分辨率。

4.设置画质

设置画质UserGameSettings也提供了蓝图接口可以直接使用:

1
2
3
4
5
void USetttingsBPFunLib::SetGameUserSettingsImageQuality(int Level)
{
GEngine->GetGameUserSettings()->SetOverallScalabilityLevel(Level);
}

这里设置的画质是直接设置的是引擎的可扩展性设置,直接使用引擎已经调整好的各项属性数据,如果想要单独设置各项属性的值则需要自己单独去设置。

从0-4的画质级别依次是0-低,1-中,2-高,3-极高,4-电影级。

只要是可延展性设置中列出来的配置项都可以通过这个接口使用引擎的预设进行修改,无需再手动进行修改,除非引擎的预定义设置无法满足实际需求。

5.获取与设置抗锯齿

抗锯齿无法使用UserGameSettings去修改,需要使用命令行去修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
UENUM(BlueprintType)
enum EAAMethod {
AAMethod_None,
FXAA, // 快速模拟抗锯齿(FXAA)
TAA, // 临时抗锯齿(TAA)
MSAA, // 多重取样抗锯齿(MSAA)
TSR, // 临时超分辨率(TSR)
DLSS // 深度学习超级采样(DLSS)
};

EAAMethod USetttingsBPFunLib::GetAAMethod()
{
const IConsoleVariable* CVarAAMethod = IConsoleManager::Get().FindConsoleVariable(TEXT("r.AntiAliasingMethod"));
EAAMethod Rel = EAAMethod::AAMethod_None;
if (CVarAAMethod)
{
int32 AAMethodCode = CVarAAMethod->GetInt();
switch (AAMethodCode)
{
case 0: Rel = EAAMethod::AAMethod_None; break;
case 1: Rel = EAAMethod::FXAA; break;
case 2: Rel = EAAMethod::TAA; break;
case 3: Rel = EAAMethod::MSAA; break;
case 4: Rel = EAAMethod::TSR; break;
case 5: Rel = EAAMethod::DLSS; break;
}
}
return Rel;
}

设置抗锯齿方法

1
2
3
4
5
void USetttingsBPFunLib::SetAAMethod(const EAAMethod& AAMethodType, UObject* WorldContext)
{
FString Method = TEXT("r.AntiAliasingMethod ") + FString::FromInt(AAMethodType);
UKismetSystemLibrary::ExecuteConsoleCommand(WorldContext, Method);
}

6.获取与设置RHI

获取引擎当前使用的RHI比较简单,直接使用GDynamicRHI就可以直接获取到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
UENUM(BlueprintType)
enum ERHI {
RHI_None,
Directx12,
Directx11,
Vulkan
};

ERHI USetttingsBPFunLib::GetRHI()
{
ERHI RHIName = ERHI::RHI_None;

FString CurrentRHI = GDynamicRHI->GetName();
if (CurrentRHI == TEXT("D3D12"))
{
RHIName = ERHI::Directx12;
}
else if (CurrentRHI == TEXT("D3D11"))
{
RHIName = ERHI::Directx11;
}
else if (CurrentRHI == TEXT("Vulkan"))
{
RHIName = ERHI::Vulkan;
}

return RHIName;
}

设置RHI则稍微麻烦一点,设置RHI需要去修改DefaultEngine.ini配置文件,由于UE5对打包后的Config目录下的引擎配置文件有做读写限制,无法直接修改,而对于Saved目录下的配置文件,不同打包版本或编辑器状态位置不同,所以需要做专门的针对处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void USetttingsBPFunLib::SetRHI(ERHI RHI)
{
FString ConfigPath;
#if WITH_EDITOR
ConfigPath = FPaths::ProjectConfigDir() / TEXT("DefaultEngine.ini");
#else
ConfigPath = FPaths::ProjectSavedDir() / TEXT("Config/Windows/DefaultEngine.ini");
#endif
FConfigFile ConfigFile;
ConfigFile.Read(ConfigPath);

FString SectionName = TEXT("/Script/WindowsTargetPlatform.WindowsTargetSettings");
FString KeyName = TEXT("DefaultGraphicsRHI");

FString DefaultGraphicsRHI;
ConfigFile.GetString(*SectionName, *KeyName, DefaultGraphicsRHI);

switch (RHI)
{
case ERHI::Directx12:
if (DefaultGraphicsRHI != TEXT("DefaultGraphicsRHI_DX12"))
{
ConfigFile.SetString(*SectionName, *KeyName, TEXT("DefaultGraphicsRHI_DX12"));
}
break;
case ERHI::Directx11:
if (DefaultGraphicsRHI != TEXT("DefaultGraphicsRHI_DX11"))
{
ConfigFile.SetString(*SectionName, *KeyName, TEXT("DefaultGraphicsRHI_DX11"));
}
break;
case ERHI::Vulkan:
if (DefaultGraphicsRHI != TEXT("DefaultGraphicsRHI_Vulkan"))
{
ConfigFile.SetString(*SectionName, *KeyName, TEXT("DefaultGraphicsRHI_Vulkan"));
}
break;
}
// 立即刷新配置文件
//GConfig->Flush(true, ConfigPath);
// 保存文件
ConfigFile.Write(ConfigPath);
}

在编辑器状态下我们可以直接修改Config目录下的DefaultEngine.ini文件,而对于打包后的程序由于无法修改Config目录下的默认配置文件,所以我们需要在打包的时候拷贝一份DefaultEngine.ini文件到Saved目录,对于DebugGame和Development的打包版本,由于这两个版本的Saved目录是直接在项目目录下的可以直接在.build.cs文件下设置

1
RuntimeDependencies.Add(Path.Combine(ModuleDirectory, "../../../../", "Saved/Config/Windows/DefaultEngine.ini"));

这样在打包时,UE会自动把DefaultEngine.ini文件拷贝到Saved/Config/Windows/DefaultEngine.ini。

但是这个方法对于Shipping发行版的打包程序不适用,因为发行版的程序的Saved目录在C:\Users\<用户名>\AppData\Local\<程序名>\Saved,所以对于发行版还需要单独处理,同样我们可以在.build.cs文件下添加

1
RuntimeDependencies.Add(Path.Combine(ModuleDirectory, "../../../../", "Config/DefaultEngine.ini"));

在打包时将DefaultEngine.ini文件拷贝到Config目录,然后再在程序启动时通过程序将配置文件拷贝到对应的Saved目录

UE的GetProjectSavedDirectory输出的Saved目录会根据打包的版本自适应,所以对于所有的打包版本都可以统一这样处理。

在Saved目录下的配置文件会覆盖引擎默认的配置文件,这样就可以实现RHI的修改了。

7.声音的设置

UE5的声音系统也是一个很庞大的系统,尤其是在推出MetaSound次世代声音系统之后,更是如此,在UE5中设置声音音量的方式很多,我这里是使用的SoundClass配合SoundClassMix来进行细分的声音音量管理和控制。

使用SoundClass的好处是SoundClass将声音资产与代码蓝图进行了解耦,开发者只需要对SoundClass进行控制即可,而无需关注SoundClass关联的声音资产在哪、关联声音资产是什么类型的资产,也不需要知道声音资产是在哪用的、怎么用的。

SoundClass的主要作用是将音频资产进行分组,可以把SoundClass理解为管理音频资源的标签或文件夹。SoundClass还提供一揽子设置,可以统一设置整个分组的音频设置,SoundClass的设置会直接覆盖其他的音频资产的设置。

SoundClassMix的主要作用就是对SoundClass进行控制,SoundClassMix可以设置不同场景下的音效配置,以便在不同场景下应用这些配置到需要用的一个或多个声音分组下的所有音频,如:室内场景-增加混响,模拟封闭空间、水下场景-应用低通滤波,模糊高频、战斗场景-增强武器音效,降低背景音乐等等。SoundClassMix与SoundClass是一个多对多的关系,一个SoundClassMix的设置可以应用到多个SoundClass上,一个SoundClass也可以被应用多个SoundClassMix的设置效果。

SoundClassMix可以单独为每一个需要控制的SoundClass进行配置。

SoundClass和SoundClassMix都很强大,但我这里应用非常简单,我只用来修改音频的音量。

UE5提供SetSoundMixClassOverride接口来应用SoundClassMix设置到SoundClass:

然后通过PushSoundMixMoidifier接口将SoundClassMix的音效设置压入音频效果栈,音频效果栈是UE对音效的处理模式,如:玩家同时处于室内(混响效果)+受伤(低频增强)+潜行(音量降低),这三个音效可能由三个SoundClassMix控制,并且出现顺序任意,所以使用栈的方式来混合音效更为合理。

当然我这里应用比较简单,不希望每调节一次音量就要压如一个SoundClassMix进入音效栈,所以我需要在设置声音属性之前先将栈内的SoundClassMix出栈:

Value可以接入Slider控件的数值或是其他任何地方的数值,这样就可以进行声音设置了。

8.帧率设置

在进行帧率设置开发之前,我们需要了解三个概念,垂直同步、固定帧率和平滑帧率。

垂直同步:垂直同步是一个GPU的底层机制,主要目的是让GPU的渲染速度与屏幕硬件的刷新速度保持一致,避免当GPU渲染速度高于屏幕刷新速度时出现的在屏幕刷新一帧的时间内GPU输出了多帧画面到屏幕,导致屏幕在一帧中渲染了多帧的GPU画面,出现画面撕裂的现象;

固定帧率:固定帧率是UE用来强制渲染帧率保持再一个固定值的手段,这可以保证游戏逻辑以一个精确的、恒定的速度运行,这对对物理模拟、动画、网络同步有严格要求的游戏逻辑非常重要。当在项目设置中设置了固定帧率后,将无法再UserGameSettings来设置帧率。固定帧率使用project Settings/Engine/General Settings/Framerate/Use Fixed Frame Rate启用,使用Fixed Frame Rate来设置固定帧率值。

平滑帧率:平滑帧率是UE用来减少帧率剧烈波动,让游戏运行感觉上更平滑的手段,通过设置帧率范围,在游戏帧率在此范围内波动时,使用平滑机制,来调整帧率的骤升与骤降,从而减少卡顿感。平滑帧率使用project Settings/Engine/General Settings/Framerate/Smooth Frame Rate启用,使用Smoothed Frame Rate Range来设定平滑范围。固定帧率与平滑帧率不兼容,不可同时启用,启用了固定帧率后平滑帧率会自动禁用。平滑帧率的最大最小值分别都可以设置Inclusive(包含边界,帧率可以等于最大最小值)、Exclusive(帧率不能等于最大最最小值,只能无限趋近)和Open(不设边界,帧率可以无限大或等于0)三个选项。

在大多数情况下我们都使用平滑帧率,下面我们设置的帧率也都是在平滑帧率下进行设置。

帧率的设置比较简单,UE提供了UserGameSettings.SetFrameRateLimit接口来设置帧率上限,有蓝图节点可以直接使用。

垂直同步在引擎里是默认启用的,所有即使设置的帧率远高出屏幕刷新率,在GPU输出到屏幕的画面中也会被限制帧率。

DLSS

现在几乎主流的PC游戏都开始支持DLSS,以提升游戏运行的帧率,提升游玩体验。

DLSS:(Deep Learning Super Sampling,深度学习超级采样)是是 NVIDIA 开发的一套AI 驱动的神经渲染技术,由 RTX 显卡的 Tensor Core(张量核心)提供算力支持。它的核心目标是在提升游戏帧率的同时,输出清晰、高质量的图像,画质可媲美甚至超越原生分辨率。简单来说,DLSS 让显卡“算得更少,看得更好”——通过 AI“脑补”出高画质画面,而非传统方式那样“硬算”每一个像素。

DLSS的原理就是让游戏在低分辨率下运行,这样游戏可以运行更高的帧率,然后由GPU利用AI模型将低分辨率的图像帧智能填补细节重建出高分辨率的图像帧,以达到在不损失分辨率细节的同时提高帧率表现。

DLSS由三大核心功能组成,分别是:

超分辨率:游戏以较低分辨率渲染(如 1080p),DLSS 利用 AI 模型将其“放大”到更高分辨率(如 4K)。AI 模型通过分析多帧低分辨率图像的运动数据和历史信息,智能地填补细节,重建出高质量的原生画质,支持RTX20及以上系列的显卡;

帧生成:在 GPU 渲染的真实帧之间,利用 AI 额外“插入”AI 生成的帧,大幅提升画面流畅度。帧生成不增加游戏逻辑的运行速度(物理、AI 等仍以原始帧率计算),但能让眼睛看到更丝滑的画面。这里有一点很关键,帧生成补的帧率是GPU的输出帧率,而不是游戏的渲染帧率,也就是说帧生成只能让画面看起来更丝滑,并不能让游戏变得丝滑,帧生成是DLSS4新增加的功能,目前只支持在RTX40系及以上的显卡上使用;

光线重建:在光线追踪场景中,使用 AI 在采样光线之间生成更高质量的像素,取代传统需要大量人工调校的降噪器,提升光追画质,主要运行于光追场景。

在UE中集成DLSS

DLSS每个版本都在持续优化和变化,这里我以最新的DLSS4.5为例,需要注意DLSS还在持续更新迭代中,每个版本都可能会有所变化,以下仅供参考。

通过[Nvidia开发者门户](NVIDIA DLSS | NVIDIA Developer可以下载各个版本的DLSS的UE插件,我这里使用UE5.5的插件。

解压之后目录结构如下:

  • Documentation:开发者文档;
  • Platforms:内含WinGDK平台的支持插件;
  • Plugins:插件目录;
  • Samples:示例项目;

这里我们主要使用Plugins下几个插件,在我下载的DLSS4.5的Plugins目录下有9个插件目录:

  • DLSS:DLSS插件在引擎中叫NVIDIA DLSS Super Resolution/Ray Reconstruction/DLAA,包含主要的超分辨率、光线重建和深度学习抗锯齿功能;
  • DLSSMoviePipelineSupport:在引擎中叫Movie Render Queue DLSS/DLAA Support,用以支持DLSS在UE的电影渲染队列中使用;
  • NIS:在引擎中叫NVIDIA Image Scaling (NIS),提供非RTX显卡的图像缩放于锐化功能;
  • Streamline:已废弃,在新版本使用StreamlineCore,DLSS各插件所依赖的底层核心技术库;
  • StreamlineCore:在引擎中叫NVIDIA Streamline Core (hidden, implementation detail),是DLSS帧生成插件所依赖的底层核心技术库;
  • StreamlineDeepDVC:在引擎中叫NVIDIA RTX Dynamic Vibrance,包含动态调节画面的色彩饱和度和对比度的功能;
  • StreamlineDLSSG:在引擎中叫NVIDIA DLSS Frame Generation and DLSS Multi Frame Generation,包含DLSS帧生成、多帧生成功能;
  • StreamlineNGXCommon:在引擎中叫NVIDIA NGX/Streamline Common,是DLSS超分辨率和帧生成所以依赖的底层核心技术库;
  • StreamlineReflex:在引擎中叫NVIDIA Reflex Low Latency,用于降低系统延迟,提升操作响应速度,配合帧生成使用。

这么多的插件不是全部都要启用的,是可以按需启用的。插件可以放在项目的Plugins下也可以放在引擎的Plugins/Marketplace目录下,同时这么的插件目录也可以自建一个目录进行管理。

使用DLSS超分辨率

如果我们只想使用DLSS的超分辨率功能,我们只需要启用NVIDIA DLSS Super Resolution/Ray Reconstruction/DLAANVIDIA Image Scaling (NIS)NVIDIA NGX/Streamline Common三个插件即可,启用NVIDIA Image Scaling (NIS)插件主要是为了配合超分辨率中的DLAA模式进行画面锐化和适配非RTX显卡。

我们可以使用插件提供SetDLSS/D:AAMode(AutoMode)SetDLSS/DLAA(No Auto)宏来启用DLSS和修改DLSS模式。这两个宏为我们封装好了设置不同模式时使用的屏幕百分比。

在关闭DLSS时,需要勾选Restore Full Res When Disabled,否则屏幕百分比不会还原,并且这里还原的屏幕百分比是100,也并不是实际项目使用的屏幕百分比。

当然我们也可以直接使用Enable DLSS-SR来单独启动DLSS,然后使用自定义规则设置屏幕百分比。

DLSS支持多种超分辨率模式:

  • Off:关闭超分辨率功能;
  • Auto:自动,有DLSS系统自动根据当前配置设置适合的超分辨率模式;
  • DLAA:原生分辨率极致抗锯齿;
  • Ultra Quality:极高质量,需要非常高的配置才支持,我的5070都不持支这个模式;
  • Quality:质量模式,优先画面效果;
  • Balanced:平衡模式,在画面与帧率之间做取舍平衡;
  • Perfomance:性能模式,优先帧率;
  • Ultra Performance:极致性能。

使用DLSS-FG帧生成

帧生成功能是DLSS4新出的功能,目前还有一些bug,甚至会出现偶发的程序崩溃问题,项目中使用需要谨慎。

要使用帧生成功能需要启用NVIDIA DLSS Super Resolution/Ray Reconstruction/DLAANVIDIA Image Scaling (NIS)NVIDIA NGX/Streamline CommonNVIDIA DLSS Frame Generation and DLSS Multi Frame GenerationNVIDIA Reflex Low LatencyNVIDIA Streamline Core (hidden, implementation detail)六个插件,

DLSS插件提供Set DLSS-FG Mode蓝图节点来设置真生成模式,在启用帧生成之前需要先启用DLSS超分辨率,帧生成依赖超分辨率功能。

帧生成提供多种模式供使用:

  • Off:关闭;
  • Auto:DLSS-FG系统会自动根据当前配置设置最优的帧生成模式,如果影响到帧率可能会关闭帧生成;
  • On2X:为每个渲染帧生成一帧画面,只有RTX40系及以上显卡支持;
  • On3X:为每个渲染帧生成两帧画面,只有RTX50系显卡支持;
  • On4X:为每个渲染帧生成3帧画面,只有RTX50系显卡支持。

生成的帧数越多,显卡输出的帧率就越高,但对画面的影响也越大,帧生成的原理是每渲染一帧的画面由显卡中的AI模型进行推测后面N帧的画面,在生成多帧时可能会出现画面虫影现象。

需要注意的时帧生成是在显卡层面进行补帧,并不会影响游戏实际帧率,也就是说帧生成只能让画面看起来更流畅,并不能让游戏操作变得更流畅,这就导致了游戏操作的流畅度跟不上画面刷新的流畅度,会带来一些延迟感,Nvidia提供了NVIDIA Reflex Low Latency插件来解决这个问题,DLSS插件提供Set Reflex Mode节点来启用Reflex功能,提供三种模式供选择:

  • Off:完全禁用 Reflex 技术,系统按照默认的渲染流程处理;
  • Enabled:动态同步CPU和GPU,大幅缩减渲染队列长度,让GPU能几乎即时地处理CPU提交的帧,显著降低延迟,且通常没有性能或功耗上的副作用;
  • Boost:在 Enabled 模式的基础上,进一步解除CPU和GPU的“节能限制”,延迟最低,但可能略微降低帧数 (FPS) 并增加功耗。

使用RTX DVC

动态饱和度是DLSS提供的可选功能, 是一个由 AI 驱动的动态画面增强滤镜。它的核心作用是智能地提升游戏画面的色彩饱和度和鲜艳度,同时避免传统“数字振动”设置容易导致的颜色过曝、细节丢失问题,让画面看起来更生动又不失真。

它和你通过 NVIDIA 控制面板直接拉高“数字振动”最大的不同在于:RTX DVC 是动态的、场景感知的。它会根据画面内容智能调整,保护肤色等关键颜色不过度饱和,从而在提升视觉冲击力的同时,尽量维持色彩的自然感。

启用NVIDIA RTX Dynamic Vibrance插件即可使用RTX DVC功能,主要提供三个蓝图节点进行设置,SetDeepDVCMode节点用于启动与关闭RTX DVC,Set DeepDVC Intensity 设置AI调节的强度,值越低越接近原图色彩,值越高越接近调整后的色彩,SetDeepDVCSaturationBoost设置饱和度。

DLSS的其他功能都是一些用的非常少的功能了这里就不一一研究了。


【UE5】CPU、GPU、分辨率、画质、抗锯齿、RHI、声音的获取与设置
http://example.com/2026/05/14/【UE5】CPU、GPU、分辨率、画质、抗锯齿、RHI、声音的获取与设置/
作者
Goulandis
发布于
2026年5月14日
许可协议