CD Code Digest Hub /GTA V · v2

Ped 运动与渲染:运动数据、运动任务、动画接口、LOD、变体流送、头部混合、伤害与头像

69 分钟阅读 17 个章节 2 张图

本页覆盖 ped 从“想往哪走”到“画到屏幕上”的后半段链路:CPedMotionData(移动混合比 MBR、朝向、姿态状态)、主运动任务的选择与 ApplyMovementRequests、CPed 暴露给动画系统的接口(MoVE 网络、更新频率、IK/表达式、PreRender/PostPreRender)、UpdateLodState 的 LOD 与淡入淡出、CPedDrawHandler 的绘制分派,以及 Peds/rendering 下的渲染支撑系统:流送 ped 的 CPedVariationStream(请求/渲染 gfx 与渲染线程引用计数)、打包 ped 的 CPedVariationPack(含超低模 SLOD)、头部混合 CPedHeadBlendData 与 MeshBlendManager、道具、伤害/纹身 CPedDamageManager/PedDecorationManager、头像 PedHeadshotManager。

一个容易误解的点必须先说明:目录 Peds/PedMoveBlend 名字像是“运动混合器”,但在本代码中它几乎是空壳——真正的运动逻辑已经迁移到运动任务(CTaskMotionPed 等,基于 MoVE 网络),PedMoveBlend 只剩一个仍在使用的头文件(见第 2 节)。

背景与相关页面#

相关源码文件#

