逆向工程师的读心术从着色器常量定位抗锯齿与帧生成挂载点【免费下载链接】uncharted_pcUncharted: The Nathan Drake Collection running natively on Windows - by SuperMedo项目地址: https://gitcode.com/gh_mirrors/un/uncharted_pc拿到一份没有源码的 PS4 游戏二进制想给它塞进现代画质技术——时序抗锯齿TAA/FSR/DLSS、帧生成FSR 3.1 FG / DLSS FG——第一道坎不是算法难而是往哪儿插。游戏是自己的渲染循环后处理、色调映射、UI 一层层叠在场景颜色之上插早了HUD 和特效会进历史缓冲插晚了拿不到线性 HDR 场景颜色与深度。本文以uncharted_pc《神秘海域德雷克合集》Windows 原生移植由 SuperMedo 发起为例拆解它如何仅靠逐帧解析游戏着色器绑定的常量缓冲在没有源码的情况下读心出三个关键挂载点相机速度通道、场景颜色之后的第一个计算通道、以及 UI 绘制前的显示缓冲写入通道。源码不可得的困境挂载点为什么难找PS4 游戏的 eboot 是 x86-64 二进制图形指令走 GNM 命令流着色器以二进制 IR 形式被打包。移植项目uncharted_pc的路径是让原版可执行文件直接跑在 Windows 上用 shadPS4 的视频核心与着色器重编译器把 GNM 翻译成 Vulkan详见 ROADMAP.md。这一步只解决了画得出来没解决在哪里动手脚。时序类画质技术的共同输入需求是苛刻的场景颜色必须是线性 HDR、且在游戏后处理bloom、色彩分级之前拿到否则颜色重建无从谈起深度必须是对应当前帧相机的那张场景深度运动向量要表达上一帧到这一帧的像素位移且必须排除 jitter 抖动本身的贡献UI 分离插帧与历史累积都不能把 HUD 算进去否则菜单会拖影、小地图会糊。游戏内显然没有这些语义标记。你能看到的只有一条 draw 命令、一个绑定的常量缓冲、一组目标和一组着色器哈希。所以问题转化为如何在成百上千个 draw/dispatch 中认出这就是场景颜色之后的第一个计算通道逐帧解析常量缓冲签名匹配项目的第一层答案是帧分析器frame analyzer。在 vk_frame_capture.cpp 中BB_CAPTURE_TRIGGERfile触发下一帧的完整记录每个 pass 的绘制次数、颜色/深度目标、采样的纹理、vs/ps/cs 哈希以及对小型全屏 pass 和每个几何 pass 的第一个 draw记录绑定常量缓冲的前 1 KiBBuffer 函数void FrameCapture::Buffer(u64 stage_hash, u32 slot, VAddr address, const void* data, u64 size) { const u64 bytes std::minu64(size, 1024) ~u64(3); ... }这一转储 对三作共用后处理的分析见 upscaler.md得到了三个以着色器哈希为键、常量缓冲为证的挂载点最终固化在 run.py 的默认环境变量里环境变量值语义BB_VELOCITY_PSf4b18264运动模糊的相机速度通道全屏960×540它的常量缓冲直接给出相机BB_UPSCALE_BEFORE_CSaec539f0光照场景颜色之后的第一个计算通道DLSS/FSR/TAA 在它之前运行帧生成在这里取深度与运动BB_HUDLESS_PS484cdbf5写入显示缓冲、UI 之前的第一个 draw其目标即无 UI 的画面这三者构成了读心的核心哈希是钥匙常量是密码本。从常量反推相机逆向数学还原有了目标 pass第二层是读懂常量缓冲本身。对三作共享的运动模糊速度通道其像素着色器 IR 转储暴露了相机重构公式注释直接写在 vk_camera_motion.cppz c9 / (depth - c8), view (z (c0 u c2), z (c1 v c3), z) p M view t (M, t: rows c12..c23) previous clip x, y, w rows c40..c43, c44..c47, c52..c55 of (p, 1)运行时钩子OnVelocityConstants做的就是把这套布局翻译成结构化的相机对象——从常量矩阵算出到上一帧裁剪空间的重投影矩阵to_previous_clip、UV 到视图的映射uv_to_view和深度到 z 的映射depth_to_zOnVelocityConstantscamera.uv_to_view {c[0], c[1], c[2], c[3]}; camera.depth_to_z {c[8], c[9]};而这条 hook 的触发点选得极巧在光栅化器的 draw 提交处按像素着色器哈希命中才读取常量vk_rasterizer.cppif (ps camera_motion-IsVelocityShader(ps-pgm_hash) !ps-buffers.empty() !ps-buffers[0].IsSpecial()) { ... camera_motion-OnVelocityConstants( reinterpret_castconst float*(sharp.base_address), bytes / sizeof(float)); }有了相机camera_motion.compcamera_motion.comp在 mode 2 下重放这套数学用深度重建视图空间位置、用重投影矩阵映射回上一帧 UV得到像素级、上一帧减当前帧的运动向量写入 RG16F 目标。这正是 FSR/DLSS/TAA 与帧生成共享的运动输入。逐帧签名场景常量作为路标另一种情况不需要先识别 pass——游戏每帧都有个 864 字节的场景常量块几乎所有 pass 都会绑定它。它的特征极其稳定[0]3000 (far)、[1]1/3000、[4]1920、[5]1080。OnConstants用这一签名做过滤命中后只取每帧第一个相机frame_has_camera保证提取视图矩阵、逆视图与投影参数OnConstantsif (data[0] ! 3000.0f || data[4] 64.0f || data[5] 64.0f || std::abs(data[1] * data[0] - 1.0f) 1e-3f) { return; } ... std::memcpy(current.view.data(), data 8, 12 * sizeof(float)); std::memcpy(current.inv_view.data(), data 180, 12 * sizeof(float)); current.proj {data[52], data[57], data[62], data[63]};这套签名匹配的思路同样被用在对象运动向量上G-buffer 顶点着色器的常量缓冲按大小分类——864 字节是场景相机块≥640 字节是角色骨骼96~384 且是 48 的倍数是小骨骼武器/道具见 motion_history.h 的ClassifyBuffer。于是哪个 draw 在画角色、哪个在画静态地图无需游戏源码即可判定只有骨骼网格才走昂贵的顶点历史重放静态网格继续用廉价相机向量——这也是从 49 FPS 提到 80 FPS 的路径之一详见 motion_vectors.md。挂载点汇合一次 dispatch 触发整套时序管线最后所有线索在OnDispatch汇合vk_temporal_upscaler.cpp计算着色器哈希等于BB_UPSCALE_BEFORE_CS时先为帧生成抓取深度与运动CaptureFrameGen与升采样器共用同一套速度通道相机再运行升采样器void TemporalUpscaler::OnDispatch(u64 cs_hash) { if (cs_hash ! trigger_hash || done_this_frame) return; if (frame_generator frame_generator-Wanted() camera_motion.VelocityMode() !frame_gen_captured) { frame_gen_captured true; CaptureFrameGen(); } ... Run(); }帧生成端vk_frame_generation.h随后在BB_HUDLESS_PS命中时抓取无 UI 帧FSR 3.1 帧插值把两帧游戏画面之间的中间帧算出来frame_gen_ui.comp再把当前帧的 UI 盖回生成帧上DLSS FG 则把无 UI 帧作为 HUD-less 输入。这套常量 → 相机 → 运动向量 → 挂载的链路构成了 60 FPS 插到 120/240 FPS 的完整底座。方法论可复用的移植工具箱这套读心术不绑定《神秘海域》可以抽象成可复用的步骤建帧分析器记录 pass 的目标、着色器哈希、绑定常量前 1 KiB——BB_CAPTURE_TRIGGER/BB_CAPTURE_DIR即为此设计配合tools/drive.py的shot可脚本化抓帧找签名常量块全屏 pass 的常量缓冲通常含远平面/1-远平面/渲染尺寸这类稳定路标用签名过滤即可锁定每帧相机哈希定位挂载点把场景颜色后的第一个 compute、UI 前第一个 draw等语义锚定到具体的 cs/ps 哈希暴露为环境变量如BB_VELOCITY_PS、BB_UPSCALE_BEFORE_CS、BB_HUDLESS_PS重放数学而非逆向逻辑从着色器 IR 转储里读出矩阵布局在宿主侧用同样的公式重建运动向量而不去逐条模拟游戏代码。这套方法对任何无源码 有渲染管线的移植场景都成立——shadPS4 系的血源移植本仓库的姊妹工程文档同源正是同一批代码、同一套签名字段在服务。反过来也提醒移植者给未来留语义标记环境变量 哈希白名单是把逆向偶然性变成工程确定性的关键。下一次再遇到往哪儿插 TAA的问题答案不是猜而是读——读那一帧的常量缓冲游戏会自己告诉你它把相机藏在了哪里。【免费下载链接】uncharted_pcUncharted: The Nathan Drake Collection running natively on Windows - by SuperMedo项目地址: https://gitcode.com/gh_mirrors/un/uncharted_pc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考