CD Code Digest Hub /GTA V · v2

渲染管线与渲染阶段:CRenderer、RenderPhases、视口、可见性到绘制列表、PostFX 与渲染目标

109 分钟阅读 15 个章节 3 张图

本页解释 GTA V 每帧如何把“相机 + 场景”变成 GPU 命令:主线程上有哪些渲染阶段(render phase)、它们以什么顺序注册与执行、可见性扫描(fwScan)的结果如何变成每个阶段的实体渲染列表(render list)、渲染列表又如何按 pass/bucket 生成 drawlist 命令,最后 DrawScene 阶段如何串起水、天空、半透明、体积光与 PostFX。drawlist 命令缓冲本身、渲染线程交接见 drawlists-and-render-thread-handoff.md;GBuffer/延迟光照/阴影细节见 deferred-lighting-and-shadows.md;着色器与 technique 见 shaders-and-materials.md;水、镜面、timecycle、植被见 environment-rendering.md。

背景与相关页面#

术语:

术语含义代码实体
渲染阶段(render phase)一个“主线程构建渲染列表 + 生成一段(或多段)drawlist”的单元,例如 GBuffer、级联阴影、水反射fwRenderPhase → CRenderPhase → CRenderPhaseScanned
实体列表索引(entity list index)每个需要可见性扫描的阶段在 gRenderListGroup 中的槽位,也是可见性标志里的 phase bitfwRenderPhase::m_EntityListIndex、fwRenderListGroup::RegisterRenderPhase
渲染 pass(RPASS_*)一个阶段渲染列表里的子列表:可见/LOD/镂空/贴花/淡出/半透明/水/树RenderPassId
渲染 bucket(RB_*)着色器的材质分类(不透明/半透明/贴花/镂空/水…)+ 高 8 位 sub-bucket(模型在哪类阶段可见)CRenderer::RenderBucketTypes、BUCKETMASK_GENERATE*
draw mode / render mode渲染列表构建器的“光照模式”(延迟/前向/阴影…)映射到渲染线程的 eRenderModeCRenderListBuilder::eDrawMode、eRenderMode

相关源码文件#

