渲染管线与渲染阶段:CRenderer、RenderPhases、视口、可见性到绘制列表、PostFX 与渲染目标
本页解释 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。
背景与相关页面#
- 主循环中
Visibility与Build Drawlist两段计时条的位置与预算见 ../01-runtime/game-skeleton-and-main-loop.md;渲染线程、GPU_IdleSection/Synchronise与安全区见 ../01-runtime/threading-and-render-thread.md。 - 可见性扫描(
fwScan、cull shape、PVS、Hi-Z 遮挡测试)属于框架层,见 ../04-rage-framework/scene-graph-and-visibility.md;软件光栅化遮挡(softrasterizer)的实现见 ../03-rage-graphics/postfx-shadows-and-occlusion.md。 - 设备抽象、
grcViewport、渲染目标创建、bucket mask 的BUCKETMASK_*宏所在的grcore/effect见 ../03-rage-graphics/grcore-device-and-effects.md;rmcDrawable/LOD 见 ../03-rage-graphics/models-drawables-and-shaders.md。 - 重要事实:RAGE 的
grpostfx、grshadowmap、grlightprepass不进入游戏构建(结论由 03 页给出;本页范围内的游戏代码也未包含它们)。GTA V 的后处理是game/renderer/PostProcessFX*,阴影是game/renderer/Shadows+RenderPhaseCascadeShadows。
术语:
| 术语 | 含义 | 代码实体 |
|---|---|---|
| 渲染阶段(render phase) | 一个“主线程构建渲染列表 + 生成一段(或多段)drawlist”的单元,例如 GBuffer、级联阴影、水反射 | fwRenderPhase → CRenderPhase → CRenderPhaseScanned |
| 实体列表索引(entity list index) | 每个需要可见性扫描的阶段在 gRenderListGroup 中的槽位,也是可见性标志里的 phase bit | fwRenderPhase::m_EntityListIndex、fwRenderListGroup::RegisterRenderPhase |
渲染 pass(RPASS_*) | 一个阶段渲染列表里的子列表:可见/LOD/镂空/贴花/淡出/半透明/水/树 | RenderPassId |
渲染 bucket(RB_*) | 着色器的材质分类(不透明/半透明/贴花/镂空/水…)+ 高 8 位 sub-bucket(模型在哪类阶段可见) | CRenderer::RenderBucketTypes、BUCKETMASK_GENERATE* |
| draw mode / render mode | 渲染列表构建器的“光照模式”(延迟/前向/阴影…)映射到渲染线程的 eRenderMode | CRenderListBuilder::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.h | CViewportManager::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.cpp | CGameScan:把阶段转为 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_disableOnPause | src/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 类型 | 视口 | 条件/说明 |
|---|---|---|---|---|
| 1 | CRenderPhasePreRenderViewport | DL_RENDERPHASE_PRE_RENDER_VP | game | PC 上处理暂停解析、DOF RT 重置、LOD 灯帧信息清理等前置工作 src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:236-262 |
| 2 | CRenderPhaseEntitySelect | DL_RENDERPHASE_DEBUG | game | ENTITYSELECT_ENABLED_BUILD(调试拾取) |
| 3 | CRenderPhaseTreeImposters | DL_RENDERPHASE_TREE | game | USE_TREE_IMPOSTERS |
| 4,5 | CRenderPhaseScript2d ×2 | DL_RENDERPHASE_SCRIPT | 无 | 分别登记为 CRenderTargetMgr::RTI_Scripted 与 RTI_MovieMesh |
| 6 | CRenderPhaseCloudGeneration | DL_RENDERPHASE_CLOUD_GEN | game | 只执行 CSky::PreRender src/dev_ng/game/renderer/RenderPhases/RenderPhaseWater.cpp:79-84 |
| 7 | CRenderPhaseReflection | DL_RENDERPHASE_REFLECTION_MAP | game | 创建反射 RT(§8);立方体 6 面 → 抛物面 + mip 模糊 src/dev_ng/game/renderer/RenderPhases/RenderPhaseReflection.cpp:774-829 |
| 8 | CRenderPhasePedDamageUpdate | DL_RENDERPHASE_PED_DAMAGE_GEN | game | 注释“after Reflection so we can reuse the memory”;调用 CPedDamageManager::AddToDrawList src/dev_ng/game/renderer/RenderPhases/RenderPhasePedDamage.cpp:45-54 |
| 9 | CRenderPhaseWaterSurface | DL_RENDERPHASE_WATER_SURFACE | game | 水面 GPU 动态模拟与湿润图更新 src/dev_ng/game/renderer/RenderPhases/RenderPhaseWater.cpp:50-66 |
| 10 | CRenderPhaseDeferredLighting_SceneToGBuffer | DL_RENDERPHASE_GBUF | game | 主 GBuffer 阶段(全局 g_SceneToGBufferPhaseNew) |
| 11 | CRenderPhaseDeferredLighting_SSAOSPU | DL_RENDERPHASE_LIGHTING | game | 仅 SPUPMMGR_PS3 |
| 12 | CRenderPhaseParaboloidShadow ×MAX_ACTIVE_PARABOLOID_SHADOWS | DL_RENDERPHASE_PARABOLOID_SHADOWS | game | 局部灯阴影,数量 = 4 高分辨率 + 4 低分辨率 = 8 src/dev_ng/game/renderer/Shadows/ParaboloidShadows_shared.h:24-38 |
| 13 | CRenderPhaseCascadeShadows | DL_RENDERPHASE_CASCADE_SHADOWS | game | 方向光级联阴影(g_CascadeShadowsNew) |
| 14 | CRenderPhaseHeight("raincollision") | DL_RENDERPHASE_RAIN_COLLISION_MAP | game | 128² D32FS8 高度图,同时登记为 GPU 粒子碰撞图 |
| 15 | CRenderPhaseRainUpdate | DL_RENDERPHASE_RAIN_UPDATE | game | 非 PS3 在水反射之后注册、PS3 在其之前;只在外景于 GBuffer 中可见或相机在水下时更新 GPU 天气粒子 src/dev_ng/game/renderer/RenderPhases/RenderPhaseWater.cpp:97-115 |
| 16 | CRenderPhaseWaterReflection | DL_RENDERPHASE_WATER_REFLECTION | game | 创建 WATER_REFLECTION_* RT(R11G11B10F,5 mip;PC 可选 MSAA) |
| 17 | CRenderPhasePreRenderViewport(第二个) | DL_RENDERPHASE_PRE_RENDER_VP | game | 源码注释 “TODO: Really game viewport?” |
| 18 | CRenderPhaseDeferredLighting_LightsToScreen | DL_RENDERPHASE_LIGHTING | game | 延迟光照合成,SetGBufferRenderPhase(g_SceneToGBufferPhaseNew) |
| 19 | CRenderPhaseMirrorReflection | DL_RENDERPHASE_MIRROR_REFLECTION | game | 平面镜/镜面 |
| 20 | RenderPhaseSeeThrough | DL_RENDERPHASE_SEETHROUGH_MAP | game | 热成像“透视”层:800×450 R11G11B10F 颜色/模糊 + 深度 |
| 21 | CRenderPhaseDrawScene | DL_RENDERPHASE_DRAWSCENE | game | 天空/水/半透明/特效/PostFX;复用 GBuffer 阶段的实体列表索引 |
| 22 | CRenderPhaseDebugOverlay | DL_RENDERPHASE_DEBUG_OVERLAY | game | __BANK,同样复用 GBuffer 列表 |
| 23 | CRenderPhaseDebug3d | DL_RENDERPHASE_DEBUG | game | DEBUG_DISPLAY_BAR |
| 24 | CRenderPhaseHud | DL_RENDERPHASE_HUD | ortho | HUD |
| 25,26 | CRenderPhasePhoneScreen(RTI_PhoneScreen)、CRenderPhasePhoneModel | DL_RENDERPHASE_SCRIPT / DL_RENDERPHASE_PHONE | ortho / game | 手机屏幕 RT 与手机模型 |
| 27 | CRenderPhaseFrontEnd | DL_RENDERPHASE_FRONTEND | ortho | 注释“leveraging this empty render phase” |
| 28 | CRenderPhaseDebug2d | DL_RENDERPHASE_DEBUG | ortho | DEBUG_DISPLAY_BAR |
| 29 | CRenderPhaseTimeBars | DL_RENDERPHASE_TIMEBARS | ortho | 计时条 |
| 30 | CRenderPhaseLensDistortion | DL_RENDERPHASE_LENSDISTORTION | ortho | 镜头畸变 |
| 31 | CRenderPhaseScreenShot | DL_RENDERPHASE_DEBUG | ortho | !__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_SHADOW | 9 | 1 级联 + 8 局部阴影 |
RPTYPE_REFLECTION | 3 | 1 抛物面 + 1 水 + 1 镜面 |
RPTYPE_MAIN | 3 | GBuf 等 |
RPTYPE_STREAMING | 5 | 1 HD 球 + 1 LOD 球 + 1 内部球 + 2 流式体积 |
RPTYPE_HEIGHTMAP | 1 | |
RPTYPE_DEBUG | 1 |
证据: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 → StartScansrc/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::BeforeRendersrc/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::PerformScansrc/dev_ng/game/renderer/Renderer.cpp:1593-1634;PerformScan=ProcessPvsWhileScanning+fwScan::JoinScansrc/dev_ng/game/scene/world/GameScan.cpp:593-612。BuildRenderListsNew= LOD 灯桶可见性 →ProcessPVSForRenderPhase→CRenderer::PreRenderEntitiessrc/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_EndRendersrc/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。
关键约束:
UpdateViewports先把所有阶段SetActive(false),阶段在自己的UpdateViewport()中未早退时再SetActive(true)(如 GBuffersrc/dev_ng/game/renderer/RenderPhases/RenderPhaseDefLight.cpp:481、DrawScenesrc/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:1278)src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.cpp:279-294。只有 active 的扫描型阶段会产生 cull shape(§4)。__ASSERT一致性检查:BuildRenderList之后断言“调用过InitRenderListBuild的阶段必须 active,反之亦然”src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.cpp:249-259。BuildDrawList跳过条件:IsDisabled()或!IsBuildDrawListEnabled()src/dev_ng/rage/framework/src/fwrenderer/RenderPhaseBase.cpp:263-277。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_ALPHA | RPASS_FADING | |
HAS_OPAQUE 其他 | RPASS_VISIBLE | |
HAS_ALPHA 且该 bit 允许 alpha | RPASS_ALPHA | ped 额外带 DRAW_FIRST_SORTED;超出 VLT_LOD2 距离的陆地车辆不进 alpha |
HAS_DISPL_ALPHA / HAS_WATER / HAS_DECAL / HAS_CUTOUT | RPASS_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 mode | RM_NORMAL | RM_NO_FADE | RM_NOT_DEFERRED |
|---|---|---|---|
USE_FORWARD_LIGHTING | rmStandard | rmStandard | rmStandard |
USE_SIMPLE_LIGHTING | rmSimple | rmSimpleNoFade | rmSimpleNoFade |
USE_DEFERRED_LIGHTING | rmGBuffer | rmGBuffer | rmStandard |
USE_SIMPLE_LIGHTING_DISTANCE_FADE | rmSimpleDistFade | 同 | 同 |
USE_NO_LIGHTING | rmNoLights | 同 | 同 |
USE_SHADOWS / USE_SKINNED_SHADOWS | rmShadows / 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_OpaquePass | VISIBLE/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_DecalPass | DECAL/DECAL、FADING/DECAL(仅延迟) |
s_AlphaPass | FADING/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、CCutSceneVehicleDrawHandler | ped、车辆、物体、过场车辆 | 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::AddToDrawlistVolumetricLightingFxsrc/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):
- 初始化曝光 RT;设置方向运动模糊参数(PC PostFX 设置为 Ultra 时 16 采样,否则 6)
src/dev_ng/game/renderer/PostProcessFX.cpp:9361-9370。 - 曝光回读:从上一帧曝光 RT 读回 4 个值(2 的幂曝光、曝光、目标曝光、亮度)写入
g_adaptation;PC 多 GPU 时默认不回读(除非g_forceExposureReadback);读回 NaN/过大值时复位src/dev_ng/game/renderer/PostProcessFX.cpp:9413-9494。曝光 RT 数在 PC 为3 + GPU 数,主机为 3src/dev_ng/game/renderer/PostProcessFX.cpp:874-880。 - 半分辨率下采样(有运动模糊时用
pp_DownSampleStencil)→ComputeLuminance→ 曝光(pp_exposure/_set/_reset)src/dev_ng/game/renderer/PostProcessFX.cpp:9526-9586。 - 高质量 DOF 缓冲
ProcessHighDOFBuffers、热浪ProcessHeatHazeBuffers、夜视亮度参数src/dev_ng/game/renderer/PostProcessFX.cpp:9607-9644。 - Bloom(透视层单独 bloom)、屏幕空间光线(SSLR:强度、挤出、高斯模糊)、
BlurBloomBuffersrc/dev_ng/game/renderer/PostProcessFX.cpp:9692-9885。 - 镜头伪影
LENSARTEFACTSMGR.Render(与热浪互斥)src/dev_ng/game/renderer/PostProcessFX.cpp:9908-9940。 - 合成 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。 - 扫描线滤镜 → 抗锯齿: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。 - 第一人称运动模糊(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_THREADS | Durango/Orbis = 4,其余 0 | 子渲染线程 | src/dev_ng/rage/base/src/grcore/effect_mrt_config.h:2-11 |
RENDER_PHASE_SUPPORT_MULIPLE_DRAW_LISTS | MULTIPLE_RENDER_THREADS && (PC‖Orbis‖Durango) → 实际仅主机 | 阶段可输出多个 drawlist | src/dev_ng/rage/framework/src/fwrenderer/renderphaseswitches.h:7 |
DRAW_LIST_MGR_SPLIT_GBUFFER | 主机开、PC 关(0 && RSG_PC) | GBuffer 拆为 DL_RENDERPHASE_GBUF_000…113 | src/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 | !__PS3 | PS3 无阴影 render mode | src/dev_ng/game/renderer/Renderer.h:84 |
__XENON / __PPU | 360 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 UpdateGPUCopyFromBackingStore | src/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、PrimeZBuffer | src/dev_ng/game/renderer/occlusion.h:37-96;软件光栅器见 03 页 |
OcclusionQueries | GPU 盒体遮挡/条件查询(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、PostProcessFXHelper | SSAO(光照阶段调用)、MLAA、自适应 DOF、镜头伪影、PostFX 动画/辅助 | SSAO 见 deferred-lighting-and-shadows.md |
RenderTargetMgr | 命名 RT(脚本/电影网格/手机屏)与阶段关联 | src/dev_ng/game/renderer/RenderTargetMgr.h:39 |
MeshBlendManager、ApplyDamage | ped 头部网格混合、车辆 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 |
computeshader | D3D11 计算着色器管理(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. 约束与边界行为#
- 阶段顺序就是执行顺序,没有依赖图;在
CreateFinalScreenRenderPhaseList中插入新阶段需同时考虑 drawlist 顺序与 RT 复用(例如 ped damage 复用反射内存的注释)。 - phase bit 上限 24,按类型预留;新增扫描型阶段必须在
CGtaRenderListGroup::Init中有剩余配额,否则RegisterRenderPhase断言。 - 渲染列表的线程归属:默认只有 update 线程可访问;
BuildRenderListsFromPVS期间临时切到 PostScan 线程,排序期间禁止访问。 - alpha 剔除、淡出与强制级联:低 alpha 实体在大多数阶段被丢弃,但抛物面反射与“强制出现在级联阴影”的实体例外,水反射可强制满 alpha。
- D3D11 只读深度:设备不支持只读深度视图时,光照、水反射、DrawScene 都改走深度拷贝路径(
CopyDepthBufferCmd、*_DEPTH_COPY)。 - 设备重置(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在各平台的取值未核对。