文件作用
src/dev_ng/game/Peds/PedMoveBlend/*遗留目录;仅 PedMoveBlendBase.h 有实际内容(CMoveBlendRatioSpeeds/CMoveBlendRatioTurnRates)
src/dev_ng/game/Peds/PedMotionData.h、PedMotionData.cpp、PedMotionState.pscCPedMotionData、MBR 常量、eMotionState
src/dev_ng/game/task/System/MotionTaskData.hMotionTaskType、sMotionTaskData(由 peds.meta 的运动任务数据集引用)
src/dev_ng/game/Peds/Ped.cpp、ped.h运动任务选择、ApplyMovementRequests、动画接口、PreRender、UpdateLodState
src/dev_ng/game/Peds/PedTuning.h、PedTuning.cppLOD 距离阈值
src/dev_ng/game/renderer/Entities/PedDrawHandler.h/.cppCPedDrawHandler::AddToDrawList、SetPedRenderGfx
src/dev_ng/game/Peds/rendering/PedVariationStream.h/.cpp流送 ped:CPedVariationStream、CPedStreamRequestGfx、CPedStreamRenderGfx、第一人称数据
src/dev_ng/game/Peds/rendering/PedVariationPack.h/.cpp打包 ped(非流送)与 SLOD 渲染
src/dev_ng/game/Peds/rendering/PedVariationDS.h/.cpp、pedvariations.psc、alternatevariations.psc变体数据结构、头部混合数据、资源加载完成回调
src/dev_ng/game/renderer/MeshBlendManager.h头部网格/纹理混合管理器
src/dev_ng/game/Peds/rendering/PedProps.h/.cpp道具请求/渲染 gfx 与 CPedPropsMgr
src/dev_ng/game/Peds/rendering/PedDamage.h/.cpp/.psc血迹/刀伤/纹身/装饰的渲染目标管理
src/dev_ng/game/Peds/rendering/PedOverlay.h/.cpp/.psc纹身/徽章/奖章装饰集合(PedDecorationManager)
src/dev_ng/game/Peds/rendering/PedHeadshotManager.h/.cppped 头像渲染与压缩
src/dev_ng/game/Peds/rendering/PedVariationDebug.h/.cpp__BANK 调试工具

1. 帧内时间线(运动 → 动画 → 渲染)#

图中证据:PrePhysics 场景回调调用 StartAnimUpdate src/dev_ng/game/scene/world/GameWorld.cpp:2650、GameWorld.cpp:2731-2734;CPed::StartAnimUpdate 以 AI LOD 的动画时间步调用 CPhysical::StartAnimUpdate src/dev_ng/game/Peds/Ped.cpp:6694-6751;ProcessControl_Intelligence 中 UpdateMovementMode → ProcessPreTaskTrees → CPedIntelligence::Process → ProcessPostTaskTrees Ped.cpp:31225-31237;ProcessPhysics 中调用 ApplyMovementRequests Ped.cpp:20068;CGameWorld::ProcessAfterAllMovement GameWorld.cpp:2182 中调用 CPedMotionData::ProcessPostMovement GameWorld.cpp:2279-2289;PreRender Ped.cpp:23301-23526、PostPreRender Ped.cpp:23530-23620;AddToDrawList src/dev_ng/game/renderer/Entities/PedDrawHandler.cpp:342-540。

ProcessControl 的完整阶段顺序、ProcessPhysics 的跳过条件见 ped-architecture.md;这里只关注与运动/渲染相关的环节。

2. Peds/PedMoveBlend:遗留的“移动混合器”目录#

早期 GTA(IV 时代)的 ped 运动由一组 CPedMoveBlend* 类实现:每个 ped 持有一个按情境(步行、水中、车内、滑雪、低重力、边跑边射……)切换的混合器。GTAV 的代码中这些类已经被掏空:

文件现状
PedMoveBlendBase.cpp、PedMoveBlendManager.cpp、PedMoveBlendOnFoot.cpp、PedMoveBlendTables.cpp0 字节
PedMoveBlendManager.h、PedMoveBlendTypes.h、PedMoveBlend.h、MoveBlendTable.h 等只有 include guard 或前置声明,例如 PedMoveBlendTypes.h 仅声明 CPedMoveBlendBase/CPed src/dev_ng/game/Peds/PedMoveBlend/PedMoveBlendTypes.h:1-7,PedMoveBlendManager.h 只有 guard PedMoveBlendManager.h:1-4
PedMoveBlendOnSkis.cpp、PedMoveBlendInWater.cpp、PedMoveBlendTypes.cpp 等只剩 #include 列表,没有函数体,例如 PedMoveBlendOnSkis.cpp:1-33、PedMoveBlendTypes.cpp:1-13
PedMoveBlendOnFootPM.h、PedMoveBlendOnFootTest.h仅保留历史注释:前者是 2009-02-18 开始的“用参数化运动实现部分移动”的 OnFoot 变体 PedMoveBlendOnFootPM.h:2-6;后者是“使用 MoVE 网络与 AI 运动任务树的新 locomotion 实现的测试” PedMoveBlendOnFootTest.h:4-7
PedMoveBlendBase.h唯一仍被使用的内容:CMoveBlendRatioSpeeds 与 CMoveBlendRatioTurnRates

后一条注释正好说明了迁移方向:运动从“混合器对象”改成了“运动任务树(PED_TASK_TREE_MOTION)+ MoVE 网络”。残留痕迹还包括 PedFactory.cpp 中仍定义却无人使用的 LARGEST_MOVEBLENDER 宏(引用了已不存在的 CPedMoveBlendInWater 等类型的 sizeof,因为宏未被展开所以能编译)src/dev_ng/game/Peds/PedFactory.cpp:69,以及 ped.h 仍包含 PedMoveBlendBase.h src/dev_ng/game/Peds/ped.h:31。

2.1 CMoveBlendRatioSpeeds / CMoveBlendRatioTurnRates#

  • CMoveBlendRatioSpeeds 保存 MBR 为 0/1/2/3(静止/走/跑/冲刺)时的移动速度 m_fSpeeds[4];GetMoveSpeed(mbr) 在相邻两档之间插值 src/dev_ng/game/Peds/PedMoveBlend/PedMoveBlendBase.h:19-57。头注释要求即使 ped 跑不到冲刺,也要把冲刺速度填成它能达到的最高速度 PedMoveBlendBase.h:15-17。
  • 源码缺陷(行为证据):走→跑、跑→冲刺两段的插值写成 (m_fSpeeds[2] - m_fSpeeds[1])*fMoveBlendRatio-1.0f,由于运算符优先级,实际计算的是“差值×MBR 再减 1”,而不是“差值×(MBR−1)”PedMoveBlendBase.h:45、PedMoveBlendBase.h:49。在 MBR∈[1,2) 时结果在区间端点处不连续。调用者例如 CTaskMotionBasicLocomotion 用它估算停止距离 src/dev_ng/game/task/Motion/Locomotion/TaskLocomotion.cpp:219-221;这一偏差对玩法的实际影响未验证。
  • CMoveBlendRatioTurnRates 保存走/跑/冲刺三档转向速率(弧度/秒),GetTurnRate 是阶梯函数(不插值)PedMoveBlendBase.h:65-103。
  • 两者通过运动任务的虚接口提供:CTaskMotionBase::GetMoveSpeeds(纯虚)与 GetTurnRates src/dev_ng/game/task/Motion/TaskMotionBase.h:136、TaskMotionBase.h:208,速度通常由 clip set 中的移动片段提取(RetrieveMoveSpeeds)TaskMotionBase.h:302。

3. CPedMotionData:运动意图的共享黑板#

CPed::m_MotionData 是 AI/玩家控制与运动任务之间的“黑板”:上层写入期望值(期望 MBR、期望朝向、强制状态),运动任务读取并驱动 MoVE 网络,物理阶段再用运动任务算出的速度推动胶囊。

3.1 MBR 约定#

常量值证据
MOVEBLENDRATIO_REVERSE / STILL / WALK / RUN / SPRINT−1 / 0 / 1 / 2 / 3src/dev_ng/game/Peds/PedMotionData.cpp:13-18
MBR_WALK_BOUNDARY / MBR_RUN_BOUNDARY / MBR_SPRINT_BOUNDARY0.1 / 1.5 / 2.5PedMotionData.cpp:20-22

头文件以 extern const float 导出这些常量 src/dev_ng/game/Peds/PedMotionData.h:14-25。MBR 是 Vector2:y 为前进分量,x 为横移分量(仅在 strafing 时有意义)。SetCurrentMoveBlendRatio(Vector2&) 在 strafing 时按二维长度钳制,否则只钳制 y PedMotionData.cpp:135-162;MBR 访问器见 PedMotionData.h:264-299。

3.2 每帧钩子#

钩子调用点做什么
ProcessPreTaskTrees任务树运行前 Ped.cpp:31232;克隆 ped 的即时 AI 更新也调用 Ped.cpp:31316记录“上帧玩家是否控制”、清本帧额外朝向/俯仰变化与“期望朝向已设置”标志 PedMotionData.h:220-232
ProcessPostTaskTrees任务树运行后 Ped.cpp:31237清本帧强制状态 m_iStateForcedThisFrame,记录是否蹲伏 PedMotionData.h:235-239
ProcessPostMovementCGameWorld::ProcessAfterAllMovement GameWorld.cpp:2287递减独立 mover 过渡计数,归零时清朝向增量 PedMotionData.h:242-253
StopAllMotion任意当前/期望 MBR 置零、朝向变化率置零 PedMotionData.h:255-260

3.3 成员与状态#

成员块 PedMotionData.h:528-620 包括:当前/期望朝向与俯仰、包围体俯仰/朝向/偏移、当前/期望 MBR(Vector2)、脚本设置的最小/最大 MBR、步态降低(gait reduction)、蹲伏、强制运动状态、覆盖 clip set、飞行状态、移动速率覆盖等。

运动状态枚举 CPedMotionStates::eMotionState 由 parser 生成,值为名字哈希,共 24 个(MotionState_None、Idle、Walk、Run、Sprint、三种 Crouch、DoNothing、AnimatedVelocity、InVehicle、Aiming、Diving_Idle/Swim、Swimming_TreadWater、Dead、三种 Stealth、Parachuting、三种 ActionMode、Jetpack)src/dev_ng/game/Peds/PedMotionState.psc:5-29。使用哈希值意味着脚本/网络可以稳定地引用这些状态,而不依赖枚举顺序(推断:网络同步与脚本命令的具体用法见 17-network-sync、14-script-runtime)。

4. 主运动任务与移动请求#

4.1 选择主运动任务#

每个 ped 模型在 peds.meta 中引用一个运动任务数据集(m_pMotionTaskDataSet),其中可有“陆地”和“水中”两项 sMotionTaskData,每项带 MotionTaskType m_Type 与 clip set 哈希 src/dev_ng/game/task/System/MotionTaskData.h:17-46。CPed::ComputePrimaryMotionTask(bLowLod) 的决策:骑乘(仅 ENABLE_HORSE)→ 游泳且有水中数据 → 否则陆地数据;没有数据集时断言“peds.meta 未定义运动任务数据集” src/dev_ng/game/Peds/Ped.cpp:875-907。

GetMotionTaskFromType 把类型映射到具体任务 Ped.cpp:909-1040:

MotionTaskType创建的任务备注
PED_ON_FOOT、PED_IN_WATERCTaskMotionPed,或低 LOD 时 CTaskMotionPedLowLod在两者之间切换时以 0.25 s 插入网络,并复制旧任务的 on-foot/武器/strafing clip set 与所有有效的 alternate clip Ped.cpp:919-971
PED_ON_MOUNT、HORSE_ON_FOOT、HORSE_SWIMMINGCTaskMotionRideHorse / CTaskMotionAnimal仅 ENABLE_HORSE,GTAV 中关闭(见 ped-subsystems.md)Ped.cpp:973-992
BIRD_ON_FOOT / FLIGHTLESS_BIRD_ON_FOOTCTaskBirdLocomotion / CTaskFlightlessBirdLocomotion使用模型的 movement clip set Ped.cpp:979-985
野猪/牛/美洲狮/郊狼/鹿/猪/鼠/寻回犬/罗威纳/猫/兔CTaskQuadLocomotionPed.cpp:993-1005
FISH_IN_WATERCTaskFishLocomotionPed.cpp:1006-1008

最后统一 SetDefaultClipSets 并回填复制的 clip set Ped.cpp:1014-1037。相关入口:StartPrimaryMotionTask Ped.cpp:6493-6501、CalculateCurrentMotionTask Ped.cpp:6503-6515、带缓存的 GetCurrentMotionTask Ped.cpp:6517-6530。

4.2 低 LOD 运动任务与低 LOD 物理#

AI LOD 管理器会把远处 ped 切换到低 LOD 运动任务/物理(阈值与配额见 ped-intelligence-and-population.md):

  • SetUsingLowLodMotionTask 设置标志、强制运动状态,并且只在模型有低 LOD 运动任务(HasLowLodMotionTask)时重启主运动任务 Ped.cpp:4397-4419。
  • SetUsingLowLodPhysics 切换碰撞、SetUseExtractedZ、FLAG_NEVER_ACTIVATE 并激活/去激活物理实例 Ped.cpp:4299-4366。
  • 低 LOD 物理 ped 在 ProcessPrePhysics 中直接走 SimulatePhysicsForLowLod Ped.cpp:19827-19842。该函数对速度更新做时间切片:按 池索引 & mask == 帧号 & mask 选择更新帧,可见时 mask=3(每 4 帧一次)、不可见时 mask=7(每 8 帧一次),并用 0.25/0.125 缩放角速度避免过冲 Ped.cpp:19281-19330。若设置了 OverridePhysics 重置标志,则直接取动画提取的速度 Ped.cpp:19298-19314。

4.3 ApplyMovementRequests:任务 → 期望速度#

ApplyMovementRequests(fTimestep, bUpdateVelocity, bUpdateAngularVelocity) Ped.cpp:17055-17153:

  1. 用 CPedMotionData 的当前朝向/俯仰构造更新后的 ped 矩阵(ComputeMatrixFromHeadingAndPitch)Ped.cpp:17066-17070。
  2. 让当前运动任务 CalcDesiredAngularVelocity;站在移动物体上时叠加地面角速度,并把地面带来的朝向变化同时加到当前/期望朝向上 Ped.cpp:17080-17118。
  3. 让任务 CalcDesiredVelocity 并 SetDesiredVelocity Ped.cpp:17121-17129。

物理侧随后的 ApplyDesiredVelocity、ProcessPedStanding(地面探测/站立力)分别位于 Ped.cpp:17168、Ped.cpp:16364,在 ProcessPhysics 中的调用见 Ped.cpp:20160、Ped.cpp:20176;其物理细节归 ../09-physics/game-physics-and-ragdoll.md。其他运动相关入口:ProcessLowLodPhysics Ped.cpp:16786、ProcessBounds Ped.cpp:20640、SetIsStrafing Ped.cpp:10323、UpdateMovementMode(动作模式/潜行)Ped.cpp:33906;ped.h 中的移动 API 声明块 src/dev_ng/game/Peds/ped.h:1365-1388。

5. CPed 的动画接口#

CPed 通过一组 CDynamicEntity/CPhysical 的虚函数接入动画系统(fwAnimDirector 与 MoVE 由 10-animation 负责):

接口实现说明
CreateMoveObject返回 CMovePedPooledObject Ped.cpp:32728-32731ped 的 MoVE 参数对象(CMovePed src/dev_ng/game/animation/MovePed.h:7,池化版本 MovePed.h:452)
GetAnimNetworkMoveInfoCClipNetworkMoveInfo::ms_NetworkPed Ped.cpp:32733-32736根 MoVE 网络定义
GetUpdateFrequencyThreshold按 LOD 淡入值返回 0.03 / 0.066 / 0.1 Ped.cpp:32743-32763远处 ped 动画更新频率更低
GetMotionTreePriority本地玩家 2(FPS 预渲染阶段为 3),其他 1 Ped.cpp:32766-32782运动树调度优先级
InitIkRequest / InitExpressionsPed.cpp:32784-32788、Ped.cpp:32790-32827注释要求面部绑定翻译表达式 "faceinit" 必须最先加入;随后按变体组件(每组件 2 个)加入表达式,HEAD/TEEF/BERD 组件与 faceinit 一样以 AddExpression(expr, true) 加入(局部变量名 mid,具体语义由 fwAnimDirector 定义),其余组件为 false
StartAnimUpdate / StartAnimUpdateAfterCamera / EndAnimUpdatePed.cpp:6694-6779都先查 CPedAILod::ShouldUpdateAnimsThisFrame;不更新时停止“通过动画队列更新 AI”;StartAnimUpdate 还处理被睡眠岛唤醒的死亡布娃娃、调用 ProcessVisemes、失效上半身修正 DOF
InstantAnimUpdateStart/EndPed.cpp:31253-31302脚本/切换时立即刷新姿态(以 0 时间步调用 StartAnimUpdate)
ProcessControl_AnimStateUpdatePed.cpp:30520-30547ProcessControl 中的动画状态更新阶段
ProcessControl_AnimationPed.cpp:30591-30616音频手势、语音口型、动作模式注视、次级混合、外部驱动 DOF、受伤 clip set(辅助函数 Ped.cpp:30618-30649)
ProcessVisemes / ProcessBodyAndFacialGesturesEmbeddedInAudioAssetsPed.cpp:8472、Ped.cpp:8066语音资产中嵌入的口型/手势

模型设定时(SetModelId)初始化 IK、创建动画导演并设置面部 clip set Ped.cpp:2869-2882。

5.1 PreRender / PostPreRender#

CPed::PreRender Ped.cpp:23301-23526 的顺序:

  1. UpdateLodState()(第 6 节)Ped.cpp:23307;游戏暂停时只更新伤害优先级/环境缩放后返回 Ped.cpp:23309-23322。
  2. 处理“取消左手握持 IK”请求,然后若设置了 ProcessPreRender2 重置标志,调用任务的 ProcessPreRender2(骨骼更新后、IK 前)Ped.cpp:23335-23361。
  3. 仅在本帧动画更新时:更新 FPS 相机相对矩阵、为 FPS 第二遍缓存输入帧、PreIkUpdate Ped.cpp:23368-23386;更新伤害优先级、应用骨骼覆盖 Ped.cpp:23390-23394。
  4. fwAnimDirector::StartUpdatePreRender,在车内时以车辆的运动树作为父级 Ped.cpp:23416-23431。
  5. 外观参数:按所在 popzone 的污垢颜色插值 ped 污垢色(死亡时加血色)Ped.cpp:23434-23470;玩家按“澳大利亚体感温度”(温度/湿度/风)计算出汗程度,冲刺时加量 Ped.cpp:23472-23494;车内相机时的车内材质模板位、脚步音频 LOD、运动模糊遮罩 Ped.cpp:23497-23515。
  6. FPS 模式下返回 PRERENDER_NEED_SECOND_PASS Ped.cpp:23519-23525。

PostPreRender Ped.cpp:23530-23620:渲染延迟标志会强制 DontRenderThisFrame;调用 CPedAILodManager::UpdatePedVisibility、PostIkUpdate;从动画骨架同步布娃娃根与包围盒;更新脚步辅助器和缓存的骨骼偏移。PreRenderShaders 更新流送 ped 的着色器效果 Ped.cpp:22572-22594;CreateDrawable 见 Ped.cpp:5872-5892。

6. UpdateLodState:几何 LOD、淡入值与组件剔除#

CPed::UpdateLodState Ped.cpp:8641-8908 每帧在 PreRender 开头运行,输出三样东西:m_modelLodIdx(0–3)、写入 CPedVariationData 的 LOD 淡入值、以及 m_hiLodFadeValue 与组件剔除掩码。

6.1 阈值#

阈值默认值(米,未乘缩放)证据
LOD(高→中)15;IG(剧情)ped 25src/dev_ng/game/modelinfo/PedModelInfo.h:73、Ped.cpp:8658-8669
Low LOD30;IG 50PedModelInfo.h:74
Super LOD80;IG 80PedModelInfo.h:75
流送 ped 的 SLOD 钳制40(玩家模型用 80)PedModelInfo.h:77、Ped.cpp:8784-8803
车内 / 玩家车内 / 驾车射击 / 道具15 / 30 / 55 / 35src/dev_ng/game/Peds/PedTuning.cpp:17-20
LOD / SLOD 交叉淡入距离3 / 5PedTuning.cpp:21-22

这些值存于 CPedTuning 的静态成员 PedTuning.cpp:25-34(类声明 src/dev_ng/game/Peds/PedTuning.h:18-50),实际距离均乘以 g_LodScale.GetGlobalScale() * GetLodMultiplier()。特殊规则:

  • MP 中带头部混合且有纹身/贴花的 ped,三个阈值分别 ×1.8/×1.5/×1.2,避免装饰过早消失 Ped.cpp:8693-8700。
  • 驾车射击用 driveby 阈值;车内玩家或与本地玩家同车用玩家车内阈值;其他车内 ped 用车内阈值,但摩托/四轮摩托/水上摩托/敞篷车改用普通阈值(看得见全身)Ped.cpp:8711-8742。
  • 透视(热成像)阶段激活时跳过 superlod,因为 SLOD 不设置该效果所需的着色器数据 Ped.cpp:8762-8763。

6.2 LOD 索引与淡入编码#

  • 距离 > SLOD → 3;> LowLOD → 2;> LOD → 1;否则 0 Ped.cpp:8765-8780。流送 ped 如果超出可用 LOD 数但仍在 SLOD 钳制距离内,则夹到最后一个可用 LOD;只有一个 LOD 时,高模距离被 SLOD 钳制覆盖 Ped.cpp:8782-8803。
  • 淡入值是一个 0–255 的复合编码 Ped.cpp:8805-8834:LOD 3 → 0;LOD 1–2 → 128,或在 SLOD 交叉区内 1–127;LOD 0 → 255,或在 LOD 交叉区内 128–255。因此单个字节同时表达“当前在哪个淡入区段”和“区段内的进度”。
  • m_hiLodFadeValue 仅在 LOD 0 时计算,从高模距离的 25% 处开始衰减(hiLodFadeRatio = 0.75)Ped.cpp:8836-8856。

6.3 组件剔除掩码#

Ped.cpp:8867-8907 按组件生成剔除位:仅高模可见的组件在非高 LOD 时剔除;带 PV_FLAG_NOT_IN_CAR 的组件在车内剔除(摩托和正在下车的 ped 例外);在飞行器内保留飞行员头盔;非高 LOD 时剔除 DECL(贴花)组件。剔除结果进入流送 gfx 的 m_culledComponents(见第 8 节)。GetComponent 取组件 drawable 的入口在 Ped.cpp:8924-8934。

7. CPedDrawHandler:绘制分派#

ped 的变体数据实际存放在渲染侧的 CPedDrawHandler 中(m_varData、m_propData、m_pPedRenderGfx)src/dev_ng/game/renderer/Entities/PedDrawHandler.h:11-44。AddToDrawList PedDrawHandler.cpp:342-540:

  1. 断言该 ped 的模型本身不是 SLOD 模型(IsSuperLodFromIndex)且有变体信息 PedDrawHandler.cpp:359。三个 SLOD 模型 MI_PED_SLOD_HUMAN/SMALL_QUADPED/LARGE_QUADPED 只作为共享代理几何 Ped.cpp:10108-10111。
  2. 不可见或设置了 DontRenderThisFrame 时不绘制——但正在构建 HUD 绘制列表时例外(用于头像/面部照片)PedDrawHandler.cpp:376-387。
  3. 打开原型批处理(g_prototype_batch)时,非流送 ped 使用 g_PedBigPrototype PedDrawHandler.cpp:393-399。
  4. 流送玩家 → CDrawStreamPedDC,在船上禁用“笨重物品”(bulky items)PedDrawHandler.cpp:454-488;流送非玩家 → CDrawStreamPedDC PedDrawHandler.cpp:491-514;打包 ped → CDrawPedBIGDC PedDrawHandler.cpp:515-527。

渲染线程执行时,SLOD 是在 draw 阶段决定的:CEntityDrawDataStreamPed::Draw 在 LOD 索引超出可用 LOD 时(且 SLOD 类型不是 SLOD_KEEP_LOWEST)改调 CPedVariationPack::RenderSLODPed,否则调 CPedVariationStream::RenderPed src/dev_ng/game/renderer/DrawLists/DrawableDataStructs.cpp:1240、DrawableDataStructs.cpp:1332-1343;打包 ped 的 CEntityDrawDataPedBIG::Draw 同理 DrawableDataStructs.cpp:1476、DrawableDataStructs.cpp:1545-1558。

SetPedRenderGfx 替换渲染 gfx 时,对旧 gfx 调 SetUsed(false) 并从 gPopStreaming 的流送变体统计中移除,再登记新 gfx PedDrawHandler.cpp:550-562。

8. 流送 ped:CPedVariationStream#

ped 模型分两类:打包 ped(所有组件 drawable/纹理随模型一起加载,普通路人)与流送 ped(GetIsStreamedGfx(),玩家与 MP 角色等,每个组件单独流送)。流送 ped 支持任意服装组合、头部混合、高清(HD)资产和第一人称替代模型。

8.1 两个扩展对象#

类型职责证据
CPedStreamRequestGfx : fwExtension一次换装请求:各组件 dwd/txd/cloth/头部混合/第一人称替代的 strRequestArray;状态 PS_INIT → PS_REQ → PS_BLEND → PS_USE → PS_FREED;HaveAllLoaded() 要求全部请求就绪且未延迟完成src/dev_ng/game/Peds/rendering/PedVariationStream.h:683-720
CPedStreamRenderGfx : fwExtension当前用于渲染的资源集合:每组件 dwd/txd/HD txd/HD dwd/cloth 索引、头部混合资源索引、第一人称替代索引、骨骼映射、剔除掩码、布料数据、CCustomShaderEffectPed、引用计数与 m_usedPedVariationStream.h:591-675

池(带溢出):请求 gfx 45 个;渲染 gfx 在 Orbis/Durango/PC 上 100 个(注释:为更多 MP 玩家增加),其他平台 42 个 src/dev_ng/game/Peds/rendering/PedVariationStream.cpp:125-131。

8.2 请求 → 应用流程#

图中证据:状态与 PushRequest/SetBlendingStarted/SetBlendingCompleted PedVariationStream.h:685、PedVariationStream.h:709-717;处理逻辑 PedVariationStream.cpp:3394-3459。

  1. 发起:CPedVariationStream::SetVariation PedVariationStream.cpp:2430-2673 校验 blendshape、处理 alternate 变体(CAlternateVariations,最多 MAX_NUM_ALTERNATES = 160 PedVariationStream.h:152)PedVariationStream.cpp:2509-2617,对 FEET/UPPR/LOWR 强制刷新头部混合(皮肤色调需要与头部一致)PedVariationStream.cpp:2619-2645,然后发请求 PedVariationStream.cpp:2647-2668。
  2. 生成文件名并请求:RequestStreamPedFilesInternal PedVariationStream.cpp:994-1120 替换 ped 上已有的请求扩展、与道具请求同步 PedVariationStream.cpp:1031-1037;文件名形如 <流送目录>[_<dlc>]/<槽位名>_<三位编号>_<u|m|r>[<alt>],其中 m 表示“与前一项匹配”、r 表示使用种族纹理,默认 u PedVariationStream.cpp:1083-1095。没有 UGC 权限的本地用户看不到帮派徽标,此时 DECL 组件强制用 0 号 drawable 以免出现灰块(-nosocialclub 的 PC BANK 构建关闭此检查)PedVariationStream.cpp:1045-1060。
  3. 轮询:ProcessStreamRequests 每帧遍历请求池 PedVariationStream.cpp:3374-3392,注册在 "Ped and Vehicle assets" 更新组,紧随其后是道具请求与 MeshBlendManager::Update src/dev_ng/game/core/game.cpp:2769-2773。
  4. 完成:ProcessSingleStreamRequest 在全部加载且同步的道具 gfx 也就绪后:首次完成调用 CPedVariationData::AssetLoadFinished(可能启动头部混合并进入 PS_BLEND;注释说明 NG 上不再需要延迟一帧,因此 return 被 #if 0 掉);混合中则等待 MESHBLENDMANAGER.IsWaitingForAssets;之后 ApplyHdAssets 或 ApplyStreamPedFiles、UpdateAllHeadBlends、必要时 FinalizeHeadBlend,最后从扩展列表销毁请求并处理同步的道具请求 PedVariationStream.cpp:3394-3459。ApplyStreamPedFiles 见 PedVariationStream.cpp:1380-1470。
  5. 分配渲染 gfx:ApplyStreamPedFiles 内部调用 CPedStreamRequestGfx::RequestsComplete PedVariationStream.cpp:1405,后者新建 CPedStreamRenderGfx,池耗尽时打日志 PedVariationStream.cpp:3741-3773;HD 路径不新建渲染 gfx,ApplyHdAssets 取 ped 当前的渲染 gfx 并调用 SetHdAssets 合入 HD 资源 PedVariationStream.cpp:1746-1758、PedVariationStream.cpp:4380-4417。

8.3 渲染线程安全:按 fence 的引用计数#

渲染线程会在主线程已经换装之后仍然绘制上一帧的 gfx,所以 CPedStreamRenderGfx 不能被立即删除:

  • 主线程构建绘制命令时,CEntityDrawDataStreamPed::Init 调 AddRefsToGfx DrawableDataStructs.cpp:1170、DrawableDataStructs.cpp:1214-1233(第一人称道具对象借用本地玩家的 gfx)。AddRefsToGfx 对每个 dwd/txd/cloth/头部混合资源加 REF_RENDER 流送引用并登记到 gDrawListMgr,然后 m_refCount++ 并压入 ms_renderRefs[ms_fenceUpdateIdx] PedVariationStream.cpp:4224-4298。
  • ms_renderRefs 有 FENCE_COUNT 个桶 PedVariationStream.h:669-670,FENCE_COUNT = 4 src/dev_ng/rage/framework/src/fwdrawlist/drawlistmgr.h:51。
  • CDrawListMgr::RemoveAllRefs(假设绘制列表已渲染完)依次调用道具、ped、车辆三类 render gfx 的 RemoveRefs src/dev_ng/game/renderer/DrawLists/drawListMgr.cpp:487-495。RemoveRefs 推进到下一个 fence 桶、对其中每个 gfx 递减计数,再扫描池删除 !m_used && !m_refCount 的条目 PedVariationStream.cpp:4419-4449。
  • SetUsed(false) 时若计数已为 0 立即删除,否则延迟到 RemoveRefs PedVariationStream.cpp:4372-4378。

这表明一个被替换的 gfx 最多在 FENCE_COUNT 轮 RemoveAllRefs 后才释放(推断:调用频率由绘制列表管理器决定,见 ../11-rendering/drawlists-and-render-thread-handoff.md)。

8.4 渲染与第一人称#

CPedVariationStream::RenderPed PedVariationStream.cpp:1960-2060:FPS 视角下剥离头部(镜子/阴影/反射阶段除外),应用剔除掩码,选择第一人称替代 drawable 或 HD drawable。第一人称相关数据(替代模型、时间周期修改、道具动画)来自若干数据文件:sp_first_person/mp_first_person 资产数据、first_person_alternates、tint_data、ped_skin_tones、facial_overlays、pedalternatevariations PedVariationStream.h:31、PedVariationStream.h:61、PedVariationStream.h:86、PedVariationStream.h:358-368、PedVariationStream.h:380。第一人称道具(最多 MAX_FP_PROPS = 3)有一个小状态机 PedVariationStream.h:527-563,由三个更新调用驱动:脚本前 PreScriptUpdateFirstPersonProp、UpdateFirstPersonProp、相机后 UpdateFirstPersonPropFromCamera game.cpp:2856、game.cpp:2902、game.cpp:2964。公共 API(SetVariation、RenderPed、RequestHdAssets、ProcessPlayerModelLoading 等)声明见 PedVariationStream.h:418-470。

9. 打包 ped:CPedVariationPack 与 SLOD#

CPedVariationPack src/dev_ng/game/Peds/rendering/PedVariationPack.h:24-95 处理非流送 ped:组件 drawable 都在模型的 drawable 字典中,选择变体只是改索引。要点:

  • LoadSlods 以 STRFLAG_DONTDELETE 常驻加载三个 SLOD 模型 slod_human、slod_small_quadped、slod_large_quadped,加载失败视为致命 src/dev_ng/game/Peds/rendering/PedVariationPack.cpp:168-188。
  • RenderShaderSetup PedVariationPack.cpp:469、RenderPed PedVariationPack.cpp:587-640、RenderSLODPed PedVariationPack.cpp:980。
  • 变体选择:SetVarDefault PedVariationPack.cpp:1145、SetVariation PedVariationPack.cpp:1355;GetExistingPedVariations/SortTextures/RandomizeTextures 用于让同模型路人之间尽量选不同纹理 PedVariationPack.h:48-50。

10. 头部混合(Head Blend)#

MP 角色创建器的脸由三张“头”(两张父母+一张遗传变化)的几何与纹理混合而成,叠加 13 层覆盖(瑕疵、胡须、眉毛、老化、化妆、腮红、伤害、基础细节、两层皮肤细节、三层身体覆盖)与 20 个微形变(鼻宽、颧骨、下巴……)。

  • 数据:CPedHeadBlendData : fwExtension 挂在 ped 上,包含 m_blendHandle、父母 ped 引用、m_headBlend/m_texBlend/m_varBlend/m_parentBlend2、每层覆盖的 alpha/法线 alpha/色调/渐变类型、微形变权重、调色板 RGB、眼睛颜色、发色等,并可通过 Serialise(CSyncDataBase&) 网络同步 src/dev_ng/game/Peds/rendering/PedVariationDS.h:441-506。
  • 资源槽位:eHeadBlendSlots 定义了头部 drawable、纹理、覆盖纹理、身体(FEET/UPPR/LOWR)皮肤纹理和微形变槽位;eHeadOverlaySlots 共 13 个 PedVariationDS.h:91-165。渲染 gfx 引用这些资源时按槽位区间选择流送模块(drawable 字典 / 纹理字典 / drawable)PedVariationStream.cpp:4284-4294。
  • 执行:CPedVariationData::AssetLoadFinished 在流送完成时调用 MeshBlendManager 的 RequestNewPedHeadBlend/UpdatePedHeadBlend,并处理帮派调色板与发色纹理 src/dev_ng/game/Peds/rendering/PedVariationDS.cpp:313-420。MeshBlendManager(单例 MESHBLENDMANAGER)最多 MAX_NUM_BLENDS = 64 个混合(注释:玩家数×2,用于赛前 corona 中的克隆),最多 321 个活动请求、延迟删除 4 帧、延迟定稿 2 帧 src/dev_ng/game/renderer/MeshBlendManager.h:28-36、MeshBlendManager.h:149、MeshBlendManager.h:480;它的 GPU 混合实现属于渲染侧,未在本页展开。
  • 入口:CPed::SetHeadBlendData Ped.cpp:9330-9351,ped.h 中的头部混合 API 块 ped.h:1942-1964。

11. 道具渲染#

道具与组件类似地分为请求/渲染 gfx:CPedPropRequestGfx 池 50、CPedPropRenderGfx 池 120 src/dev_ng/game/Peds/rendering/PedProps.cpp:70-74;CPedPropsMgr 提供设置/清除/渲染道具的静态接口 src/dev_ng/game/Peds/rendering/PedProps.h:357-420。道具请求可以与组件请求同步(换帽子时同时换发型),由 CPedVariationStream::ProcessSingleStreamRequest 在组件完成后调用 CPedPropsMgr::ProcessSingleStreamRequest PedVariationStream.cpp:3436-3456;渲染引用同样由 CDrawListMgr::RemoveAllRefs 回收 drawListMgr.cpp:490。道具锚点与 CPedPropData 的数据层见 ped-architecture.md。

12. 伤害与装饰:CPedDamageManager / PedDecorationManager#

ped 身上的血迹、刀伤、纹身、伤疤、徽章、污垢并不是世界贴花,而是渲染到每个 ped 专属的“伤害渲染目标”上,再由 ped 着色器采样(世界贴花见 ../12-vfx/decals-and-vfx-systems.md)。

12.1 类型与容量#

概念定义证据
伤害类型血迹、刀伤、皮肤装饰(纹身/淤青)、带法线的皮肤装饰(伤疤)、布料装饰(污垢/护甲损伤)、带法线的布料装饰src/dev_ng/game/Peds/rendering/PedDamage.h:57-69
伤害区躯干、头、左右臂、左右腿、奖章区(共 7;血迹区 6)PedDamage.h:78-90
血迹渲染目标高分辨率 2 个;中分辨率 7+2×4=15;低分辨率 = 中×4(PS3 多 1 组)PedDamage.h:96-101
伤害集合上限kMaxDamagedPeds = 66(注释:略大于 MP 人数×2,竞速中会克隆);压缩集合 64;压缩纹理 32(克隆共享)PedDamage.h:103
目标尺寸 / blit 上限NG(Durango/Orbis/PC)512×1280、血迹 blit 128;旧平台 256×640、63(PS3 顶点批大小限制);装饰 blit 24;自定义纹身 blit 63PedDamage.h:820-828
平台开关PC/Durango 在 GPU 线程压缩装饰;PC 支持多 GPUPedDamage.h:36-37

12.2 类层次与流程#

  • CPedDamageSetBase PedDamage.h:784 → CPedDamageSet(未压缩,持有渲染目标与 blit 列表)PedDamage.h:914;CCompressedPedDamageSet(MP 玩家的装饰被烘焙并压缩成纹理)PedDamage.h:1064-1100;CompressedDecorationBuilder PedDamage.h:1204;管理器 CPedDamageManager PedDamage.h:1400-1654。
  • 分配:AllocateDamageSet 先找空闲槽,否则在未锁定的集合中选“重用分数”最高者抢占;分数基于与相机距离和可见性;MP 网络玩家若使用压缩装饰则跳过装饰目标 src/dev_ng/game/Peds/rendering/PedDamage.cpp:5403-5463。ped 在 PreRender 中每帧 UpdateDamagePriority(见第 5.1 节),对应 UpdatePriority PedDamage.cpp:5571。
  • 调优数据:common:/data/effects/peds/PedDamage.xml(CPedDamageData,结构见 PedDamage.psc)在初始化和 DLC 挂载时加载 PedDamage.cpp:5190、PedDamage.cpp:5316,挂载逻辑见 PedDamage.cpp:5165-5193;细节体积纹理 ped_detail_atlas PedDamage.cpp:8508。
  • 每帧:StartRenderList PedDamage.cpp:6862-6885、PostPreRender PedDamage.cpp:6887-6903、AddToDrawList PedDamage.cpp:6930-6970、按优先级排序 SortPedDamageSets PedDamage.cpp:6981-7040。实际 blit 在独立的渲染阶段 RenderPhasePedDamage 中执行 src/dev_ng/game/renderer/RenderPhases/RenderPhasePedDamage.cpp:39-65;UI/头像中的 ped 由 RenderPhaseStd 特殊处理 src/dev_ng/game/renderer/RenderPhases/RenderPhaseStd.cpp:475-497。

12.3 装饰集合(PedOverlay)#

PedDecorationManager 管理纹身/徽章/奖章“集合”:区域 ePedDecorationZone(与伤害区一一对应)、类型 TATTOO/BADGE/MEDAL src/dev_ng/game/Peds/rendering/PedOverlay.h:46-64。网络同步用位域表达一个玩家拥有的装饰:NUM_DECORATION_BITFIELDS = 56,即 56×32 = 1792 位 PedOverlay.h:33-34;管理器类与位域 API 见 PedOverlay.h:351-420,预设上限 kMaxUniquePresets 见 PedOverlay.h:442。集合文件通过 CDataFileMgr::PED_OVERLAY_FILE 挂载接口加载(支持 DLC)src/dev_ng/game/Peds/rendering/PedOverlay.cpp:747、PedOverlay.cpp:758-762。

13. 头像:PedHeadshotManager#

头像(暂停菜单、联系人、MP 玩家列表中的小脸)通过离屏渲染 ped 得到:

  • 容量:34 张头像,其中 7 张高分辨率;尺寸 128×128 src/dev_ng/game/Peds/rendering/PedHeadshotManager.h:455-458。在 PC/Durango/Orbis 上渲染目标常驻(PHM_RT_RESIDENT)PedHeadshotManager.h:42。
  • 状态:头像状态 PedHeadshotManager.h:62-69、条目信息 PedHeadshotManager.h:71-93;透明背景头像由 PedHeadshotTextureBuilder 的状态机生成 PedHeadshotManager.h:97-115,管理器主体 PedHeadshotManager.h:450-540。
  • 还存在一个基于 CloudListener 的导出器 PedHeadshotTextureExporter,用于把头像纹理上传到云 PedHeadshotManager.h:218-222;其服务端细节不在本页范围(在线服务见 ../16-network-online/rline-online-services.md)。
  • 时序:UpdateSafe 注册为游戏更新调用 game.cpp:2750(实现 src/dev_ng/game/Peds/rendering/PedHeadshotManager.cpp:2115-2140);PreRender PedHeadshotManager.cpp:2605、AddToDrawList PedHeadshotManager.cpp:2618、渲染线程侧 RenderUpdate/RunCompression PedHeadshotManager.cpp:2701-2720。头像 ped 能在 DontRenderThisFrame 时仍被绘制,正是依赖第 7 节的 HUD 绘制列表例外。

14. 约束与边界行为#

  • PedMoveBlend 不是运行路径:阅读运动代码应从 CTaskMotionPed/CTaskMotionBase 入手,而不是 Peds/PedMoveBlend(第 2 节)。
  • 池耗尽:流送渲染 gfx(100/42)、请求 gfx(45)、道具 gfx(120/50)都是带溢出(spillover)的池;渲染 gfx 分配失败时 RequestsComplete 只打日志 PedVariationStream.cpp:3741-3773。伤害集合 66 个满时抢占重用分数最高的未锁定集合;全部锁定则返回 kInvalidPedDamageSet 并在 BANK 构建警告 PedDamage.cpp:5455-5462。
  • 渲染延迟释放:替换服装后旧资源会在若干 fence 周期内保持 REF_RENDER 引用(第 8.3 节),短时间大量换装会放大流送内存占用。
  • 热成像:透视阶段禁用 SLOD,远处 ped 会保持 LOD 2 而不是 SLOD(第 6.1 节)。
  • FPS 双遍:FPS 模式下本地玩家 PreRender 需要第二遍,GetMotionTreePriority 也在 FPS 预渲染阶段提高到 3(第 5 节)。
  • 调试:CPedVariationDebug 仅在 __BANK 编译 src/dev_ng/game/Peds/rendering/PedVariationDebug.h:11;伤害系统提供 -peddamagedebug、-noncompressedpeddamagedebug、-debugpeddamagetargets 命令行参数 PedDamage.cpp:75-78。

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

  • CMoveBlendRatioSpeeds::GetMoveSpeed 的优先级缺陷对游戏行为的实际影响(停止距离估算等)未做数值验证。
  • MeshBlendManager 的 GPU 混合/压缩实现、CCustomShaderEffectPed 的着色器变量,以及 RenderPhasePedDamage 的具体 blit 着色器,属于渲染侧,本页只给出接口。
  • PedDamage.xml、pedalternatevariations、第一人称数据文件的实际内容未查看;文中只描述其结构与加载点。
  • CPedAILod 的动画时间切片自适应公式(决定 ShouldUpdateAnimsThisFrame)见 ped-intelligence-and-population.md,其中也标注了未验证部分。