Merge remote-tracking branch 'blueroses.top/master'

# Conflicts:
#	.obsidian/plugins/various-complements/histories.json
This commit is contained in:
BlueRose 2025-06-24 20:24:03 +08:00
commit fcf82064cb
16 changed files with 836 additions and 3 deletions

View File

@ -1 +1 @@
{"AsyncCompute":{"AsyncCompute":{"currentFile":{"count":1,"lastUpdated":1749890609632}}}} {"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}}}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 794 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 871 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 731 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 841 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 854 KiB

After

Width:  |  Height:  |  Size: 131 B

View File

@ -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
<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.renderdoc.renderdoccmd.arm64" platformBuildVersionCode="26" platformBuildVersionName="8.0.0">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-feature android:glEsVersion="0x00030000" android:required="true"/>
<application android:debuggable="true" android:hasCode="true" android:icon="@drawable/icon" android:label="RenderDocCmd">
<activity android:configChanges="keyboardHidden|orientation" android:exported="true" android:label="RenderDoc" android:name=".Loader" android:screenOrientation="landscape">
<meta-data android:name="android.app.lib_name" android:value="renderdoccmd"/>
</activity>
</application>
</manifest>
```
其中有所有注册的`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.

View File

@ -200,4 +200,4 @@ Resource Inspector中右侧查看那些事件使用了此资源
- 最后填写运行exe程序路径。 - 最后填写运行exe程序路径。
```bash ```bash
renderdoccmd.exe capture -d "C:\Game\ScarletNexus" -w --opt-hook-children C:\Game\ScarletNexus\ScarletNexus.exe renderdoccmd.exe capture -d "C:\Game\ScarletNexus" -w --opt-hook-children C:\Game\ScarletNexus\ScarletNexus.exe
``` ```·

View File

@ -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<FReadBackTask> PendingReads;`
2. 之后会在USWorldSubsystem::Tick()中调用ShaderWorld::GSWReadbackManager.TickReadBack(),不断检查是否可回读,并进行最终回读。
- 调用顺序Tick() -> CollisionManagement() -> CollisionGPU() -> UpdateCollisionMeshData()
```c++
namespace ShaderWorld
{
FORCEINLINE void AsyncReadPixelsFromRT(UShaderWorldRT2D* InRT, TSharedPtr<FSWColorRead, ESPMode::ThreadSafe> 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<FRHIGPUTextureReadback> ReadBackStaging = MakeShared<FRHIGPUTextureReadback>(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<TSharedPtr<FSWColorRead, ESPMode::ThreadSafe>&>(HeightData), const_cast<TSharedPtr < FThreadSafeBool, ESPMode::ThreadSafe>&>(Completion));
}
});
}
```
### InitializeReadBackDependencies
- 作用初始化几个GPU数据回读用的RT。
- 调用顺序BeginPlay() -> InitiateWorld() -> InitializeReadBackDependencies()
1. 初始化3个RTReadRequestLocation、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<FClipMapMeshElement> 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`
- ReadRequestLocationRTF_RG32f初始化于`InitializeReadBackDependencies() <- InitiateWorld()`
- ReadRequestLocationHeightmapRTF_RGBA8初始化于`InitializeReadBackDependencies() <- InitiateWorld()`
## 代码
```c++
bool AShaderWorldActor::RetrieveHeightAt(const TArray<FVector>& Origin, const FSWHeightRetrievalDelegate& Callback)
{
if (!GeneratorDynamicForReadBack || !SWorldSubsystem)
return false;
if (!bProcessingHeightRetrieval.IsValid())
{
bProcessingHeightRetrieval = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
bProcessingHeightRetrieval->AtomicSet(false);
}
if (!bProcessingHeightRetrievalRT.IsValid())
{
bProcessingHeightRetrievalRT = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
bProcessingHeightRetrievalRT->AtomicSet(false);
}
if (!(*bProcessingHeightRetrieval.Get()) && ReadRequestLocation && ReadRequestLocationHeightmap && GeneratorDynamicForReadBack)
{
bProcessingHeightRetrieval->AtomicSet(true);
bProcessingHeightRetrievalRT->AtomicSet(false);
HeightRetrieveDelegate = Callback;
//初始化采样点数组结构体FSWShareableSamplePoints
PointsPendingReadBacks = MakeShared<FSWShareableSamplePoints, ESPMode::ThreadSafe>();
TSharedPtr<FSWShareableSamplePoints>& 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<FSWColorRead, ESPMode::ThreadSafe>();
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<FRHIGPUTextureReadback> ReadBackStaging = MakeShared<FRHIGPUTextureReadback>(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<TSharedPtr<FSWColorRead, ESPMode::ThreadSafe>&>(HeightData), const_cast<TSharedPtr < FThreadSafeBool, ESPMode::ThreadSafe>&>(Completion));
}
});
HeightReadBackFence.BeginFence(true);
}
return true;
}
return false;
}
```
### RequestReadBackLoad
```c++
bool USWorldSubsystem::LoadSampleLocationsInRT(UShaderWorldRT2D* LocationsRequestedRT,
TSharedPtr<FSWShareableSamplePoints>& 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<FLoadReadBackLocations_CS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel), PermutationVector);
FLoadReadBackLocations_CS::FParameters* PassParameters = GraphBuilder.AllocParameters<FLoadReadBackLocations_CS::FParameters>();
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<float2> DestLocationsTex;
Buffer<float> 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()

View File

@ -39,6 +39,7 @@ rating: ⭐
- [ ] 【三渲二技法】《穿靴子的猫》同款Stamp Map制作 https://www.bilibili.com/video/BV1ou411c7pE/?spm_id_from=333.1387.favlist.content.click&vd_source=d47c0bb42f9c72fd7d74562185cee290 - [ ] 【三渲二技法】《穿靴子的猫》同款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://zhuanlan.zhihu.com/p/602960198
- https://github.com/sacshadow/3D_ChineseInkPaintingStyleShader
- https://zhuanlan.zhihu.com/p/98948117 - https://zhuanlan.zhihu.com/p/98948117
- https://zhuanlan.zhihu.com/p/63893540 - https://zhuanlan.zhihu.com/p/63893540
- https://zhuanlan.zhihu.com/p/34406208 - https://zhuanlan.zhihu.com/p/34406208

View File

@ -276,4 +276,211 @@ UE5使用DLSS时需要关闭TAA并且调整[[ScreenPercentage与描边宽度
- More Bools! - More Bools!
- bRenderAsStatic UE5.2不存在该选项。 - bRenderAsStatic UE5.2不存在该选项。
- bPauseAnims该选项位于SkeletalMeshComponent。 - bPauseAnims该选项位于SkeletalMeshComponent。
- bNoSkeletonUpdate该选项位于SkeletalMeshComponent。 - 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和maskwpo前面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生成有距离场阴影的也在这里追踪建议就是交由逻辑层管理根据不同策略动态开关。

View File

@ -93,6 +93,13 @@ Unreal Insights聆听TCP端口1980。
- File - File
- Net - 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
官方文档:[Stat命令](https://docs.unrealengine.com/4.27/zh-CN/TestingAndOptimization/PerformanceAndProfiling/StatCommands/) 官方文档:[Stat命令](https://docs.unrealengine.com/4.27/zh-CN/TestingAndOptimization/PerformanceAndProfiling/StatCommands/)
参考文章:[UE4 性能 - (一)瓶颈定位](https://zhuanlan.zhihu.com/p/438543980) 参考文章:[UE4 性能 - (一)瓶颈定位](https://zhuanlan.zhihu.com/p/438543980)

View File

@ -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)