文件内容
src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.{h,cpp}fwRenderPhase、fwRenderPhaseManager(RENDERPHASEMGR)、fwRenderPhaseScanner
src/dev_ng/rage/framework/src/fwrenderer/renderlistgroup.{h,cpp}fwEntityList、fwRenderListDesc、fwRenderListGroup(gRenderListGroup)、排序任务
src/dev_ng/rage/framework/src/fwrenderer/renderlistbuilder.{h,cpp}fwDrawListAddPass、fwRenderSubphase、fwRenderListBuilder::ExecutePassList/AddToDrawListEntityListCore
src/dev_ng/game/camera/viewports/ViewportManager.cpp、Viewport.hCViewportManager::CreateFinalScreenRenderPhaseList(阶段注册总表)、BuildRenderLists、RenderPhases;CViewport 层次
src/dev_ng/game/renderer/Renderer.{h,cpp}CRenderer:bucket/render mode 枚举、ConstructRenderListNew、PreRender(Entities)、IssueAllScanTasksNew、初始化
src/dev_ng/game/renderer/PostScan.{h,cpp}CPostScan:PVS 处理、预渲染、BuildRenderListsFromPVS
src/dev_ng/game/renderer/renderListGroup.{h,cpp}RenderPassId、RenderPhaseType、CGtaRenderListGroup(排序方式、延迟添加)
src/dev_ng/game/renderer/RenderListBuilder.{h,cpp}CRenderListBuilder:pass 表、draw mode 映射、AddToDrawList*、状态回调
src/dev_ng/game/renderer/RenderPhases/*.cpp全部阶段实现(RenderPhaseDefLight、RenderPhaseStd、RenderPhaseCascadeShadows 等)
src/dev_ng/game/renderer/Entities/*实体 draw handler(CEntityDrawHandler 等)
src/dev_ng/game/renderer/PostProcessFX.{h,cpp}、PostProcessFXHelper.*、AdaptiveDOF.*、LensArtefacts.*、MLAA.*PostFX
src/dev_ng/game/renderer/rendertargets.{h,cpp}、RenderTargetMgr.*CRenderTargets、VideoResManager、命名 RT 管理
src/dev_ng/game/renderer/occlusion.*、OcclusionQueries.*游戏侧遮挡(软件 Hi-Z 封装)与 GPU 盒体查询
src/dev_ng/game/scene/world/GameScan.cppCGameScan:把阶段转为 fwRenderPhaseCullShape 并启动扫描

规模参考:game/renderer 的 .cpp/.h 约 16 万行,其中 RenderPhases 约 2.8 万行(RenderPhaseCascadeShadows.cpp 单文件约 1 万行)、PostProcessFX.cpp 约 9.9 千行、Water.cpp 约 9.5 千行、PlantsMgr.cpp 约 7.9 千行(本次盘点用 PowerShell Measure-Object -Line 统计,只作量级参考)。

1. 类型层次与职责#

图证据:fwRenderPhase/fwRenderPhaseManager 定义 src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.h:68-258;CRenderPhase/CRenderPhaseScanned src/dev_ng/game/renderer/RenderPhases/RenderPhase.h:51-107;派生类声明 src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.h:58、src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.h:141、src/dev_ng/game/renderer/RenderPhases/RenderPhaseCascadeShadows.h:242、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.h:108;fwRenderListGroup/fwRenderListDesc/fwEntityList src/dev_ng/rage/framework/src/fwrenderer/renderlistgroup.h:61-461。

类型职责证据
fwRenderPhase保存 drawlist 类型 m_DrawListType、实体列表索引、grcViewport 副本、可见性掩码/类型、LOD 掩码(LODMASK_HD_INTERIOR…LODMASK_LIGHT)、m_bDisabled/m_bActive/m_BuildDrawListEnabled;虚函数 BuildRenderList/BuildDrawList/UpdateViewport/GetScanner;DrawListPrologue = 计时条 + DLC(dlCmdNewDrawList, type),DrawListEpilogue = EndCurrentDrawList()src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.h:68-206、src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.cpp:171-189
fwRenderPhaseManager(单例宏 RENDERPHASEMGR)有序数组 m_RenderPhases;BuildRenderList/BuildDrawList/UpdateViewports 按注册顺序遍历;RegisterRenderPhase 断言不重复注册src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.cpp:236-294、src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.cpp:520-528、src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.h:266-267
CRenderPhase绑定一个 CViewport,UpdateViewport 复制视口的 grcViewport;带 m_disableOnPausesrc/dev_ng/game/renderer/RenderPhases/RenderPhase.cpp:26-49
CRenderPhaseScanned需要可见性扫描的阶段:持有 fwRenderPhaseScanner(相机位置/方向/远裁剪、render flags、LOD 视口)、门户追踪器;SetCullShape 默认用视锥平面;ConstructRenderList 设置全局相机朝向与远裁剪src/dev_ng/game/renderer/RenderPhases/RenderPhase.cpp:52-112
CViewport / CViewportGame / CViewportPrimaryOrtho / CViewportFrontend3DScene持有 grcViewport、camFrame、优先级、所在内部房间;CGame::ViewportSystemInit(INIT_CORE) 依次添加正交、游戏、前端 3D 三个视口src/dev_ng/game/camera/viewports/Viewport.h:66-228、src/dev_ng/game/Core/game.cpp:3614-3627
CRenderer全静态:bucket/render mode 枚举与 bucket mask 辅助、相机与预流式距离、“哪些阶段可渲染 alpha/水/镂空/贴花”的位掩码src/dev_ng/game/renderer/Renderer.h:86-399

RENDERPHASEMGR 在 CRenderer::Init(INIT_CORE) 中以容量 40 实例化;同一函数还初始化 DeferredLighting、CTiledLighting、级联阴影、CParaboloidShadow、SSAO、CGtaRenderListGroup、CGameScan,并把默认 sub-bucket 设为 RB_MODEL_DEFAULT src/dev_ng/game/renderer/Renderer.cpp:515-589。

2. 阶段注册:CViewportManager::CreateFinalScreenRenderPhaseList#

阶段不在 CRenderer 里写死,而是由视口管理器注册;gameSkeleton 把 CViewportManagerWrapper::CreateFinalScreenRenderPhaseList 注册为 update 调用 src/dev_ng/game/Core/game.cpp:2950。函数只在 g_SceneToGBufferPhaseNew == NULL 时创建整套阶段,最后调用 RENDERPHASEMGR.UpdateViewports() src/dev_ng/game/camera/viewports/ViewportManager.cpp:550-953。注册顺序即执行顺序(fwRenderPhaseManager 只顺序遍历数组)。

#阶段类drawlist 类型视口条件/说明
1CRenderPhasePreRenderViewportDL_RENDERPHASE_PRE_RENDER_VPgamePC 上处理暂停解析、DOF RT 重置、LOD 灯帧信息清理等前置工作 src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:236-262
2CRenderPhaseEntitySelectDL_RENDERPHASE_DEBUGgameENTITYSELECT_ENABLED_BUILD(调试拾取)
3CRenderPhaseTreeImpostersDL_RENDERPHASE_TREEgameUSE_TREE_IMPOSTERS
4,5CRenderPhaseScript2d ×2DL_RENDERPHASE_SCRIPT无分别登记为 CRenderTargetMgr::RTI_Scripted 与 RTI_MovieMesh
6CRenderPhaseCloudGenerationDL_RENDERPHASE_CLOUD_GENgame只执行 CSky::PreRender src/dev_ng/game/renderer/RenderPhases/RenderPhaseWater.cpp:79-84
7CRenderPhaseReflectionDL_RENDERPHASE_REFLECTION_MAPgame创建反射 RT(§8);立方体 6 面 → 抛物面 + mip 模糊 src/dev_ng/game/renderer/RenderPhases/RenderPhaseReflection.cpp:774-829
8CRenderPhasePedDamageUpdateDL_RENDERPHASE_PED_DAMAGE_GENgame注释“after Reflection so we can reuse the memory”;调用 CPedDamageManager::AddToDrawList src/dev_ng/game/renderer/RenderPhases/RenderPhasePedDamage.cpp:45-54
9CRenderPhaseWaterSurfaceDL_RENDERPHASE_WATER_SURFACEgame水面 GPU 动态模拟与湿润图更新 src/dev_ng/game/renderer/RenderPhases/RenderPhaseWater.cpp:50-66
10CRenderPhaseDeferredLighting_SceneToGBufferDL_RENDERPHASE_GBUFgame主 GBuffer 阶段(全局 g_SceneToGBufferPhaseNew)
11CRenderPhaseDeferredLighting_SSAOSPUDL_RENDERPHASE_LIGHTINGgame仅 SPUPMMGR_PS3
12CRenderPhaseParaboloidShadow ×MAX_ACTIVE_PARABOLOID_SHADOWSDL_RENDERPHASE_PARABOLOID_SHADOWSgame局部灯阴影,数量 = 4 高分辨率 + 4 低分辨率 = 8 src/dev_ng/game/renderer/Shadows/ParaboloidShadows_shared.h:24-38
13CRenderPhaseCascadeShadowsDL_RENDERPHASE_CASCADE_SHADOWSgame方向光级联阴影(g_CascadeShadowsNew)
14CRenderPhaseHeight("raincollision")DL_RENDERPHASE_RAIN_COLLISION_MAPgame128² D32FS8 高度图,同时登记为 GPU 粒子碰撞图
15CRenderPhaseRainUpdateDL_RENDERPHASE_RAIN_UPDATEgame非 PS3 在水反射之后注册、PS3 在其之前;只在外景于 GBuffer 中可见或相机在水下时更新 GPU 天气粒子 src/dev_ng/game/renderer/RenderPhases/RenderPhaseWater.cpp:97-115
16CRenderPhaseWaterReflectionDL_RENDERPHASE_WATER_REFLECTIONgame创建 WATER_REFLECTION_* RT(R11G11B10F,5 mip;PC 可选 MSAA)
17CRenderPhasePreRenderViewport(第二个)DL_RENDERPHASE_PRE_RENDER_VPgame源码注释 “TODO: Really game viewport?”
18CRenderPhaseDeferredLighting_LightsToScreenDL_RENDERPHASE_LIGHTINGgame延迟光照合成,SetGBufferRenderPhase(g_SceneToGBufferPhaseNew)
19CRenderPhaseMirrorReflectionDL_RENDERPHASE_MIRROR_REFLECTIONgame平面镜/镜面
20RenderPhaseSeeThroughDL_RENDERPHASE_SEETHROUGH_MAPgame热成像“透视”层:800×450 R11G11B10F 颜色/模糊 + 深度
21CRenderPhaseDrawSceneDL_RENDERPHASE_DRAWSCENEgame天空/水/半透明/特效/PostFX;复用 GBuffer 阶段的实体列表索引
22CRenderPhaseDebugOverlayDL_RENDERPHASE_DEBUG_OVERLAYgame__BANK,同样复用 GBuffer 列表
23CRenderPhaseDebug3dDL_RENDERPHASE_DEBUGgameDEBUG_DISPLAY_BAR
24CRenderPhaseHudDL_RENDERPHASE_HUDorthoHUD
25,26CRenderPhasePhoneScreen(RTI_PhoneScreen)、CRenderPhasePhoneModelDL_RENDERPHASE_SCRIPT / DL_RENDERPHASE_PHONEortho / game手机屏幕 RT 与手机模型
27CRenderPhaseFrontEndDL_RENDERPHASE_FRONTENDortho注释“leveraging this empty render phase”
28CRenderPhaseDebug2dDL_RENDERPHASE_DEBUGorthoDEBUG_DISPLAY_BAR
29CRenderPhaseTimeBarsDL_RENDERPHASE_TIMEBARSortho计时条
30CRenderPhaseLensDistortionDL_RENDERPHASE_LENSDISTORTIONortho镜头畸变
31CRenderPhaseScreenShotDL_RENDERPHASE_DEBUGortho!__FINAL

表证据:注册序列 src/dev_ng/game/camera/viewports/ViewportManager.cpp:576-942;各阶段构造函数中的 drawlist 类型见 src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:230、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1228、src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:366、src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:1346、src/dev_ng/game/renderer/RenderPhases/RenderPhaseCascadeShadows.cpp:11050、src/dev_ng/game/renderer/RenderPhases/RenderPhaseParaboloidShadows.cpp:70、src/dev_ng/game/renderer/RenderPhases/RenderPhaseReflection.cpp:263、src/dev_ng/game/renderer/RenderPhases/RenderPhaseWaterReflection.cpp:289、src/dev_ng/game/renderer/RenderPhases/RenderPhaseMirrorReflection.cpp:136、src/dev_ng/game/renderer/RenderPhases/RenderPhaseHeightMap.cpp:86、src/dev_ng/game/renderer/RenderPhases/RenderPhaseFX.cpp:354、src/dev_ng/game/renderer/RenderPhases/RenderPhaseWater.cpp:46、src/dev_ng/game/renderer/RenderPhases/RenderPhaseTreeImposters.cpp:161、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStdNY.cpp:63、src/dev_ng/game/renderer/RenderPhases/RenderPhaseDebugNY.cpp:260、src/dev_ng/game/renderer/RenderPhases/RenderPhaseDebugOverlay.cpp:3924、src/dev_ng/game/renderer/RenderPhases/RenderPhasePedDamage.cpp:40;常量 HGHT_TARGET_SIZE = 128、SEETHROUGH_LAYER_WIDTH/HEIGHT = 800/450 src/dev_ng/game/renderer/RenderPhases/RenderPhaseHeightMap.h:20、src/dev_ng/game/renderer/RenderPhases/RenderPhaseFX.h:21-22。

要点:

  • 执行顺序不等于“消费者在后”:阴影阶段(12、13)注册在 GBuffer(10)之后,但阴影图由 LightsToScreen(18)消费,所以没问题;反射阶段(7)旁注释写着“moving after shadow phase to add shadow to reflection map”,但代码实际把它注册在阴影之前 src/dev_ng/game/camera/viewports/ViewportManager.cpp:711-713。注释与代码不一致;这表明反射图要么不使用本帧阴影,要么使用上一帧的阴影结果(推断:未追踪反射着色器读取的阴影图来源)。
  • 重建:PC 设备重置时 CViewportManager::DeviceReset 先 ShutdownRenderPhases()(删除全部阶段、清空 gRenderListGroup 注册),再重建 grcViewport 类与整个阶段表 src/dev_ng/game/camera/viewports/ViewportManager.cpp:205-231、src/dev_ng/game/camera/viewports/ViewportManager.cpp:247-279。
  • phase bit 分配:扫描型阶段调用 gRenderListGroup.RegisterRenderPhase(type, this) 取得实体列表索引——GBuffer 与透视层属于 RPTYPE_MAIN,级联/抛物面阴影属于 RPTYPE_SHADOW,反射/水反射/镜面属于 RPTYPE_REFLECTION,高度图属于 RPTYPE_HEIGHTMAP,实体选择属于 RPTYPE_DEBUG。注册点:src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:414、src/dev_ng/game/renderer/RenderPhases/RenderPhaseCascadeShadows.cpp:11061、src/dev_ng/game/renderer/RenderPhases/RenderPhaseParaboloidShadows.cpp:80、src/dev_ng/game/renderer/RenderPhases/RenderPhaseReflection.cpp:275、src/dev_ng/game/renderer/RenderPhases/RenderPhaseWaterReflection.cpp:300、src/dev_ng/game/renderer/RenderPhases/RenderPhaseMirrorReflection.cpp:147、src/dev_ng/game/renderer/RenderPhases/RenderPhaseHeightMap.cpp:104、src/dev_ng/game/renderer/RenderPhases/RenderPhaseFX.cpp:374、src/dev_ng/game/renderer/RenderPhases/RenderPhaseEntitySelect.cpp:66。

2.1 phase bit 的预算#

可见性标志里每个阶段占 1 bit,上限 24(MAX_NUM_RENDER_PHASES = 24,注释提醒“we need to store 1 bit per phase for lots of vis stuff”)src/dev_ng/game/renderer/renderListGroup.h:13-15。CGtaRenderListGroup::Init 按类型切分这 24 bit:

RenderPhaseType预留 bit 数源码注释
RPTYPE_SHADOW91 级联 + 8 局部阴影
RPTYPE_REFLECTION31 抛物面 + 1 水 + 1 镜面
RPTYPE_MAIN3GBuf 等
RPTYPE_STREAMING51 HD 球 + 1 LOD 球 + 1 内部球 + 2 流式体积
RPTYPE_HEIGHTMAP1
RPTYPE_DEBUG1

证据:src/dev_ng/game/renderer/renderListGroup.cpp:37-46。SetRenderPhaseTypeInfo 依次分配起始 bit 并断言总数 ≤ 24;RegisterRenderPhase 返回 startBit + 已用数 并断言不超过该类型上限 src/dev_ng/rage/framework/src/fwrenderer/renderlistgroup.cpp:460-478、src/dev_ng/rage/framework/src/fwrenderer/renderlistgroup.cpp:565-578。合计 9+3+3+5+1+1 = 22 bit。RPTYPE_STREAMING 只用于场景流式 cull shape,没有真实阶段对象(BuildRenderListsFromPVS 注释“the streaming cull shapes aren't backed by a real render phase”)src/dev_ng/game/renderer/PostScan.cpp:1366-1379。

3. 每帧时序:从 UpdateViewports 到 BuildDrawList#

图证据:

  • CGameScan::KickVisibilityJobs 注册为 update 调用并执行 PreScan → SetupScan → StartScan src/dev_ng/game/Core/game.cpp:3006、src/dev_ng/game/scene/world/GameScan.cpp:614-633。
  • Visibility 段:ClearRenderLists → (视早启动标志) PreScan → gVpMan.BuildRenderLists() → Water::BlockTillUpdateComplete → CRenderer::PreRender → COcclusion::BeforeRender src/dev_ng/game/Core/game.cpp:3436-3521。
  • CViewportManager::BuildRenderLists:SetCanRenderFlags(0…) → RENDERPHASEMGR.BuildRenderList() → gWorldMgr.PostScan() → CRenderer::IssueAllScanTasksNew() → gPostScan.BuildRenderListsNew() src/dev_ng/game/camera/viewports/ViewportManager.cpp:1004-1082。
  • IssueAllScanTasksNew 计算 GBuf/镜面/阴影/调试阶段位掩码后调用 CGameScan::PerformScan src/dev_ng/game/renderer/Renderer.cpp:1593-1634;PerformScan = ProcessPvsWhileScanning + fwScan::JoinScan src/dev_ng/game/scene/world/GameScan.cpp:593-612。
  • BuildRenderListsNew = LOD 灯桶可见性 → ProcessPVSForRenderPhase → CRenderer::PreRenderEntities src/dev_ng/game/renderer/PostScan.cpp:208-226;后者插入依赖任务 src/dev_ng/game/renderer/PostScan.cpp:708-709;依赖体 src/dev_ng/game/renderer/PostScan.cpp:2035-2085。
  • CGame::Render:等待 PostScan 辅助任务与排序,再 AddDrawList_BeginRender → gVpMan.RenderPhases() → AddDrawList_EndRender src/dev_ng/game/Core/game.cpp:3538-3607;RenderPhases() 先发一个只含 dlCmdRenderPhasesDrawInit 的 DL_RENDERPHASE_PREAMBLE,再 RENDERPHASEMGR.BuildDrawList() src/dev_ng/game/camera/viewports/ViewportManager.cpp:1212-1226。RenderPhasesDrawInit 在渲染线程设置默认状态块、方向/环境光全局、全局 alpha、MSAA 参数,PC 立体 3D 时更新立体纹理 src/dev_ng/game/camera/viewports/ViewportManager.cpp:1100-1203。

关键约束:

  1. UpdateViewports 先把所有阶段 SetActive(false),阶段在自己的 UpdateViewport() 中未早退时再 SetActive(true)(如 GBuffer src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:481、DrawScene src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1278)src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.cpp:279-294。只有 active 的扫描型阶段会产生 cull shape(§4)。
  2. __ASSERT 一致性检查:BuildRenderList 之后断言“调用过 InitRenderListBuild 的阶段必须 active,反之亦然” src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.cpp:249-259。
  3. BuildDrawList 跳过条件:IsDisabled() 或 !IsBuildDrawListEnabled() src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.cpp:263-277。
  4. g_render_lock(bank 调试冻结):打开时 BuildRenderLists 暂停游戏与流式并直接返回,ClearRenderLists、ConstructRenderListNew、IssueAllScanTasksNew 都早退 src/dev_ng/game/camera/viewports/ViewportManager.cpp:1008-1020、src/dev_ng/game/renderer/Renderer.cpp:852-853。

4. 可见性结果如何进入每个阶段#

4.1 阶段 → cull shape#

CGameScan::SetupScan 遍历 RENDERPHASEMGR,对每个 IsScanRenderPhase() && IsActive() 且 GetCullFlags() 非零的阶段构造 fwRenderPhaseCullShape:设置 LOD 掩码、起始场景节点(门户/内部)、SetRenderPhaseId(GetEntityListIndex())、可见性掩码/类型、flags,调用阶段的 SetCullShape 补充几何(默认视锥)后 fwScan::AddCullShape() src/dev_ng/game/scene/world/GameScan.cpp:181-215、src/dev_ng/game/scene/world/GameScan.cpp:330-352。timecycle 的 “render exterior” 值接近 0 时去掉 CULL_SHAPE_FLAG_RENDER_EXTERIOR src/dev_ng/game/scene/world/GameScan.cpp:201-210。GBuffer 阶段的 cull flags 是“主视口 + 内外景 + 穿越门户 + 裁剪存储门户 + 屏幕四边形对 + 启用遮挡 + 流式实体” src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:587-597。扫描本身(多 cull shape 并行遍历场景图、产生 PVS 条目)见 ../04-rage-framework/scene-graph-and-visibility.md。

4.2 阶段 BuildRenderList:只做“准备”#

扫描型阶段的 BuildRenderList() 并不逐个添加实体,而是调用 CScene::SetupRender → CRenderer::ConstructRenderListNew。该函数把阶段的 render flags(RENDER_SETTING_RENDER_ALPHA_POLYS/WATER/CUTOUT_POLYS/DECAL_POLYS/ALLOW_PRERENDER)转换为“该 phase bit 允许哪些 pass”的全局位掩码,按 drawlist 类型把 bit 加入阴影/GBuf/镜面/调试集合,然后 InitRenderListBuild() 并调用阶段的 ConstructRenderList() src/dev_ng/game/renderer/Renderer.cpp:847-915、src/dev_ng/game/scene/scene.cpp:984-990。GBuffer 阶段在此之外还执行 COcclusion::BeforeSetupRender、Water::PreRender、COcclusion::AfterPreRender src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:521-560,并在 ConstructRenderList 中根据相机沿视线方向的速度计算预流式距离 src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:562-584。

4.3 边扫描边预渲染:ProcessPvsWhileScanning#

扫描任务以“结果块”形式陆续产出 PVS。主线程循环读取 m_ResultBlockCount,对已完成的块调用 ProcessResultBlock(对 GBuf/镜面可见的实体启动 PreRender);块不够且 g_scanTasksPending > 0 时等待(PC 用 WaitForSingleObject(g_scanTasksReady, 1),主机 sysIpcSleep(0))。为避免竞态,看到 g_scanTasksPending 归零后会再读一次计数(带读屏障)才退出 src/dev_ng/game/renderer/PostScan.cpp:528-586。PVS 越界是致命断言(注释:扫描任务不检查越界写)src/dev_ng/game/renderer/PostScan.cpp:589-590。随后立刻启动 PVS 排序依赖 StartSortPVSJob,收集“总是预渲染”实体与“动画需更新但不可见”的 ped,执行不可见实体预渲染 src/dev_ng/game/renderer/PostScan.cpp:596-641。

单个实体的预渲染分流:没有 drawable 或有附着父物体的放入 PreRenderList 延后;否则立即 PreRender(),若返回值不是 PRERENDER_DONE 再进入 PreRender2List src/dev_ng/game/renderer/PostScan.cpp:232-260。PreRender2 在 CRenderer::PreRenderEntities 中统一执行,后者还串起车辆警灯/手电优先级、植物异步拷贝、车辆灯异步任务、CGameWorld::ProcessAfterPreRender、角色布料、timecycle 异步查询收尾、CVisualEffects::UpdateAfterPreRender、抛物面阴影更新与灯光可见性排序 src/dev_ng/game/renderer/Renderer.cpp:953-1045。

4.4 ProcessPVSForRenderPhase 与 BuildRenderListsFromPVS#

ProcessPVSForRenderPhase:场景流式 g_SceneStreamerMgr.Process() → FinishPreRender → ProcessPostVisibility(alpha 等)→ 插入 BuildRenderListsFromPVS 依赖任务 → 主线程把可见 CLightEntity 转为光源(ProcessVisibleLight),对“晚到”的灯强制 GBuf + 镜面可见 → LOD 灯/远处灯可见性 src/dev_ng/game/renderer/PostScan.cpp:657-749。

工作线程上的 BuildRenderListsFromPVS 以 SYS_THREAD_POSTSCAN 身份运行,并临时把 gRenderListGroup 的线程访问掩码切到该线程 src/dev_ng/game/renderer/PostScan.cpp:2035-2041。对每个 PVS 条目、每个 phase bit 置位的阶段,按下列决策把实体加入该阶段的 fwRenderListDesc:

判定(按顺序)结果 pass说明
阶段类型特定的 custom flags—GBuf:INTERIOR_SORT(实体与相机同处内/外)与 USE_EXTERIOR_SCREEN_QUAD;抛物面反射:内部排序 + LOD 排序值(SLOD3 用反向距离);镜面/水反射:alpha pass 也带 custom flags src/dev_ng/game/renderer/PostScan.cpp:1533-1595
alpha < ALPHA_CULL_THRESHOLD 且不是抛物面反射/强制级联阴影跳过水反射可由 ShouldEntityForceUpAlpha 强制满 alpha src/dev_ng/game/renderer/PostScan.cpp:1617-1630
GetLodData().IsTree()RPASS_TREE树只进树 pass
alpha < LODTYPES_ALPHA_VISIBLE(非抛物面反射、非强制级联)RPASS_FADING淡入淡出
HAS_OPAQUE 且有 LOD 子节点(非抛物面反射)RPASS_LOD
HAS_OPAQUE,非阴影阶段且 FORCE_ALPHARPASS_FADING
HAS_OPAQUE 其他RPASS_VISIBLE
HAS_ALPHA 且该 bit 允许 alphaRPASS_ALPHAped 额外带 DRAW_FIRST_SORTED;超出 VLT_LOD2 距离的陆地车辆不进 alpha
HAS_DISPL_ALPHA / HAS_WATER / HAS_DECAL / HAS_CUTOUTRPASS_ALPHA / RPASS_WATER / RPASS_DECAL / RPASS_CUTOUT受对应 can-render 位掩码约束

表证据:src/dev_ng/game/renderer/PostScan.cpp:1309-1721,决策主体在 src/dev_ng/game/renderer/PostScan.cpp:1617-1716。由此可见 pass 由实体 base flags(来自 archetype 着色器 bucket 组成)+ 当前 alpha + 阶段能力共同决定;同一实体可同时进入多个 pass(例如既有不透明又有贴花)。

之后对所有阶段的列表 PostRenderListBuild() 并启动排序任务 StartSortEntityListJob:宏名仍叫 SORT_RENDERLISTS_ON_SPU 且恒为 1,在非 PS3 平台用 sysDependency,按 NUM_RENDER_LIST_SORT_JOBS = 4 切分 src/dev_ng/game/renderer/PostScan.cpp:2047-2077、src/dev_ng/rage/framework/src/fwrenderer/renderlistgroup.h:18-20、src/dev_ng/rage/framework/src/fwrenderer/renderlistgroup.cpp:490-529。CGame::Render 在构建 drawlist 前用 WaitForPostScanHelper + WaitForSortEntityListJob 自旋等待(sysIpcYield)src/dev_ng/game/renderer/PostScan.cpp:1865-1873、src/dev_ng/rage/framework/src/fwrenderer/renderlistgroup.cpp:531-550。排序进行中访问列表会触发 FastAssert(!IsSortInProgress()) src/dev_ng/rage/framework/src/fwrenderer/renderlistgroup.h:440。排序后仍要加实体时用 CGtaRenderListGroup::DeferredAddEntity:最多 MAX_PARABOLOID_SHADOW_RENDERPHASES + 4 个列表、每列表 600 个实体;ProcessDeferredAdditions(在安全区调用)先等待排序完成,再添加并对该 pass 重排 src/dev_ng/game/renderer/renderListGroup.cpp:16-140。

4.5 排序规则#

pass排序证据
RPASS_VISIBLE升序,ped 偏置(B*644704:车内 ped 在车之后绘制)src/dev_ng/game/renderer/renderListGroup.cpp:48
RPASS_LOD、RPASS_DECAL、RPASS_TREE升序(近到远)src/dev_ng/game/renderer/renderListGroup.cpp:49-55
RPASS_CUTOUT、RPASS_FADING、RPASS_ALPHA、RPASS_WATER降序(远到近)同上

元素 fwEntityList::CElement 为 {fwEntity*, u32 baseFlags, float sortVal, u16 customFlags, u16 renderFlags}(大端平台字段顺序不同);“简单”排序把 sortVal 与 custom flags 中 LOD 排序位、外景四边形位拼成 64 位键比较(CUSTOM_FLAGS_SORT_MASK_LL),DRAW_LAST/DRAW_FIRST_SORTED 以 ±10000(PUSH_TO_BACK_DISTANCE)偏移 src/dev_ng/rage/framework/src/fwrenderer/renderlistgroup.h:59-228。每个列表维护 base/render flags 的并集与交集,生成 drawlist 时可整表早退 src/dev_ng/rage/framework/src/fwrenderer/renderlistgroup.h:251-290、src/dev_ng/rage/framework/src/fwrenderer/renderlistbuilder.cpp:289-294。

5. 渲染列表 → drawlist:pass 表、bucket 与 draw mode#

5.1 bucket 与 sub-bucket#

CRenderer::RenderBucketTypes 的低 8 个是材质 bucket:RB_OPAQUE=0, RB_ALPHA=1, RB_DECAL=2, RB_CUTOUT=3, RB_NOSPLASH=4, RB_NOWATER=5, RB_WATER=6, RB_DISPL_ALPHA=7;8 以上是“模型 sub-bucket”,表示几何在何种阶段出现:RB_MODEL_DEFAULT=8(gbuf/drawscene)、RB_MODEL_SHADOW=9、RB_MODEL_REFLECTION=10(抛物面反射)、RB_MODEL_MIRROR=11、RB_MODEL_WATER=12 src/dev_ng/game/renderer/Renderer.h:186-219。bucket mask 打包为 (subBucketMask << 8) | (1 << baseBucket),BUCKETMASK_MATCH 要求着色器与传入 mask 的高、低字节都有交集 src/dev_ng/rage/base/src/grcore/effect.h:95-106、src/dev_ng/rage/framework/src/fwdrawlist/drawlistmgr.h:416。

bucket 状态通过命令传递:dlCmdSetBucketsAndRenderMode 在构造时(主线程)写入 update 侧状态,执行时(渲染线程)写入 m_rtBucket/m_rtRenderMode/m_rtSubBucketMask;未指定 sub-bucket 时用默认值(游戏设为 RB_MODEL_DEFAULT)src/dev_ng/rage/framework/src/fwdrawlist/drawlist.cpp:3853-3889、src/dev_ng/game/renderer/Renderer.cpp:579。阴影阶段改用 RB_MODEL_SHADOW sub-bucket src/dev_ng/game/renderer/RenderListBuilder.cpp:1303。实体绘制时读取 GetRtBucketMask() 交给 rmcDrawable::Draw(例如 src/dev_ng/game/renderer/DrawLists/DrawableDataStructs.cpp:431-459)——同一 drawable 在不同阶段画哪些几何由 bucket mask 选择。

5.2 draw mode → render mode#

CRenderListBuilder::InitDLCCommands 注册 draw mode 到三种映射(正常 / 无淡出 / 前向):

draw modeRM_NORMALRM_NO_FADERM_NOT_DEFERRED
USE_FORWARD_LIGHTINGrmStandardrmStandardrmStandard
USE_SIMPLE_LIGHTINGrmSimplermSimpleNoFadermSimpleNoFade
USE_DEFERRED_LIGHTINGrmGBufferrmGBufferrmStandard
USE_SIMPLE_LIGHTING_DISTANCE_FADErmSimpleDistFade同同
USE_NO_LIGHTINGrmNoLights同同
USE_SHADOWS / USE_SKINNED_SHADOWSrmShadows / rmShadowsSkinned同同
USE_SHADOWS_SPECIAL / USE_SKINNED_SHADOWS_SPECIAL(曲面细分)rmSpecial / rmSpecialSkinned同同

证据:src/dev_ng/game/renderer/RenderListBuilder.cpp:640-657;eRenderMode 位值 src/dev_ng/game/renderer/Renderer.h:86-103;PS3 无 rmShadows(HAS_RENDER_MODE_SHADOWS = !__PS3)src/dev_ng/game/renderer/Renderer.h:84。因此延迟模式下 alpha 表的行用 RM_NOT_DEFERRED → rmStandard:半透明物体在 DrawScene 中前向着色。

5.3 pass 表(fwDrawListAddPass)#

每行 {pass, bucket, drawModeMask, filterMask, filterExcludeMask, renderModeMapping, generalFlags};ExecutePassList 要求 draw mode 落在 drawModeMask 内、filter 含全部 filterMask 位且不含 filterExcludeMask 位,满足时以映射后的 render mode 调用回调 src/dev_ng/rage/framework/src/fwrenderer/renderlistbuilder.h:79-113、src/dev_ng/rage/framework/src/fwrenderer/renderlistbuilder.cpp:157-204。游戏层定义的主要表 src/dev_ng/game/renderer/RenderListBuilder.cpp:133-248:

表内容(行序即绘制顺序)
s_OpaquePassVISIBLE/OPAQUE → TREE/OPAQUE → TREE/CUTOUT(强制 clip)→ LOD/OPAQUE → CUTOUT/CUTOUT → FADING/OPAQUE、FADING/CUTOUT(无淡出模式)
s_OpaquePassSSA / s_OpaquePass_SinglePassSSA同上,但镂空使用子采样 alpha(SSA);多 pass 版本首行带 SSA_QUERY_BEGIN、末行带 SSA_QUERY_END,用于条件渲染
s_DecalPassDECAL/DECAL、FADING/DECAL(仅延迟)
s_AlphaPassFADING/ALPHA(仅淡出)、FADING/OPAQUE(RENDER_FADE_PROPS)、ALPHA/ALPHA,均 RM_NOT_DEFERRED
s_MirrorAlphaPass镜面阶段的半透明,额外允许前向的淡出不透明与贴花
s_DisplAlphaPass / s_WaterPass / s_ForcedAlphaPass位移 alpha / 水 bucket / 仅强制 alpha(水折射用)

CRenderListBuilder::AddToDrawList(list, filter, drawMode, subphase, …) 是通用入口:先 Lights::AddToDrawList()(调试模式除外),再按 filter 依次执行:不透明表 → (延迟模式)植物贴花 dlCmdPlantMgrRenderDecal → 贴花表 → 远处车辆 dlCmdDistantCarsRender → 植物 dlCmdPlantMgrRender → 镜面 alpha 或 alpha 表 → 位移 alpha → 水 src/dev_ng/game/renderer/RenderListBuilder.cpp:800-900。

5.4 每个实体如何进入 drawlist#

fwRenderListBuilder::AddToDrawListEntityListCore:先用列表 flags 并/交集整表早退;逐实体检查 base/render flags(RAGE_INSTANCED_TECH 时检查级联/反射面的实例位);第一个通过的实体前调用 renderStateSetup 回调;useAlpha 时把实体 alpha 写入 fwDrawDataAddParams::AlphaFade,低于阈值跳过;custom flags 变化时调用 customFlagsChangedCallback;最终 pEntity->GetDrawHandlerPtr()->AddToDrawList(pEntity, drawParams);循环结束调用 renderStateCleanup src/dev_ng/rage/framework/src/fwrenderer/renderlistbuilder.cpp:262-492。延迟模式的状态回调 RenderStateSetupDeferred 根据 pass/bucket 选择 DeferredLighting::PrepareFadePass/PrepareSSAPass/PrepareCutoutPass/PrepareDecalPass/PrepareTreePass/PrepareDefaultPass,再发 dlCmdSetBucketsAndRenderMode(bucket, rmStandard) src/dev_ng/game/renderer/RenderListBuilder.cpp:2335-2395;清理回调先 flush 原型批与实例批 src/dev_ng/game/renderer/RenderListBuilder.cpp:2397-2400。

5.5 实体 draw handler(renderer/Entities)#

每个 CEntity 拥有一个 fwDrawData 派生的 draw handler,按实体类型选择:

类用途证据
CEntityDrawHandler(抽象)共同基类:自定义着色效果实例创建、BeforeAddToDrawList/AfterAddToDrawList(设置灯光与 in-interior 全局)、AddBasic/Fragment/BendableToDrawList 静态实现src/dev_ng/game/renderer/Entities/EntityDrawHandler.h:189-219
CNoDrawingDrawHandler、CEntityBasicDrawHandler、CEntityFragDrawHandler、CEntityBendableDrawHandler、CEntityInstanced*DrawHandler不绘制 / 普通 drawable / fragment / 可弯曲植物 / 实例化几何src/dev_ng/game/renderer/Entities/EntityDrawHandler.h:221-267
CDynamicEntityDrawHandler、CDynamicEntityFragmentDrawHandler、CDynamicEntitySkinnedDrawHandler动态实体(碎片/蒙皮)src/dev_ng/game/renderer/Entities/DynamicEntityDrawHandler.h:6-27
CPedDrawHandler、CVehicleDrawHandler、CObjectDrawHandler/CObjectFragmentDrawHandler、CCutSceneVehicleDrawHandlerped、车辆、物体、过场车辆src/dev_ng/game/renderer/Entities/PedDrawHandler.h:11、src/dev_ng/game/renderer/Entities/VehicleDrawHandler.h:11、src/dev_ng/game/renderer/Entities/ObjectDrawHandler.h:7-18、src/dev_ng/game/renderer/Entities/CutSceneDrawHandler.h:7
CEntityBatchDrawHandler、CGrassBatchDrawHandler实例化批(EntityBatch/草批)src/dev_ng/game/renderer/Entities/EntityBatchDrawHandler.h:17-36
CPtFxDrawHandler、CVehicleGlassDrawHandler粒子排序实体、车窗玻璃src/dev_ng/game/renderer/Entities/PtFxDrawHandler.h:6、src/dev_ng/game/renderer/Entities/VehicleGlassDrawHandler.h:6

以 AddBasicToDrawList 为例(静态无骨骼 drawable):有骨骼直接返回;若启用原型批处理(g_prototype_batch)且不是前向灯光 pass,则按当前构建的 drawlist 选择“原型”——反射图用 g_BasicEntityReflectionPrototype,DrawScene/镜面用 g_BasicEntityInteriorPrototype,rmStandard 下的树(CSE_TREE)用 g_TreePrototype,无自定义着色效果的普通实体用 g_BasicEntityPrototype;原型把每个实体的少量数据追加到命令缓冲中的一页,攒满或切换原型时以单条 CDrawPrototypeBatchDC 提交;没有原型时退回 BeforeAddToDrawList + CDrawEntityDC/CDrawEntityFmDC(满矩阵变换)src/dev_ng/game/renderer/Entities/EntityDrawHandler.cpp:836-960、src/dev_ng/game/renderer/Entities/EntityDrawHandler.h:20-163、src/dev_ng/game/renderer/Entities/EntityDrawHandler.cpp:691-728。命令与数据结构细节见 drawlists-and-render-thread-handoff.md;ped/车辆变体渲染见 ../06-peds/ped-motion-and-rendering.md 与 ../08-vehicles/vehicle-architecture.md。

6. 关键阶段的 drawlist 内容#

6.1 GBuffer:CRenderPhaseDeferredLighting_SceneToGBuffer::BuildDrawList#

非拆分版本(PC 及旧主机,见 §9):DrawListPrologue → GBuffer::SetRenderingTo(true) → SSA/MSAA 清除 → 方向光全局、GBuffer 锁定 → dlCmdSetCurrentViewport → 屏幕尺寸、昼夜平衡、动态烘焙增强与湿润 → DeferredLighting::PrepareGeometryPass → 清深度/模板(DEFERRED_MATERIAL_CLEAR)→ 可选 COcclusion::PrimeZBuffer(用遮挡体预填 Z)→ Water::ProcessDepthOccluderPass → CRenderListBuilder::AddToDrawList(idx, RENDER_OPAQUE|RENDER_FADING, USE_DEFERRED_LIGHTING) → Lights::SetupRenderThreadLights → 解锁并 FinishGeometryPass → CTiledLighting::DepthDownsample → MSAA 边缘标记 → GBuffer::SetRenderingTo(false) → DrawListEpilogue src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:721-941。拆分版本(主机多渲染线程)把不透明实体按 g_SceneToGBuffer_SubPassCount = 16 份切分到 DL_RENDERPHASE_GBUF_000…113 多个 drawlist,达到批量阈值就换下一个,最后一个 drawlist 绘制贴花与植物 src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:943-1060。

6.2 光照:CRenderPhaseDeferredLighting_LightsToScreen::BuildDrawList#

顺序:重置着色器变量 → 设置视口 → DeferredLighting::DefaultState → HDR 格式(Xenon 8/10-bit,PS3 8/16-bit,其余 16/16-bit)→ SSAO CalculateAndApply(在清场景 RT 之前,注释:可利用 ESRAM 中的缓冲) → 锁定场景 RT 并清色 → 方向/环境全局与级联阴影着色变量 → D3D11 不支持只读深度时拷贝深度 → 绑定 GBuffer → (XMAS 天气且联机/回放,或强制下雪,且不在水下)积雪 pass → 反射图 → 方向光 + 环境 + 反射 → 植被光照 → AmbientLights::Render → CLODLights::RenderLODLights → OcclusionQueries::RenderBoxBasedQueries → 场景灯 Lights::AddToDrawListSceneLights(GPU 计时条预算 5 ms)→ 水坑 pass(湿度 > 0)→ ped 皮肤 pass → 解锁 → DefaultExitState src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:1377-1634。各 pass 的实现见 deferred-lighting-and-shadows.md。

6.3 场景合成:CRenderPhaseDrawScene::BuildDrawList#

图证据:整体 src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1647-2394;分步:src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1678-1689、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1723-1734、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1738-1771、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1786-1831、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1842-1861、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1866-1880、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1898-1916、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1986-2012、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2059-2095、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2122-2199、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2213-2252、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2281-2312、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2323-2384;Static_PostFxDepthPass → PostFX::ProcessDepthFX src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2451-2453。

要点:

  • “晚绘制”的半透明:相机在内部时,当前内部的 alpha 推迟到 RenderAfter 之后再画;相机在车内(或渲染“最后车辆”)时车内 alpha 同样推迟,状态通过 SetLateInteriorStateDC/SetLateVehicleInteriorAlphaStateDC 传到渲染线程 src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1937-1984、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2136-2211。
  • 强制 alpha 使用水阶段的实体列表(m_WaterRenderPhase->GetEntityListIndex())src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1995。
  • 拆分版本(主机):DrawScene 依次输出 _FIRST、_PRE_ALPHA、若干 _ALPHA_xx、_POST_ALPHA、_NON_DEPTH_FX、_DEBUG、_LAST 多个 drawlist,每个新 drawlist 开头都重发视口、CTimeCycle::SetShaderParams、HDR 格式、昼夜平衡等状态,因为子渲染线程不继承状态 src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2002-2037、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2260-2279。
  • 体积光在 Finalize 段 Lights::AddToDrawlistVolumetricLightingFx src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2242-2251。

7. PostFX(PostProcessFX.cpp)#

GTA V 的后处理是游戏层自有实现(namespace PostFX),不是 RAGE grpostfx。

7.1 主线程 → 渲染线程参数#

PostFX::PostFXParamBlock::ms_params[2] 双缓冲:GetUpdateThreadParams() 断言 update 线程并按 gRenderThreadInterface.GetUpdateBuffer() 索引,GetRenderThreadParams() 断言渲染线程并按 GetRenderBuffer() 索引 src/dev_ng/game/renderer/PostProcessFX.h:464-486。PostFX::Update(gameSkeleton update 调用 src/dev_ng/game/Core/game.cpp:3051)在主线程:更新镜头伪影、手机照片编辑器、暂停菜单 PostFX、屏幕模糊淡入淡出;决定光线(light rays)开关(相机在水下或强制水面以上,且方向光强度 > 0);维护运动模糊的上一帧 view-proj(相机切换或前向夹角 > 90° 时重置);并从 timecycle 当前关键帧读取 filmic 色调映射 A–F/W(明暗两组 + 覆盖开关)、色彩校正/偏移、去饱和、bloom 阈值/强度、曝光微调与上下限等写入参数块 src/dev_ng/game/renderer/PostProcessFX.cpp:7422-7690。timecycle 本身见 environment-rendering.md。

7.2 ProcessDepthFX(DrawScene 中、alpha 之前)#

若启用雾光(POSTFX_FOGRAY,要求阴影有效且强度 > 0;PC 还要求着色质量 > 0):把级联阴影图降采样到 mini 图 → 生成 FogRayRT(SM5 用 gather 版本)→ 双边高斯横/纵模糊;之后在只读深度上执行深度雾 pass(pp_depthfx[_tiled],MSAA 时分边缘/非边缘)src/dev_ng/game/renderer/PostProcessFX.cpp:8667-8916。

7.3 ProcessNonDepthFX(DrawScene 末尾)#

按代码顺序(src/dev_ng/game/renderer/PostProcessFX.cpp:9240-10760):

  1. 初始化曝光 RT;设置方向运动模糊参数(PC PostFX 设置为 Ultra 时 16 采样,否则 6)src/dev_ng/game/renderer/PostProcessFX.cpp:9361-9370。
  2. 曝光回读:从上一帧曝光 RT 读回 4 个值(2 的幂曝光、曝光、目标曝光、亮度)写入 g_adaptation;PC 多 GPU 时默认不回读(除非 g_forceExposureReadback);读回 NaN/过大值时复位 src/dev_ng/game/renderer/PostProcessFX.cpp:9413-9494。曝光 RT 数在 PC 为 3 + GPU 数,主机为 3 src/dev_ng/game/renderer/PostProcessFX.cpp:874-880。
  3. 半分辨率下采样(有运动模糊时用 pp_DownSampleStencil)→ ComputeLuminance → 曝光(pp_exposure / _set / _reset)src/dev_ng/game/renderer/PostProcessFX.cpp:9526-9586。
  4. 高质量 DOF 缓冲 ProcessHighDOFBuffers、热浪 ProcessHeatHazeBuffers、夜视亮度参数 src/dev_ng/game/renderer/PostProcessFX.cpp:9607-9644。
  5. Bloom(透视层单独 bloom)、屏幕空间光线(SSLR:强度、挤出、高斯模糊)、BlurBloomBuffer src/dev_ng/game/renderer/PostProcessFX.cpp:9692-9885。
  6. 镜头伪影 LENSARTEFACTSMGR.Render(与热浪互斥)src/dev_ng/game/renderer/PostProcessFX.cpp:9908-9940。
  7. 合成 pass:technique 索引由位组合 BM_HighDof/BM_HighDofShallow/BM_MotionBlur/BM_NightVision/BM_HeatHaze/BM_Noise/BM_ExtraEffects 选择,暗角与渐变滤镜参数打包进同一组常量 src/dev_ng/game/renderer/PostProcessFX.cpp:10034-10069;Bokeh 精灵结果作为输入 src/dev_ng/game/renderer/PostProcessFX.cpp:10252-10261。
  8. 扫描线滤镜 → 抗锯齿:TXAA 优先于 FXAA/MLAA;FXAA/MLAA 前先解析 MSAA;否则普通 MSAA 解析 src/dev_ng/game/renderer/PostProcessFX.cpp:9973-9980、src/dev_ng/game/renderer/PostProcessFX.cpp:10382-10610。
  9. 第一人称运动模糊(3 步)→ 镜头畸变(POSTFX_SEPARATE_LENS_DISTORTION_PASS)→ 伤害覆盖层 src/dev_ng/game/renderer/PostProcessFX.cpp:10635-10701。

MLAAEnabled = !TXAAEnabled && !FXAAEnabled && MLAA_ONLY(g_MLAA.IsEnabled() || ) 0 && USE_FXAA && … —— 按 C++ 优先级,在 MLAA_ONLY 展开为参数的构建中等价于 !TXAA && !FXAA && (IsEnabled() || (0 && …)),否则恒为假 src/dev_ng/game/renderer/PostProcessFX.cpp:9976。各平台 MLAA_ONLY/ENABLE_MLAA 的取值本页未核对。

多渲染线程注意:主机上 PostFX 使用带 append 计数器的 UAV,这在多线程上下文中不安全,因此 DL_DRAWSCENE_NON_DEPTH_FX 与 DL_HUD 被限制到 CPU_DRAWSCENE(D3D11 下为主渲染线程)src/dev_ng/game/renderer/DrawLists/drawListMgr.cpp:140-156。

8. 渲染目标与分辨率#

8.1 CRenderTargets 主要缓冲#

CRenderTargets::AllocateRenderTargets 按平台分支创建:PC/Durango/Orbis 的 BackBuffer 为 A16B16G16R16F(64 bpp、sRGB;非 final 可用 -DX11Use8BitTargets 改为 8 位),MSAA 取 GRCDEVICE.GetMSAA(),失败直接 Quitf(ERR_GFX_RENDERTARGET);PS3 的 BackBuffer FP16/BackBuffer 放在 GCM 内存池;Xenon 在 EDRAM 堆 src/dev_ng/game/renderer/rendertargets.cpp:1426-1553、src/dev_ng/game/renderer/rendertargets.cpp:1867-1891。深度有 DepthBuffer、DepthBuffer Pre Alpha(透过窗户的 DOF 等用)、DepthBufferCopy(D3D11 不允许只读深度视图时)、DepthBuffer_Resolved、UI 深度缓冲与若干只读视图;Durango 的深度声明可驻留 ESRAM 的阶段集合(GBUF/阴影/雨碰撞/水反射/雨更新/光照/DrawScene/HUD),ESRAM 上限 4 MB src/dev_ng/game/renderer/rendertargets.cpp:1592-1673。阶段内切换目标的主要接口是 LockSceneRenderTargets/UnlockSceneRenderTargets/LockUIRenderTargets src/dev_ng/game/renderer/rendertargets.cpp:2186-2252。GBuffer 布局见 deferred-lighting-and-shadows.md。

反射图:立方体 6 面渲染后合成到抛物面 RT,默认 REFLECTION_TARGET_WIDTH × HEIGHT = 1024 × 512、6 级 mip;PC/D3D11/Orbis 用 R11G11B10F,否则 A16B16G16R16F;PC(除非 -UseDefaultResReflection)与 Orbis(-UseVarReflection)按设置 m_ReflectionQuality 与分辨率计算尺寸,并增加 mip 级使最低级与主机一致 src/dev_ng/game/camera/viewports/ViewportManager.cpp:604-720、src/dev_ng/game/renderer/RenderPhases/RenderPhaseReflection.h:22-24。

8.2 分辨率:VideoResManager#

三组尺寸:native(设备)、scene/game(3D 场景)、UI(固定 PSN_BUFFER_WIDTH/HEIGHT)。PC 有 10 种缩放模式(MODE_1o1…MODE_5o2,0.5×–2.5×),由设置 m_SamplingMode 选择,DeviceReset 时按 GRCDEVICE 宽高 × 比例重算 game 尺寸;主机 game 尺寸固定为 PSN_BUFFER_*;-gamewidth/-gameheight 可覆盖 src/dev_ng/game/renderer/rendertargets.h:629-686、src/dev_ng/game/renderer/rendertargets.cpp:3741-3846、src/dev_ng/game/system/SettingsManager.cpp:613。IsSceneScaled() 为真时 DrawScene 需把 pre-alpha 深度额外拷贝到 UI 尺寸 src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1904-1909。

动态分辨率:在 game/、rage/base/src/grcore、rage/framework/src 中检索 dynres/dynamic_res/DynamicResolution 未发现运行时按 GPU 负载调整分辨率的实现(device_d3d11.cpp 的命中只是“动态资源”统计);PC 的缩放是设置项决定的静态比例,通过设备重置生效。这是“检索未发现”,不是对不存在的证明。

9. 平台与构建差异#

开关值影响证据
MULTIPLE_RENDER_THREADSDurango/Orbis = 4,其余 0子渲染线程src/dev_ng/rage/base/src/grcore/effect_mrt_config.h:2-11
RENDER_PHASE_SUPPORT_MULIPLE_DRAW_LISTSMULTIPLE_RENDER_THREADS && (PC‖Orbis‖Durango) → 实际仅主机阶段可输出多个 drawlistsrc/dev_ng/rage/framework/src/fwrenderer/renderphaseswitches.h:7
DRAW_LIST_MGR_SPLIT_GBUFFER主机开、PC 关(0 && RSG_PC)GBuffer 拆为 DL_RENDERPHASE_GBUF_000…113src/dev_ng/game/renderer/DrawLists/drawListMgr_config.h:4
DRAW_LIST_MGR_SPLIT_CASCADE_SHADOWS/PARABOLOID_SHADOWS/DRAWSCENE表面写 PC/Orbis/Durango 都开,但与上一行相与PC 上因 MRT=0 实际不拆src/dev_ng/game/renderer/DrawLists/drawListMgr_config.h:5-7
HAS_RENDER_MODE_SHADOWS!__PS3PS3 无阴影 render modesrc/dev_ng/game/renderer/Renderer.h:84
__XENON / __PPU360 GPR 分配命令、Hi-Z 控制、平铺 GBuffer;PS3 bubble fence、SPU SSAO、EDGE 阴影 pass 表遗留代码路径大量存在src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:818-824、src/dev_ng/game/renderer/RenderPhases/RenderPhase.cpp:114-126、src/dev_ng/game/renderer/RenderListBuilder.cpp:251-269
__BANK实体选择/调试叠加阶段、GBuffer 可视化、pass/bucket 禁用、隔离单个阶段src/dev_ng/rage/framework/src/fwrenderer/renderlistbuilder.cpp:173-193
!__FINAL截图阶段src/dev_ng/game/camera/viewports/ViewportManager.cpp:940-942

10. 多 GPU(PC AFR)注意事项#

游戏层对多 GPU(交替帧渲染)的处理是分散的“每 GPU 一份 / 每 GPU 计数”模式:

位置做法证据
脚本/手机 RT 阶段启用时 SetMultiGPUDrawListCounter(GetGPUCount(true)),每帧递减,降到 0 才 Disable(),保证每块 GPU 的 RT 副本都被画过src/dev_ng/game/renderer/RenderTargetMgr.h:180-185、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1158-1167
PostFX 曝光曝光 RT 数 3 + GPU 数;多 GPU 时跳过 GPU 回读并设 g_setExposureToTarget = true(直接使用目标曝光)src/dev_ng/game/renderer/PostProcessFX.cpp:874-880、src/dev_ng/game/renderer/PostProcessFX.cpp:8009-8013、src/dev_ng/game/renderer/PostProcessFX.cpp:9447
暂停画面ResolveForMultiGPU:把后台缓冲拷到带 CPU 副本的纹理,再逐 GPU UpdateGPUCopyFromBackingStoresrc/dev_ng/game/renderer/rendertargets.cpp:3494-3529、src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:2289-2310
局部阴影缓存静态缓存条目再生成时记录 m_gpucounter = GetGPUCount(true),需在每块 GPU 上都重建src/dev_ng/game/renderer/Shadows/ParaboloidShadows.cpp:1084-1089
水面模拟≥ 2 GPU 时关闭 GPU 水面更新;GPU 参数数组按 GPU 数分配src/dev_ng/game/renderer/Water.cpp:2322-2331、src/dev_ng/game/renderer/Water.cpp:2708-2711

其余命中(SSAO、小地图、加载画面、暂停菜单、远处灯、水坑等)未逐一阅读。共同原理(推断):AFR 下每块 GPU 有独立资源副本,任何“跨帧累积”的 GPU 状态都必须按 GPU 数重复更新或改走 CPU。

11. 其他 game/renderer 组件(盘点)#

组件作用(以类/接口为依据)详述位置
COcclusion(occlusion.*)游戏侧遮挡:每阶段 BeforeSetupRender、栅格化盒体/模型遮挡体与水面、Hi-Z 缓冲访问、IsSphereVisible/IsConeVisible、PrimeZBuffersrc/dev_ng/game/renderer/occlusion.h:37-96;软件光栅器见 03 页
OcclusionQueriesGPU 盒体遮挡/条件查询(dlCmdBoxOcclusionQueries/BoxConditionalQueries),在光照阶段执行src/dev_ng/game/renderer/OcclusionQueries.h:39-91、src/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:1580-1584
SSAO、MLAA、AdaptiveDOF、LensArtefacts、PostProcessFXHelperSSAO(光照阶段调用)、MLAA、自适应 DOF、镜头伪影、PostFX 动画/辅助SSAO 见 deferred-lighting-and-shadows.md
RenderTargetMgr命名 RT(脚本/电影网格/手机屏)与阶段关联src/dev_ng/game/renderer/RenderTargetMgr.h:39
MeshBlendManager、ApplyDamageped 头部网格混合、车辆 GPU 形变(CApplyDamage::DrawDamage 在 DrawScene 开头)../06-peds/ped-motion-and-rendering.md、../08-vehicles/vehicle-physics-handling-damage.md
UI3DDrawManager、ScreenshotManager、sprite2d前端 3D ped 渲染、截图、2D 精灵/屏幕四边形../18-frontend-ui/pause-menu-and-hud.md
HorizonObjects、TreeImposters、TerrainEdgeInfo、zonecull(CCullZones、CMirrorAttributeZone)、River地平线远景、树木替身、地形开放边、剔除区/镜面属性区、河流实体environment-rendering.md
computeshaderD3D11 计算着色器管理(gCSGameManager)src/dev_ng/game/renderer/Renderer.cpp:581-583
Util/(ShaderUtil、UpsampleHelper、RenderDocManager、ShmooFile、向量/RNG 工具)着色辅助、RenderDoc 接入、着色器 shmoo 配置shaders-and-materials.md
Debug/(EntitySelect、Raster)、RenderPhaseDebugOverlay实体拾取 ID 渲染、调试栅格化、线框/叠加../23-debug-security/debug-tools.md

12. 约束与边界行为#

  1. 阶段顺序就是执行顺序,没有依赖图;在 CreateFinalScreenRenderPhaseList 中插入新阶段需同时考虑 drawlist 顺序与 RT 复用(例如 ped damage 复用反射内存的注释)。
  2. phase bit 上限 24,按类型预留;新增扫描型阶段必须在 CGtaRenderListGroup::Init 中有剩余配额,否则 RegisterRenderPhase 断言。
  3. 渲染列表的线程归属:默认只有 update 线程可访问;BuildRenderListsFromPVS 期间临时切到 PostScan 线程,排序期间禁止访问。
  4. alpha 剔除、淡出与强制级联:低 alpha 实体在大多数阶段被丢弃,但抛物面反射与“强制出现在级联阴影”的实体例外,水反射可强制满 alpha。
  5. D3D11 只读深度:设备不支持只读深度视图时,光照、水反射、DrawScene 都改走深度拷贝路径(CopyDepthBufferCmd、*_DEPTH_COPY)。
  6. 设备重置(PC):整套阶段与 grcViewport 类被销毁重建,全局阶段指针(g_SceneToGBufferPhaseNew 等)在 ShutdownRenderPhases 中清空。

13. 未决问题 / 证据不足#

  • RenderPhaseCascadeShadows.cpp(约 1 万行)的内部(级联划分、reveal 子 drawlist、粒子阴影)本页只引用入口,细节在 deferred-lighting-and-shadows.md。
  • CRenderPhaseMirrorReflection、CRenderPhaseWaterReflection 在 environment-rendering.md 简述;CRenderPhaseTreeImposters、CRenderPhaseHud/FrontEnd/PhoneModel/LensDistortion/TimeBars 未逐行阅读。
  • PostFX:Bokeh DOF、Diffusion DOF(计算着色器)、ComputeLuminance 的降采样链、TXAA 集成只列出入口。
  • 反射阶段注册位置与注释“after shadow phase”不一致,未追踪反射着色器读取的阴影数据属于哪一帧。
  • MLAA_ONLY、ENABLE_MLAA、USE_FXAA 在各平台的取值未核对。