diff --git a/.obsidian/plugins/various-complements/histories.json b/.obsidian/plugins/various-complements/histories.json index 9b6ab89..c2b0173 100644 --- a/.obsidian/plugins/various-complements/histories.json +++ b/.obsidian/plugins/various-complements/histories.json @@ -1 +1 @@ -{"AsyncCompute":{"AsyncCompute":{"currentFile":{"count":1,"lastUpdated":1749890609632}}}} \ No newline at end of file +{"Class":{"Class":{"currentFile":{"count":1,"lastUpdated":1748942840437}}},"DebugView":{"DebugView":{"internalLink":{"count":1,"lastUpdated":1749700323505}}},"XXXXX,之后点击回车。注:XXXXX为模拟器端口号,请参考打开的模拟器问题诊断内展示端口号或MuMu多开器12内的adb端口后再输入。":{"XXXXX,之后点击回车。注:XXXXX为模拟器端口号,请参考打开的模拟器问题诊断内展示端口号或MuMu多开器12内的adb端口后再输入。":{"currentFile":{"count":1,"lastUpdated":1750142832492}}},"\\Users\\loujiajie\\desktop\\log.txt":{"\\Users\\loujiajie\\desktop\\log.txt":{"currentFile":{"count":1,"lastUpdated":1750143148509}}}} \ No newline at end of file diff --git a/.obsidian/themes/assets/Yin and Yang Demo Header.png b/.obsidian/themes/assets/Yin and Yang Demo Header.png index 492b2d7..d564b57 100644 Binary files a/.obsidian/themes/assets/Yin and Yang Demo Header.png and b/.obsidian/themes/assets/Yin and Yang Demo Header.png differ diff --git a/.obsidian/themes/assets/dark1.png b/.obsidian/themes/assets/dark1.png index e90139e..3dc22ad 100644 Binary files a/.obsidian/themes/assets/dark1.png and b/.obsidian/themes/assets/dark1.png differ diff --git a/.obsidian/themes/assets/dark2.png b/.obsidian/themes/assets/dark2.png index 665b57c..6519c82 100644 Binary files a/.obsidian/themes/assets/dark2.png and b/.obsidian/themes/assets/dark2.png differ diff --git a/.obsidian/themes/assets/dark3.png b/.obsidian/themes/assets/dark3.png index 2fb0081..37b5abf 100644 Binary files a/.obsidian/themes/assets/dark3.png and b/.obsidian/themes/assets/dark3.png differ diff --git a/.obsidian/themes/assets/light1.png b/.obsidian/themes/assets/light1.png index f35fe55..52abbf1 100644 Binary files a/.obsidian/themes/assets/light1.png and b/.obsidian/themes/assets/light1.png differ diff --git a/.obsidian/themes/assets/light2.png b/.obsidian/themes/assets/light2.png index b44f2ea..abd9276 100644 Binary files a/.obsidian/themes/assets/light2.png and b/.obsidian/themes/assets/light2.png differ diff --git a/.obsidian/themes/assets/light3.png b/.obsidian/themes/assets/light3.png index 6e3f3af..aa3a04f 100644 Binary files a/.obsidian/themes/assets/light3.png and b/.obsidian/themes/assets/light3.png differ diff --git a/.obsidian/themes/assets/screenshot.png b/.obsidian/themes/assets/screenshot.png index db1d643..c5e813a 100644 Binary files a/.obsidian/themes/assets/screenshot.png and b/.obsidian/themes/assets/screenshot.png differ diff --git a/03-UnrealEngine/Moible/Android常用命令.md b/03-UnrealEngine/Moible/Android常用命令.md new file mode 100644 index 0000000..789a2e7 --- /dev/null +++ b/03-UnrealEngine/Moible/Android常用命令.md @@ -0,0 +1,175 @@ +--- +title: Android常用命令 +date: 2025-06-17 14:09:07 +excerpt: +tags: +rating: ⭐ +--- +# Adb +adb 是 Android 的调试工具,非常强大,熟悉一些 adb 的命令能够让效率加倍。 + +## 安装 Apk + adb install APK_FILE_NAME.apk + +## 启动 App +安装的 renderdoccmd 是没有桌面图标的,想要自己启动的话只能使用下列 adb 命令: +```bash +adb shell am start org.renderdoc.renderdoccmd.arm64/.Loader -e renderdoccmd "remoteserver" +``` + +adb 启动 App 的 shell 命令模板: +```bash +adb shell am start PACKAGE_NAME/.ActivityName +``` + +这个方法需要知道 App 的包名和 Activity 名,包名很容易知道,但是 Activity 如果不知道可以通过下列操作获取: + +首先使用一个反编译工具将 apk 解包(可以使用之前的[apktools](https://imzlp.com/notes/#apk%E7%9A%84%E5%8F%8D%E7%BC%96%E8%AF%91%E4%B8%8E%E7%AD%BE%E5%90%8D)): +```bash +apktool.bat d -o ./renderdoccmd_arm64 org.renderdoc.renderdoccmd.arm64.apk| +``` + +然后打开 `org.renderdoc.renderdoccmd.arm64` 目录下的 `AndroidManifest.xml` 文件,找到其中的 `Application` 项: +```xml + + + + + + + + + + +``` +其中有所有注册的`Activity`,没有有界面的 apk 只有一个 Activity,所以上面的 renderdoccmd 的主 Activity 就是`.Loader`。 + +如果说有界面的 app,则会有多个,则可以从 `AndroidManifest.xml` 查找 `Category` 或者根据命名 (名字带`main` 的 Activity)来判断哪个是主 Activity。一般都是从 lanucher 开始,到 main,或者有的进登陆界面。 + +> PS:使用 UE 打包出游戏的主 Activity 是`com.epicgames.ue4.SplashActivity`,可以通过下列命令启动。 + +```bash +adb shell am start com.imzlp.GWorld/com.epicgames.ue4.SplashActivity +``` + +## 传输文件 +使用 adb 往手机传文件: +```bash +adb push 1.0_Android_ETC2_P.pak /sdcard/Android/data/com.imzlp.TEST/files/UE4GameData/Mobile422/Mobile422/Saved/Paks +adb push FILE_NAME REMOATE_PATH +``` + +从手机传递到电脑: +```bash +adb pull /sdcard/Android/data/com.imzlp.TEST/files/UE4GameData/Mobile422/Mobile422/Saved/Paks/1.0_Android_ETC2_P.pak A.Pak +adb pull REMOATE_FILE_PATH LOCAL_PATH +``` + +## Logcat +使用 `logcast` 可以看到 Android 的设备 Log 信息。 +```bash +adb logcat +``` + +会打印出当前设备的所有信息,但是我们调试 App 时不需要看到这么多,可以使用 `find` 进行筛选(注意大小写严格区分): +```bash +adb logcat | find "GWorld" +adb logcat | find "KEY_WORD" +``` + +查看 UE 打包的 APP 所有的 log 可以筛选: +```bash + adb logcat | find "UE4" +``` + +如果运行的次数过多积累了大量的 Log,可以使用清理: +```bash +adb logcat -c +``` + +输出到文件: +```bash +adb logcat > c:\Users\loujiajie\Desktop\log.txt +``` +## 从设备中提取已安装的 APK +注意:执行下列命令时需要检查手机是否开放开发者权限,手机上提示的验证指纹信息要允许。 + +```bash +# 查看链接设备 +$ adb devices +List of devices attached +b2fcxxxx unauthorized +# 列出手机中安装的所有 app +$ adb shell pm list package +# 如果提示下问题,则需要执行 adb kill-server +error: device unauthorized. +This adb servers $ADB_VENDOR_KEYS is not set +Try 'adb kill-server' if that seems wrong. +Otherwise check for a confirmation dialog on your device. +# 正常情况下会列出一堆这样的列表 +C:\Users\imzlp>adb shell pm list package +package:com.miui.screenrecorder +package:com.amazon.mShop.android.shopping +package:com.mobisystems.office +package:com.weico.international +package:com.github.shadowsocks +package:com.android.cts.priv.ctsshim +package:com.sorcerer.sorcery.iconpack +package:com.google.android.youtube + +# 找到指定 app 的的 apk 位置 +$ adb shell pm path com.github.shadowsocks +package:/data/app/com.github.shadowsocks-iBtqbmLo8rYcq2BqFhJtsA==/base.apk +# 然后将该文件拉取到本地来即可 +$ adb pull /data/app/com.github.shadowsocks-iBtqbmLo8rYcq2BqFhJtsA==/base.apk +/data/app/com.github.shadowsocks-iBtqbmLo8rYcq2BqFhJtsA==/...se.apk: 1 file pulled. 21.5 MB/s (4843324 bytes in 0.215s) +``` + +## 刷入 Recovery +下载[Adb](https://ue5wiki.com/wiki/6fa72650/index/Adb.7z),然后根据具体情况使用下列命令(如果当前已经在 bootloader 就不需要执行第一条了)。 +```bash +adb reboot bootloader +# 写入 img 到设备 +fastboot flash recovery recovery.img +fastboot flash boot boot.img +# 引导 img +fastboot boot recovery.img +``` +## 端口转发 +可以通过 adb 命令来指定: +```bash +# PC to Device +adb reverse tcp:1985 tcp:1985 +# Device to PC +adb forward tcp:1985 tcp:1985 +``` + +- [Android 上超级好用的前端调试方法(adb reverse)](http://blog.xiaoyu.im/post_678.html) + +## 根据包名查看 apk 位置 +可以使用以下 adb 命令: +```bash +$ adb shell pm list package -f com.tencent.tmgp.fm +package:/data/app/com.tencent.tmgp.fm-a_cOsX8G3VClXwiI-RD9wQ==/base.apk=com.tencent.tmgp.fm +``` +最后一个参数是包名, 输出的则是 apk 的路径。 + +## 查看当前窗口的 app 的包名 +使用以下 adb 命令: +```bash +adb shell dumpsys window w | findstr \/ | findstr name= +mSurface=Surface(name=SideSlideGestureBar-Bottom)/@0xa618588 +mSurface=Surface(name=SideSlideGestureBar-Right)/@0x619b646 +mSurface=Surface(name=SideSlideGestureBar-Left)/@0xea02007 +mSurface=Surface(name=StatusBar)/@0x7e4962d +mAnimationIsEntrance=true mSurface=Surface(name=com.tencent.tmgp.fm/com.epicgames.ue4.GameActivity)/@0x43b30a0 +mSurface=Surface(name=com.tencent.tmgp.fm/com.epicgames.ue4.GameActivity)/@0xa3481e +mAnimationIsEntrance=true mSurface=Surface(name=com.vivo.livewallpaper.monster.bmw.MonsterWallpaperService)/@0x53e44ae +``` +其中的 `mAnimationIsEntrance=true mSurface=Surface(name=` 之后,到 `/` 之前的字符串就是我们的 app 包名。 + +# MuMu打开ABD方法 +1. CMD CD到MuMu安装目录下,例如:`E:\Program Files\Netease\MuMu Player 12\shell` +2. 在命令提示符窗口内输入adb.exe connect 127.0.0.1:XXXXX,之后点击回车。注:XXXXX为模拟器端口号,请参考打开的模拟器问题诊断内展示端口号或MuMu多开器12内的adb端口后再输入。 + 1. 在MuMu多开器右上角的ADB图标,点击就可以显示端口。 + 2. \ No newline at end of file diff --git a/03-UnrealEngine/Rendering/Debug/RenderDoc使用技巧.md b/03-UnrealEngine/Rendering/Debug/RenderDoc使用技巧.md index 48b4b10..488a911 100644 --- a/03-UnrealEngine/Rendering/Debug/RenderDoc使用技巧.md +++ b/03-UnrealEngine/Rendering/Debug/RenderDoc使用技巧.md @@ -200,4 +200,4 @@ Resource Inspector中右侧查看那些事件使用了此资源 - 最后填写运行exe程序路径。 ```bash renderdoccmd.exe capture -d "C:\Game\ScarletNexus" -w --opt-hook-children C:\Game\ScarletNexus\ScarletNexus.exe -``` \ No newline at end of file +```· \ No newline at end of file diff --git a/03-UnrealEngine/Rendering/RenderFeature/ShaderWorldPlugin/ShaderWorld.md b/03-UnrealEngine/Rendering/RenderFeature/ShaderWorldPlugin/ShaderWorld.md new file mode 100644 index 0000000..a3961c2 --- /dev/null +++ b/03-UnrealEngine/Rendering/RenderFeature/ShaderWorldPlugin/ShaderWorld.md @@ -0,0 +1,428 @@ +--- +title: Untitled +date: 2025-06-03 10:19:25 +excerpt: +tags: +rating: ⭐ +--- +# 前言 +1. ShaderWorldPCGInterop: +2. ShaderWorld: +3. ShaderWorldCore: + +# ShaderWorld +- Class + - Actor + - ShaderWorldActor.h:[[#AShaderWorldActor]] + - SWorld.h:[[#ASWorld]] + +## ASWorld +## AShaderWorldActor + + + +## DrawMaterialToRenderTarget +USWorldSubsystem::DrawMaterialToRenderTarget +=> +SWShaderToolBox::DrawMaterial +=> +DrawMaterial_CS_RT + +调用路径: +- AShaderWorldActor::[[#RetrieveHeightAt]](好像没有引用):检索高度 +- AShaderWorldActor::ComputeHeight_Segmented_MapForClipMap + - AShaderWorldActor::ProcessSegmentedComputation() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick() +- AShaderWorldActor::ComputeHeightMapForClipMap + - AShaderWorldActor::UpdateClipMap() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick() +- AShaderWorldActor::ComputeDataLayersForClipMap + - AShaderWorldActor::UpdateClipMap() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick() +- AShaderWorldActor::UpdateCollisionMeshData:更新碰撞模型数据。 + - AShaderWorldActor::CollisionGPU() <- AShaderWorldActor::CollisionManagement() <- AShaderWorldActor::Tick() +- FSpawnableMesh::UpdateSpawnableData + - AShaderWorldActor::ProcessSegmentedComputation() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick() + + +## Cache机制 +AShaderWorldActor::ProcessSegmentedComputation() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick() + + +# 其他Bug + + +## SetTextureParameterValue相关逻辑排查 +- AShaderWorldActor中的SetTextureParameterValue + - ~~ExportCacheInBounds~~ + - ~~AssignHeightMapToDynamicMaterial~~ + - UpdateStaticDataFor + - ComputeHeight_Segmented_MapForClipMap:似乎会设置 + - ~~UpdateCollisionMeshData~~ + - [x] [[#InitializeReadBackDependencies]] + - [x] InitiateMaterials + +### UpdateStaticDataFor + + +### ComputeHeight_Segmented_MapForClipMap +- 作用: +- 调用顺序:AShaderWorldActor::Tick() -> AShaderWorldActor::TerrainAndSpawnablesManagement() -> AShaderWorldActor::ProcessSegmentedComputation() -> ComputeHeight_Segmented_MapForClipMap + + +>// 1) Intersect clipmap with grid quad + // 2) Gather non computed quads + // 3) Allocated Compute element to missing Quad + // 4) Update the indirection data to the new elements + // 5) Update the Clipmap Heightmap with the grid data + + +### UpdateCollisionMeshData + - 作用: + 1. 判断DynCollisionMat是否有效,无效就使用`Generator`(高度数据生成材质)来创建。 + 2. 设置材质参数NoMargin、TexelPerSide、PatchFullSize、MeshScale。 + 3. 设置随机种子相关的材质参数。 + 4. 设置材质参数PatchLocation。 + 5. 生成碰撞数据到`CollisionRT`。 + 6. 笔刷功能逻辑:ApplyBrushStackToHeightMap()。 + 7. ExportPhysicalMaterialID逻辑。 + 8. GPU碰撞数据回读:ShaderWorld::AsyncReadPixelsFromRT()。 + 1. ShaderWorld::GSWReadbackManager.AddPendingReadBack(),将回读Task增加`TArray PendingReads;`。 + 2. 之后会在USWorldSubsystem::Tick()中调用ShaderWorld::GSWReadbackManager.TickReadBack(),不断检查是否可回读,并进行最终回读。 + - 调用顺序:Tick() -> CollisionManagement() -> CollisionGPU() -> UpdateCollisionMeshData() + +```c++ +namespace ShaderWorld +{ + FORCEINLINE void AsyncReadPixelsFromRT(UShaderWorldRT2D* InRT, TSharedPtr Destination, TSharedPtr < FThreadSafeBool, ESPMode::ThreadSafe> Completion) + { + + ENQUEUE_RENDER_COMMAND(ReadGeoClipMapRTCmd)( + [InRT, HeightData = Destination, Completion = Completion](FRHICommandListImmediate& RHICmdList) + { + check(IsInRenderingThread()); + + if (HeightData.IsValid() && InRT->GetResource()) + { + FRDGBuilder GraphBuilder(RHICmdList); + TSharedPtr ReadBackStaging = MakeShared(TEXT("SWGPUTextureReadback")); + FRDGTextureRef RDGSourceTexture = RegisterExternalTexture(GraphBuilder, InRT->GetResource()->TextureRHI, TEXT("SWSourceTextureToReadbackTexture")); + AddEnqueueCopyPass(GraphBuilder, ReadBackStaging.Get(), RDGSourceTexture); + GraphBuilder.Execute(); + + ShaderWorld::GSWReadbackManager.AddPendingReadBack(RHICmdList, GPixelFormats[RDGSourceTexture->Desc.Format].BlockBytes, RDGSourceTexture->Desc.Extent.X, RDGSourceTexture->Desc.Extent.Y, ReadBackStaging, const_cast&>(HeightData), const_cast&>(Completion)); + } + + }); + } +``` + + +### InitializeReadBackDependencies +- 作用:初始化几个GPU数据回读用的RT。 +- 调用顺序:BeginPlay() -> InitiateWorld() -> InitializeReadBackDependencies() + +1. 初始化3个RT:ReadRequestLocation、ReadRequestLocationHeightmap、GeneratorDynamicForReadBack。 +2. 会设置`TObjectPtr < UMaterialInstanceDynamic> GeneratorDynamicForReadBack`各种变量 +```c++ +GeneratorDynamicForReadBack->SetScalarParameterValue("HeightReadBack", 1.f); +GeneratorDynamicForReadBack->SetTextureParameterValue("SpecificLocationsRT", ReadRequestLocation); +GeneratorDynamicForReadBack->SetScalarParameterValue("NoMargin", 0.f); +GeneratorDynamicForReadBack->SetScalarParameterValue("N", N); +GeneratorDynamicForReadBack->SetScalarParameterValue("NormalMapSelect", 0.f); +GeneratorDynamicForReadBack->SetScalarParameterValue("HeightMapToggle", 1.f); +``` +3. 设置随机种子相关Shader Parameter。 +### InitiateMaterials +作用:初始化`TArray Meshes;`的Material、`Producers` +调用顺序:BeginPlay() -> InitiateWorld() -> InitiateMaterials() + +经过断点调试,会设置WorldSettings里的Material(地形Material)的HeightMap与NormalMap。 + + +# +SWorldSubsystem->DrawMaterialToRenderTarget + + +# Rebuild逻辑 +## 重要函数 +- AShaderWorldActor::BeginPlay() +- AShaderWorldActor::Setup()(<- TerrainAndSpawnablesManagement(float& DeltaT) <- Tick()) + +## Rebuild逻辑顺序 +1. AShaderWorldActor::BeginPlay() + 1. + + +# Debug +1. AShaderWorldActor::ComputeHeight_Segmented_MapForClipMap 十多次 +2. UpdateCollisionMeshData +3. AShaderWorldActor::ComputeHeight_Segmented_MapForClipMap 十多次 +4. [[#RetrieveHeightAt]] +5. UpdateCollisionMeshData 3次 +6. AShaderWorldActor::ComputeHeight_Segmented_MapForClipMap 十多次 + +# RetrieveHeightAt +可能存在bug待排查的: +- ShaderWorldSubsystem->LoadSampleLocationsInRT() +- ShaderWorldSubsystem->DrawMaterialToRenderTarget() + +## 相关变量 +- FThreadSafeBool + - bProcessingHeightRetrieval + - bProcessingHeightRetrievalRT +- MID + - GeneratorDynamicForReadBack +- UShaderWorldRT2D(`UTextureRenderTarget2D`) + - ReadRequestLocation:RTF_RG32f,初始化于`InitializeReadBackDependencies() <- InitiateWorld()` + - ReadRequestLocationHeightmap:RTF_RGBA8,初始化于`InitializeReadBackDependencies() <- InitiateWorld()` + + + +## 代码 +```c++ +bool AShaderWorldActor::RetrieveHeightAt(const TArray& Origin, const FSWHeightRetrievalDelegate& Callback) +{ + if (!GeneratorDynamicForReadBack || !SWorldSubsystem) + return false; + + if (!bProcessingHeightRetrieval.IsValid()) + { + bProcessingHeightRetrieval = MakeShared(); + bProcessingHeightRetrieval->AtomicSet(false); + } + if (!bProcessingHeightRetrievalRT.IsValid()) + { + bProcessingHeightRetrievalRT = MakeShared(); + bProcessingHeightRetrievalRT->AtomicSet(false); + } + + + if (!(*bProcessingHeightRetrieval.Get()) && ReadRequestLocation && ReadRequestLocationHeightmap && GeneratorDynamicForReadBack) + { + bProcessingHeightRetrieval->AtomicSet(true); + bProcessingHeightRetrievalRT->AtomicSet(false); + HeightRetrieveDelegate = Callback; + + //初始化采样点数组结构体FSWShareableSamplePoints + PointsPendingReadBacks = MakeShared(); + TSharedPtr& Samples = PointsPendingReadBacks; + + FBox BoundingBoxRead(Origin); + + Samples->PositionsXY.SetNum(25 * 2); + for (int i = 0; i < 25; i++) + { + if (i < Origin.Num()) + { + Samples->PositionsXY[i * 2] = Origin[i].X; + Samples->PositionsXY[i * 2 + 1] = Origin[i].Y; + } + else + { + Samples->PositionsXY[i * 2] = 0.f; + Samples->PositionsXY[i * 2 + 1] = 0.f; + } + } + + if (USWorldSubsystem* ShaderWorldSubsystem = SWorldSubsystem) + { + //从渲染线程 + ShaderWorldSubsystem->LoadSampleLocationsInRT(ReadRequestLocation, Samples); + +#if SW_COMPUTE_GENERATION + ShaderWorldSubsystem->DrawMaterialToRenderTarget( + { false, + false, + GetWorld()->Scene, + (float)GetWorld()->TimeSeconds, + false, + true, + ReadRequestLocationHeightmap->SizeX, + 10, + FVector(0.f), + true, + ReadRequestLocation, + GeneratorDynamicForReadBack, + ReadRequestLocationHeightmap + }); +#else + UKismetRenderingLibrary::DrawMaterialToRenderTarget(this, ReadRequestLocationHeightmap, GeneratorDynamicForReadBack); +#endif + + int32 Size_RT_Readback = ReadRequestLocationHeightmap.Get()->SizeX; + + FVector Barycentre = BoundingBoxRead.GetCenter(); + FVector Extent = BoundingBoxRead.GetExtent(); + float gridspacing = Extent.X * 2.0 / (Size_RT_Readback - 1); + + if (IsValid(BrushManager)) + BrushManager->ApplyBrushStackToHeightMap(this, 0, ReadRequestLocationHeightmap.Get(), Barycentre, gridspacing, Size_RT_Readback, true, true, ReadRequestLocation.Get()); + + + ReadBackHeightData = MakeShared(); + ReadBackHeightData->ReadData.SetNum(25); + + ENQUEUE_RENDER_COMMAND(ReadGeoClipMapRTCmd)( + [InRT = ReadRequestLocationHeightmap, HeightData = ReadBackHeightData, Completion = bProcessingHeightRetrievalRT](FRHICommandListImmediate& RHICmdList) + { + check(IsInRenderingThread()); + + if (HeightData.IsValid() && InRT->GetResource()) + { + FRDGBuilder GraphBuilder(RHICmdList); + + TSharedPtr ReadBackStaging = MakeShared(TEXT("SWGPUTextureReadback")); + + FRDGTextureRef RDGSourceTexture = RegisterExternalTexture(GraphBuilder, InRT->GetResource()->TextureRHI, TEXT("SWSourceTextureToReadbackTexture")); + + AddEnqueueCopyPass(GraphBuilder, ReadBackStaging.Get(), RDGSourceTexture); + + GraphBuilder.Execute(); + + ShaderWorld::GSWReadbackManager.AddPendingReadBack(RHICmdList, GPixelFormats[RDGSourceTexture->Desc.Format].BlockBytes, RDGSourceTexture->Desc.Extent.X, RDGSourceTexture->Desc.Extent.Y, ReadBackStaging, const_cast&>(HeightData), const_cast&>(Completion)); + + } + + }); + + HeightReadBackFence.BeginFence(true); + } + + return true; + } + + return false; +} +``` + + +### RequestReadBackLoad +```c++ +bool USWorldSubsystem::LoadSampleLocationsInRT(UShaderWorldRT2D* LocationsRequestedRT, + TSharedPtr& Samples) +{ + if (!RenderThreadResponded) + return false; + + const SWSampleRequestComputeData ReadBackData(LocationsRequestedRT, Samples); + SWToolBox->RequestReadBackLoad(ReadBackData); + return true; +} +``` +# SWShaderToolBox +## RequestReadBackLoad +```c++ +void SWShaderToolBox::RequestReadBackLoad(const SWSampleRequestComputeData& Data) const +{ + if (Data.CPU) + return CPUTools.RequestReadBackLoad(Data); + + ENQUEUE_RENDER_COMMAND(ShaderTools_copy_rt) + ([this, Data](FRHICommandListImmediate& RHICmdList) + { + if (Data.SamplesXY && Data.SamplesXY->GetResource()) + RequestReadBackLoad_RT(RHICmdList,Data); + } + ); + +} + +void SWShaderToolBox::RequestReadBackLoad_RT(FRHICommandListImmediate& RHICmdList, const SWSampleRequestComputeData& Data) const +{ + if (!(Data.SamplesXY && Data.SamplesXY->GetResource())) + return; + + FRDGBuilder GraphBuilder(RHICmdList); + + { + RDG_EVENT_SCOPE(GraphBuilder, "ShaderWorld PositionReadBack"); + RDG_GPU_STAT_SCOPE(GraphBuilder, ShaderWorldReadBack); + + FIntVector GroupCount; + GroupCount.X = FMath::DivideAndRoundUp((float)Data.SamplesXY->GetResource()->GetSizeX(), (float)SW_LoadReadBackLocations_GroupSizeX); + GroupCount.Y = FMath::DivideAndRoundUp((float)Data.SamplesXY->GetResource()->GetSizeY(), (float)SW_LoadReadBackLocations_GroupSizeY); + GroupCount.Z = 1; + + const FUnorderedAccessViewRHIRef RT_UAV = GraphBuilder.RHICmdList.CreateUnorderedAccessView(Data.SamplesXY->GetResource()->TextureRHI); + + const FRDGBufferRef LocationRequest = CreateUploadBuffer( + GraphBuilder, + TEXT("SWLoadSampleLocations"), + sizeof(float), + Data.SamplesSource->PositionsXY.Num(), + Data.SamplesSource->PositionsXY.GetData(), + Data.SamplesSource->PositionsXY.Num() * Data.SamplesSource->PositionsXY.GetTypeSize() + ); + + const FRDGBufferSRVRef LocationRequestSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(LocationRequest, PF_R32_FLOAT));; + + FLoadReadBackLocations_CS::FPermutationDomain PermutationVector; + TShaderMapRef ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel), PermutationVector); + + FLoadReadBackLocations_CS::FParameters* PassParameters = GraphBuilder.AllocParameters(); + PassParameters->SampleDim = Data.SamplesXY->GetResource()->GetSizeX(); + PassParameters->DestLocationsTex = RT_UAV; + PassParameters->SourceLocationBuffer = LocationRequestSRV; + + + GraphBuilder.AddPass( + RDG_EVENT_NAME("ShaderWorld LoadReadBacklocations_CS"), + PassParameters, + ERDGPassFlags::Compute | + ERDGPassFlags::NeverCull, + [PassParameters, ComputeShader, GroupCount](FRHICommandList& RHICmdList) + { + FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *PassParameters, GroupCount); + }); + + } + + GraphBuilder.Execute(); +} +``` + +FLoadReadBackLocations_CS +```c++ +uint SampleDim; +RWTexture2D DestLocationsTex; +Buffer SourceLocationBuffer; + +[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, THREADGROUP_SIZEZ)] +void SampleLocationLoaderCS(uint3 ThreadId : SV_DispatchThreadID) +{ + if (any(ThreadId.xy >= SampleDim.xx)) + return; + + uint IndexPixel = (ThreadId.x + ThreadId.y * SampleDim) * 2; + + DestLocationsTex[ThreadId.xy] = float2(SourceLocationBuffer[IndexPixel],SourceLocationBuffer[IndexPixel + 1]); +} +``` + + +# FSWDrawMaterial_SL_CS +IMPLEMENT_MATERIAL_SHADER_TYPE(template<>, FSWDrawMaterial_SL_CS, TEXT("/ShaderWorld/ShaderWorldUtilities.usf"), TEXT("DrawMaterialCS"), SF_Compute); + +FMeshMaterialShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); +OutEnvironment.SetDefine(TEXT("SW_DRAW_WITH_HEIGHTNORMAL"), 0); +OutEnvironment.SetDefine(TEXT("SW_DRAWMATERIAL"), 1); +OutEnvironment.SetDefine(TEXT("SW_SPECIFIC_LOCATION_DRAW"), 1); + +OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), bIsMobileRenderer ? SW_MobileLowSharedMemory_GroupSizeX : FComputeShaderUtils::kGolden2DGroupSize); +OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), bIsMobileRenderer ? SW_MobileLowSharedMemory_GroupSizeY : FComputeShaderUtils::kGolden2DGroupSize); +OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEZ"), 1); + +# +- M_Blank_HeightLayer + - MFC_WorldPositionNormal_Layer + - M_Blank_HeightLayer(未连接) + - MFC_Position_ForCache +- 其他 + - MF_CacheRead_Reference_Tessellation + - MFC_CacheRead:没有其他引用 + - MFC_CacheRead_Tessellationt + - MFC_CacheReadNoVertexManipulation + - MFC_ExternalCacheRead + + + +# 植被生成逻辑 +Tick() => TerrainAndSpawnablesManagement() => UpdateSpawnables() => UpdateSpawnable() \ No newline at end of file diff --git a/03-UnrealEngine/卡通渲染相关资料/渲染功能/其他渲染功能/风格化渲染.md b/03-UnrealEngine/卡通渲染相关资料/渲染功能/其他渲染功能/风格化渲染.md index c4fad31..55dc11f 100644 --- a/03-UnrealEngine/卡通渲染相关资料/渲染功能/其他渲染功能/风格化渲染.md +++ b/03-UnrealEngine/卡通渲染相关资料/渲染功能/其他渲染功能/风格化渲染.md @@ -39,6 +39,7 @@ rating: ⭐ - [ ] 【三渲二技法】《穿靴子的猫》同款Stamp Map制作 https://www.bilibili.com/video/BV1ou411c7pE/?spm_id_from=333.1387.favlist.content.click&vd_source=d47c0bb42f9c72fd7d74562185cee290 - 水墨效果参考 - https://zhuanlan.zhihu.com/p/602960198 + - https://github.com/sacshadow/3D_ChineseInkPaintingStyleShader - https://zhuanlan.zhihu.com/p/98948117 - https://zhuanlan.zhihu.com/p/63893540 - https://zhuanlan.zhihu.com/p/34406208 diff --git a/03-UnrealEngine/性能优化/UE5优化方法与实践笔记.md b/03-UnrealEngine/性能优化/UE5优化方法与实践笔记.md index 330594a..51c5af1 100644 --- a/03-UnrealEngine/性能优化/UE5优化方法与实践笔记.md +++ b/03-UnrealEngine/性能优化/UE5优化方法与实践笔记.md @@ -276,4 +276,211 @@ UE5使用DLSS时需要关闭TAA,并且调整[[ScreenPercentage与描边宽度 - More Bools! - bRenderAsStatic :UE5.2不存在该选项。 - bPauseAnims:该选项位于SkeletalMeshComponent。 - - bNoSkeletonUpdate:该选项位于SkeletalMeshComponent。 \ No newline at end of file + - bNoSkeletonUpdate:该选项位于SkeletalMeshComponent。 + +# DrawCall优化相关 +使用**Stat DrawCount** 可以参看当前视角所有Pass所有DrawCall。 + +使用**Stat SceneRendering** 可以查看Mesh Draw Call。 + +# Shadow优化 +原文:https://zhuanlan.zhihu.com/p/644620609 +## 阴影 +阴影主要聊一下平行光的两个解决方案,一个传统的[CSM](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=CSM&zhida_source=entity),一个是ue5配合nanite的[VSM](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=VSM&zhida_source=entity).传统的CSM效果最好,没有噪点,也没有BUG,但是走了Nanite的话,CSM跟nanite配合是水士不服。UE5主推的VSM,性能最好,但是bug多,有噪点,但是又没办法跟距离场配合,开启了VSM,平行光的阴影只有VSM这一种,真是又爱又恨。 + +### csm +CSM的实行网上教程很多,这里主要聊优化手段。 +- CSM缓存 +可以通过r.Shadow.CSMCaching修改为1,开启csm缓存.它会为静态和动态的对像分别启用一张深度图,动态的深度图每帧都会重绘,但是可以通过r.Shadow.CacheWPOPrimitives调整,让WPO的也强制缓存。当两帧平行光没有变化,而且两帧的CSM重叠超过一个阈值的时候,CSM会复用上一帧的深度图,这个阈值通过r.Shadow.CSMScrollingOverlapAreaThrottle调整,默认是0.75。这个CSM缓存在低分辨率下的时候也个好优化,对于高分辨率,深度图失效的时候会重现申请,申请这个RT是个不少的消耗,这一点要注意。 + +- CSM层遮挡剔除 +CSM有好多层,如果前面有个建筑物很高,完全档住人你的视线,这里CSM,远处的层级照样绘制深度,这个是没有必要的,可以通过遮挡剔除优化掉,UE也做了这个优化。过能r.Shadow.OcclusionCullCascadedShadowMaps修改,让不在视野范围的深度图不参与计算,这个是个不错的优化,最好开启。 + +- 调整屏幕占比factor +对于一些特别远的,屏幕占比很低的物体,没必要去生成深度,可以通过修改r.Shadow.RadiusThreshold生成深度图的对像的半径阈值。 + +- 强制使用LOD +可以通过r.ForceLODShadow调用生成阴影深度的LOD,这个是个大优化。 + +- 调整分辨率 +这个优化是最好的,直接把分辨率降低,消耗直接变低。r.Shadow.MaxCSMResolution可以通过此参数调整csm每层的分辨率。 + +- 逐帧更新 +因为csm有很多层,对于远处的层,没必要每帧都更新,可以在csm缓存的代码里,修改一下,就可以做到远处的层级逐帧更新,这个优化,可以把CSM的性能提高了好多。 + +- 代理阴影 +对于非nanite的网格体,有强制使用LOD,可以降低深度图对像的面数,但是对于nanite,前面的手段是不生效的。对于nanite来说,走的是自已的[raster](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=raster&zhida_source=entity)。但是很多时候nanite光栅化出来的面数非常巨大,引起nanite切换lod失效的因素非常多。所以对于那些远处,又或者自投影细节比较少的网格体,可以使用代理nanite阴影去解决。修改引擎支持proxyshadow component,只设置影响深度pass,其它pass都不绘制,可以解决nanite在csm情况下LOD失效的时候减面优化性能。 + +上面的手段,基本都是对于非nanite的有效,对于nanite的,上面的优化手段基本都是失效的,nanite的网格全是gpu driver的,有自已的渲染管线。 + +### vsm +VSM的原理就是一张大VT,不同clipmap范围的VT页分辨率不一样,同时动静分离,在平行光不变的情况下,静态页面复用,只重绘动态的页面,同时对于动态的页面缓存,非nanite的网格体也可以缓存,可以通过r.Shadow.Virtual.Cache.MaxMaterialPositionInvalidationRange调整数值,让离自已一定范围内的非nanite才失效,超过强行缓存。对于平行光不停在变情况的情况下,所有页面都在失效,这时可以关闭缓存(r.shadow.vsm.cache),反而可以提高一点性能,节省一些显存,因为不分动静页,也不会分析哪些而页面失效。 + +目前VSM的问题比较多,主要集中在两个方面,bug多和噪点问题,bug比如,页面分析出错,应该失效的页面不失效,大范围WPO导致超出cluster bound box,导致深度图被clamp在一个范围内,同一个物体的深度在不同clipmap下的页面不一至 ,导致一些奇奇怪怪的阴影错乱问题, 噪点问题是算法决定了,smrt,判断是否是软阴影的一个算法,当一个像素发送射线过多,会导致性能不行,过低,噪点严重。总体来说,VSM,还不能完全线上使用。 + +VSM配合nanite使用,性能是非常好的,下面讲一下VSM的一些优化手段,主要是降分辨率。 + +- clipmap范围与页面密度 +可以通过调用r.Shadow.Virtual.Clipmap.FistLevel和LastLevel来调clipmap的覆盖范围,在不同的clipmap有同的页面覆盖面积。这里不仅可以优化性能,也可以优化显存。 + +- 分辨率bias +在clipmap范围不变的情况下,可以通过调整每个页面的覆盖范围,通过调整r.Shadow.Virtual.ResolutionLodBiasDirectional来修改平行光的页面覆盖面积以达到降分辨率的处理 + +- 非nanite物体wpo强制缓存 +调整r.Shadow.Virtual.Cache.MaxMaterialPositionInvalidationRange数值,让在数值范围内的WPO强制使用缓存。 + +### 距离场阴影 +距离场阴影的原理是,把屏幕切tile,在cs里,找到每个tile对应有可能overlap的距离场,然后每个tile每个像素追踪距离场,就可以知道像素是否在阴影下。距离场阴影性能非常好。对于远处的物体阴影 ,都建议开距离场阴影。但是,在VSM开启的情况下,Nanite物体距离场是失效的。 + +对于点光的阴影,可以通过在远距离时使用距离场阴影,近距离影时使用shadowmap。 + +这里提示一点,在室内或者完全不受平行光的环境下,可以把平行光的阴影关掉,用距离场去代替。 + +### 接触阴影 +接触阴影主要是屏幕空间下的通过hzb追踪得来的阴影,他会有效果表现下比较差,但是对于植被这种,特别适合,当场景大量存在存被的情况下,可以开启接触阴影。 + +### 点光源 +对于点光在GPU端,分两种情况,直接阴影和间接阴影。有三种阴影选择,深度图,接触阴影,距离场,接触阴影效果不好,深度图精度最高,支持动态物体,但是很影响性能,距离场性能最好,但是不支持动态物体。对于远一点的,可以用距离场代替,随着距离变近,切换到shadowmap,把光源交由逻辑层管理。 + +如果一个光源间接光照强度大于0,此光源就会在[lumen scene](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=lumen+scene&zhida_source=entity)生效,在lumen scene里不仅要计算光照,也要计算阴影,包括离屏阴影,性能消耗不低,无论使用shadowmap还是距离场,这里交由脚本层管理,过远的把间接光照强度调成0,不参与lumen scene. + +## [RVT](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=RVT&zhida_source=entity) +RVT的使用教程知乎非常多,这里聊一下性能优化点。对于贴图混合层特别多的时候,用RVT可以优化非常大的性能,特别是地形,但是RVT有时候也会突然消耗非常高,可达3MS以上,下面各个聊。 + +### produce与upload +当VT不存在,或者MIP级别不匹配的时候,引擎会从磁盘(svt)或者通过drawcall(rvt)生成对应的tile,这个过程叫produce,当生成后,就会upload到gpu中,更新indirect table和vt图集,这个过种叫upload. + +### produce +svt的produce不影响性能,主要影响性的是地形的rvt的produce,因为地形的图层太多了,这里会通过调用地形的材质drawcall生成对应的vt tile.这里性能消耗是不低的,可以通过r.VT.MaxTilesProducedPerFrame调整数量,注意,这个参数是包括svt的tile数量。 + +### upload +一般upload不会产生GPU的消耗,除非你的upload数量非常高。可以通过r.VT.MaxUploadsPerFrame调整每帧的upload数量,编辑下通过r.VT.MaxUploadsPerFrameInEditor调整。 + +### VT池问题 +当VT池不足的时候,vt池会不停的切换mip以降显存,这里GPU的VT消耗就会起来。可以通过命令r.VT.Residency.Show查看哪些VT池不足,也可以通过r.VT.Residency.Notify,当对应的池不足切mip的时候,屏幕下会有打印warning,找到对应的池,在配置文件里增大显存。 + +### 地形 +地形用RVT去减少采样是个非常大的优化,另外RVT支持离线烘倍,可以把低的mip离线烘倍,减少切mip的时候的draw call发生以优化性能。通过RVT Volumn下的虚拟纹理构建,把低的MIP烘倍出来。 + +## Nanite +nanite是UE5最稳定,性能最好的新技术,对于实现原理,没有想像中这么复杂,可以去看一下知乎大佬丛越系列的文章,讲的非常详细,这里聊一下nanite遇到的性能问题。 + +### WPO +WPO非常影响性能,当cluster剔除后,光栅化后出来的面数也不低,全开WPO,会让vibility buffer生成性能翻倍,这里可以使用世界位置偏移禁用距离调整,超过距离后WPO禁用 + +### Mask +当有mask材质的时候,没办法通过HZB做剔除,只能先光栅化出来才能通过alphatest来决定去流,overdraw非常严重,对于nanite的材质,最好禁用mask材质。使用mask材质性能非常低,之前有测试过,有mask性能直接减半。 + +### LOD减面失效问题 +nanite的减面是要依赖平滑法线组去决定三角面是否能合并,如果一个模形导出的时候没有平滑法线组,UE会有warning,这个要特别注意,如果没有平滑法线组,面数非常爆裂。 + +### Drawcall问题 +nanite的bass pass是通过在屏幕空间切tile,每个tile有要用到drawcall的材质,CPU端是不知道此时GPU端哪些材质是否draw,所以nanite的draw call是在CPU端,把加载进来的所有材质都发起drawcall,每个drawcall是一个quad,没有在屏幕的时候在vs阶段就culling掉.正常来说,一个culling掉的drawcall是基本不耗时的,但是在一些老显卡(20系),一个culling掉的drawcall,也会有小耗时,如果材质多的话,这些culling掉的drawcall是个不小的消耗。 + +### 破面问题 +引起破面的问题主要有下面几个因素,但是几个因素引起的表现不太一样,下面一个一个聊。 + +### 实例数 +nanite在raster的时候会限制BVH node节点数量,可以通过r.Nanite.MaxNodes去调整,当实例数超过上限的时候,场景每帧会随机掉面。 + +### cluster数 +这里有两个类型cluster,一个是candicate cluster,一个是visiable cluster,分别通过r.Nanite.MaxCandidateClusters和r.Nanite.MaxVisibleClusters,当场景cluster数量足的时候,场景会随机掉失cluster,整个画面在闪烁。 + +对于实例数和cluster数的统计,可以通过nanitestats去看到当前的数据,可能数据非常接近上限的时候,就要调整,这里要注意一点,当使用VSM的时候,如果在VSM的view这些数量也超过了,会导致深度生成每帧都不一样,阴影会错乱. + +### nanite流送池显存不足 +nanite是通过每帧的feedback去让CPU发起流送的,当流送池显存不够的时候,就是流送低的cluster tree,这个时候整个场景的面数非常低,而且会破面。 + +### cluster id上限 +这个没找到原因,当上述所有条件都足够的情况下,仍然会出现随机破面,画面闪烁的问题。当场景存在大量的nanite,而且每个nanite实例都是分离的,最后退化一个cluster的时候,这个cluster仍然很多,这个时候,也会随机破面,目前怀疑是跟 cluster id上限的问题。主要出现在植被非常多的时候。 + +## Lumen +UE5默认lumen是把效果,质量都是拉满的,如果默认使用lumen的参数,性能很炸。对于lumen,反而bug比较少,主要问题是在集中在性能问题。lumen对场景的效果的提升确实有的,但是很多时候,质量拉满和质量很低的情况下,画面其实看不出有多少区别,所以,在我看来,lumen要用,但不须要很高的质量,基本于这个观点出现,lumen就有很多地方可以优化,如果当某个地方须要很高lumen质量的时候,可以在拉一个新的post process volume,提高lumen的质量。lumen整体分成三个部分,lighting,probe gather,反射,下面集中围绕这三个聊一下优能优化的点。另外,想了解lumen的实现,可以参考丛越大佬的文章,写得非常详细。 + +### Lighting速度 +lumen首先通地在lumen的direct ligting,再加上radiosity的得到每一个patch的lighting,最终combine lighting成indirect lighting.在接下的阶段final gather中,放入probe去trace,得到final lighting.这里有两个参数比较影响性能。 + +- direct lighting速度 +可以通过修改r.LumenScene.DirectLighting.UpdateFactor参数和LumenSceneLightingUpdateSpeed两个参数控制更新速度,前者是分母,后者是分子,这两个参数共同决定了当前帧分配更新图集大小。此图集通过PriorityHistogram中收集优先级的surface cache的tile放入到更新图集,从而达到分帧更新。 + +- final gather速度 +总体流程跟上面类似,通过r.Lumen.ScreenProbeGather.RadianceCache.NumProbesToTraceBudget和LumenFinalGatherLightingUpdateSpeed参数总同决定了final gather阶段最大的trace probe数量,在SelectMaxPriorityBucketCS中选择优化级大的,而且小于上述两个参数共同决定的最大上限probe数去trace. + +### 追踪距离 +可以通过r.Lumen.TraceDistanceScale和LumenMaxTraceDistance去决定在lumen下距离场的最大追踪距离。这两个参数是影响整个lumen须要距离场cone trace追踪的所有pass,包括final gather的radian cache trace,radiosity的trace,reflect的trace,减少追踪距离,可以优化性能,但是会带来一些不想要的后果,比如反射不到,lumen scene阴影漏光(导致场景有些地方会有从亮到暗的过程),泄露天光等。这里可以在不同区域拉不同的pp盒子,在一些野外,把追踪距离设置小一点提高性能,在一些封闭的地方或者须要反射的地方,可以把追踪距离设置大点。 + +### 离屏阴影 +lumen scene的lighting不仅要算colour,也要计算阴影,它不仅是当前屏幕的,是包括整个surface cache的,对于离屏阴影,UE默认使用mesh object sdf去追踪,如果场景存在大量物体,每个物体都有[mesh sdf](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=mesh+sdf&zhida_source=entity),这样子每个surface cache tile对应的可能对交的mesh sdf非常多,每个一像素对各个mesh sdf追踪是个很大的性能问题,而且对于离屏的阴影,大部分是不须要mesh sdf去追踪,野外大场景,如果一个mesh太小,就算lumen scene 露光也看不出来,对于室内,存在小的sdf漏光找美术去调整就好。 + +能过r.LumenScene.DirectLighting.OffscreenShadowing.TraceMeshSDFs设置为0,关闭离屏的mesh sdf trace,使用global sdf去trace. + +### Mesh SDF +mesh sdf是lumen的核心,就我看来,目前lumen的消耗有80%来源于mesh sdf,用到mesh sdf的地方非常多。可以通过LumenSceneDetail和r.Lumen.DiffuseIndirect.MeshSDF.RadiusThreshold共同控制,让物体包围球的半径大于上述两个决定的数值的时候,才参与mesh sdf的软追踪。这个优化非常大,特别场景存在大量的小物体的时候,比如植被,这些小物体对于lumen scene的贡献非常低,当然,除了反射。为了解决反射这种情况,我们并没有使用上面的参数控制小物体距离场生效与否,我们直接把小物体的距离场全都默认关闭,这样子,对于须要反射的地方,小物体手动开启距离场。 + +另外,调整LumenSceneDetail不仅会影响mesh sdf的culling,也会导致mesh card的丢失,这个参数会引够mesh card小于某个分辨率的时候也会culling掉,在surceface cache视图可以看到很多黄色图块(黄色的代表culling掉,紫色代表没有surface cache),这会引起光照的异常(黑影)。 + +### TraceProbe数量 +在final gather阶段是非常耗时的,最直观的影响因素就是trace射线的数量,它是由八面体probe的密度,每个probe trace的射线数量共同决定。probe的密度通过r.Lumen.ScreenProbeGather.DownsampleFactor和LumenFinalGatherQuality共同决定,八面体,每个面的trace线射数量由r.Lumen.ScreenProbeGather.TracingOctahedronResolution和LumenFinalGatherQuality共同决定。这个参数数值,可以在不同的情况调整不同的数值,在野外调到最低,在室内或者封闭的地方相应调高。 + +### 反射分辨率 +这个非常影响性能,这个目前没找到优化方案,最快的解决方式是降低反射buffer的分辨率,须要高精度的反射,比如达到镜面反射的情况,把pp盒的lumen反射质量调高,其它情况默认调到中档质量,低质量是没有反射。 + +lumen默认管方都是质量拉满的,可以调优的地方非常多,另外,lumen非常吃显存,对于显存部分的,有机会在显存篇幅里介绍。 + +### 异步Lumen +lumen在5.1后支持了异步lumen,这个是依赖GPU Async Compute,这个开启后在我们项目Lumen能节省1~2ms的消耗。 + +- 异步Light Probe Gather +首先要支持GSupportsEfficientAsyncCompute,AMD默认支持,N卡默认关闭,可以通过命名行ForceAsyncCompute强制打开。 + + + +另外,通过r.LumenScene.DirectLighting.ReuseShadowMaps关闭lumen scene下的复用阴影的shadow map,使用距离场阴影代替,注意,这会导致动态骨骼在lumen scene没有阴影,而且距离场阴影是很难跟shadowmap的阴影一致的,这会导致两种方式下final lighting会不一样。r.Lumen.DiffuseIndirect.AsyncCompute打开支持异步light probe gather,通过以上三个,异步Light Probe Gather就支持了。 + +- 异步Reflect +在r.LumenScene.DirectLighting.ReuseShadowMaps关闭下,异步反射可以通过r.Lumen.Reflections.AsyncCompute打开了。 + +- 异步Lighting +可以通过r.LumenScene.Lighting.AsyncCompute打开异步lighting. + +通过上面三个把异步lumen全部打开的情况下,在已经经过极致优化的lumen下,还可以优化到1到2MS,把lumen在GPU的耗时压得很低,而且不失效果。 + +## 植被问题 +植被有两个方向可以走,nanite与普通植被,各有利弊。 + +### nanite植被 +当植被开启nanite后,性能可以提高一倍以上,就算面数非常高,不可视的面都在nanite culling介段剔除掉。nanite植被有两个问题非常影响性能,wpo和mask,wpo前面nanite有提到,可以超过一定的距离禁用。对于mask材质,建议走实体模型。另外,当植被数量到达一定程度,nanite cluster会随机丢失造成画面闪烁。 + +### 普通植被 +普通植被最快,最有效的优化方式是做好LOD,减少culling distance.除此之外,还有两个因素对性能也有一定的影响。 + +- foliage actor的范围 +foliage actor的默认tile的大小是256,如果一个tile下有大量的植被,而且植被密度非常浓密的时候,这里不仅会引起分区流送引起cluster tree构建的卡顿,也会导致gpu下prepass和basspass消耗过高,因为默认的剔除盒子太大,导致prepass和basspass存在大量的vs的overdraw. + +- cluster tree遮挡剔除盒子大小 +cluste tree的剔除盒大小,决定了这一块instance是否绘制,过大的盒子,会引起prepass和basspass的vs阶段的overdraw,过小的盒子,导致存在大量的遮挡剔除查询导致render线程耗时过高,而且也会产生过多的drawcall。引擎对于cluster tree的控制参数都是全局的,有一些component适用,也一些则不合适。过密的component须要提高盒子的粒度,过于稀疏而且面数不高的,可以减少盒子数。因为,如果植被特别影响性能的时候,可以修改引擎支持让这些参数跟着component走。引起cluster tree盒子的因素有点多,这里主要聊三个。 + +- foliage.MaxOcclusionQueriesPerComponent +控制每个component的最多盒子数 + +- foliage.MinOcclusionQueriesPerComponent +控制每个component的最少盒子数 + +- foliage.MinInstancesPerOcclusionQuery +控制每个盒子最小包含的实例数 + +如果这个盒子粒度控制好,在植被比较多的情况下,可以在prepass和basspass省下2MS的预算。 + +## SingleLayerWater +如果水材质的效果不是用面表达的,是用法线贴图去做的话,可以把材质的mesh换成一个quad,高面的plane,在singlelayerwater里非常耗时。 + +## 直接光照 +### cluster deffer lighting +推荐知乎大佬(张亚坤:多光源渲染的版本答案:Cluster Shading)的文章,对于cluster deferred lighting有个介绍,先简单说一下cluster deferred流程。在gather light介段,把视锥体切豆腐,在cs中把光源注入到格子中,然后在cluster deferring shading的发起一个quad的drawcall,每个像素找到对应的grid,从grid中找到对应的light,叠加shading.cluster shading性能肯定是非常好的,优化地方不多,只与采样的buffer大小有关。 + +UE5默认是关闭cluster deffered shading的,可以通过r.UseClusteredDeferredShading打开。 + +引起不能cluster shading的原因有很多,如产生阴影,有light function,使用light channel,平行光,rect光等。在非cluster shading与unbatch light之间,还有一些light,主要是非shadow,非light function的,主要是rect等,每一个发起一个drawcall,这里消耗不低,能减少就减少一下,把生效距离设置短一点。 + +### unbatch light +对于unbatch light,主要是lumen scene light和正常的直接光照light,这里就不说了,光照lighting,shadow mask生成,有距离场阴影的也在这里追踪,建议就是交由逻辑层管理,根据不同策略动态开关。 diff --git a/03-UnrealEngine/性能优化/UnrealInsight以及其他性能监测工具.md b/03-UnrealEngine/性能优化/UnrealInsight以及其他性能监测工具.md index 4dcc36e..b1eb8cd 100644 --- a/03-UnrealEngine/性能优化/UnrealInsight以及其他性能监测工具.md +++ b/03-UnrealEngine/性能优化/UnrealInsight以及其他性能监测工具.md @@ -93,6 +93,13 @@ Unreal Insights聆听TCP端口1980。 - File - Net +### Android诊断方法 +- [Gathering Unreal Insights Traces on Android](https://dev.epicgames.com/community/learning/tutorials/eB9R/unreal-engine-gathering-unreal-insights-traces-on-android) +- [Android设备上的Unreal Insights](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/how-to-use-unreal-insights-to-profile-android-games-for-unreal-engine) + +可以使用IP直接连接。 + + ## Stat 官方文档:[Stat命令](https://docs.unrealengine.com/4.27/zh-CN/TestingAndOptimization/PerformanceAndProfiling/StatCommands/) 参考文章:[UE4 性能 - (一)瓶颈定位](https://zhuanlan.zhihu.com/p/438543980) diff --git a/03-UnrealEngine/性能优化/移动端/UE5移动端优化方法与实践笔记.md b/03-UnrealEngine/性能优化/移动端/UE5移动端优化方法与实践笔记.md new file mode 100644 index 0000000..9ef7c53 --- /dev/null +++ b/03-UnrealEngine/性能优化/移动端/UE5移动端优化方法与实践笔记.md @@ -0,0 +1,15 @@ +--- +title: Untitled +date: 2025-06-09 18:05:03 +excerpt: +tags: +rating: ⭐ +--- +# 前言 +- [移动游戏的性能优化 | 材质优化篇 - 腾讯游戏学堂的文章](https://zhuanlan.zhihu.com/p/688098852) +- [UE4移动端性能分析](https://zhuanlan.zhihu.com/p/1034898589) + +# 合批 +## UE4移动端合批 +- [UE4 4.23 移动端渲染的Dynamic Instancing分析(上)](https://zhuanlan.zhihu.com/p/99789976) +- [UE4 4.23 移动端渲染的Dynamic Instancing分析(下)](https://zhuanlan.zhihu.com/p/100224369) \ No newline at end of file