这是一篇调试记录:某主机平台上点光源阴影渲染莫名变得极慢、还伴随显示破损,最后查出根因是 Vertex Shader 的输出结构和 Geometry Shader 的输入结构不匹配,导致 GS 阶段生成了大量无效图元。方法本身和平台无关,记下来当案例。
问题表现
特定区域 GPU 消耗异常升高、帧率明显下降。GPU trace 显示瓶颈在渲染点光源(PointLight)ShadowMap 上——渲染某个角色风格体时单这一项就超过 50ms,而在另一台对照主机平台上同样的内容只花 1ms。两台平台跑同一套引擎和 shader,50 倍差距明显不正常。
0. 为什么需要这份规范
在大多数因 Niagara 导致性能问题的项目中,常见现象并不是"GPU 模拟太慢",而是:
- 渲染线程长时间阻塞在等待 Niagara 系统创建渲染缓冲(
WaitForGatherDynamicMeshElements); - 单帧内大量
CreateRHIBuffer调用; - 关卡里同一资产被复制粘贴几十上百个实例;
- 视野外、远距离的 Ambient 系统没有被正确剔除,每帧仍在渲染线程上分配显存缓冲。
大项目里在 UE 编辑器打开「提交(Check In)」窗口时会卡很久——严重时要几个小时。根因是 UE 默认会扫描全量资产来构造待提交列表,项目资产一多就爆炸。解法是把「扫全量」改成「只向版本控制服务端要 checked-out 文件列表」。
根因:ChoosePackagesToCheckIn 扫了整个工程
默认路径下,弹出提交窗口会去遍历工程和引擎的全部内容目录,逐个查每个 package 的源码管理状态。资产上万后这一步慢到不可用:
// 默认做法:把整个引擎/工程内容目录都塞进去扫描
// Filenames.Add(FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir()));
// Filenames.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()));
// Filenames.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectConfigDir()));
// Filenames.Add(FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()));
有一类"高帧率"不是真把游戏逻辑跑到高帧,而是渲染层插帧:游戏线程仍然稳定在 60 FPS tick,渲染层在两次真实帧之间插一帧出来 present,视觉上得到更高的刷新率。好处是 CPU 开销小(逻辑还是 60 FPS),代价是画质不如真高帧,而且限制多、容易出 bug。这篇记录它的实现原理和踩到的相机插值抖动问题。
原理:游戏 tick 不变,present 层插帧
游戏线程的 tick 频率不变(持续 60 FPS),高帧是"假"渲染出来的。每个 Vsync 周期里,除了正常的主绘制,还额外插一次绘制:
用 PIX on Windows 对 UE 打包游戏做 Timing Capture,结果抓出来空空如也——没有 CommandQueue、没有 Thread、没有 marker 和 EventName。折腾下来根因是 attach 错了进程:抓的是外层 bootstrap 进程,而真正的游戏跑在它拉起的子进程里。
现象
按常规流程配置 PIX、点 Start Timing Capture,capture 里什么都没有——no CommandQueue, no Thread。
根因:UE 打包游戏是「外层 bootstrap + 真实游戏子进程」两层
为了用 RenderDoc 调试和分析 shader,需要在 \Engine\Config\ConsoleVariables.ini 里开启保留 shader 调试信息。但在 UE4.27 上,一旦要编译的 shader 数量很大,编译过程会把内存吃光——极端情况下 128GB 甚至 270GB 内存都会被撑爆而崩溃。根因是一个经典的循环引用导致的内存泄漏,UE5.0.2 有对应 hotfix。
前提
用 RenderDoc 调 shader 必须保留 shader 调试信息,配置在:
\Engine\Config\ConsoleVariables.ini