Ped 运动与渲染:运动数据、运动任务、动画接口、LOD、变体流送、头部混合、伤害与头像
本页覆盖 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 节)。
背景与相关页面#
CPed的整体结构、每帧处理顺序、CPedVariationData/CPedPropData数据层:见 ped-architecture.md。- AI LOD(
CPedAILod,决定动画是否本帧更新、是否用低 LOD 运动任务/物理):见 ped-intelligence-and-population.md。 - 运动任务本身(
CTaskMotionPed、CTaskMotionBase、移动任务):见 ../07-ai-tasks/task-catalogue.md 与 ../07-ai-tasks/task-system.md。 - MoVE 网络、
fwAnimDirector、IK 管理器、表达式:见 ../10-animation/rage-animation-and-move.md 与 ../10-animation/game-animation-and-ik.md。 - 布娃娃与 ped 物理胶囊:见 ../09-physics/game-physics-and-ragdoll.md。
- 绘制列表与渲染线程交接、
FENCE_COUNT:见 ../11-rendering/drawlists-and-render-thread-handoff.md;ped 着色器(CCustomShaderEffectPed、ped*.fx):见 ../11-rendering/shaders-and-materials.md;渲染阶段:见 ../11-rendering/render-pipeline-and-phases.md。 - 世界贴花与 VFX(与 ped 身上的血迹/纹身不同的系统):见 ../12-vfx/decals-and-vfx-systems.md。
CPedModelInfo、流送 ped 模型、peds.meta:见 ../05-world/model-info.md;人口流送(gPopStreaming):见 ../05-world/game-streaming-and-scene-streamer.md。
相关源码文件#
| 文件 | 作用 |
|---|---|
src/dev_ng/game/Peds/PedMoveBlend/* | 遗留目录;仅 PedMoveBlendBase.h 有实际内容(CMoveBlendRatioSpeeds/CMoveBlendRatioTurnRates) |
src/dev_ng/game/Peds/PedMotionData.h、PedMotionData.cpp、PedMotionState.psc | CPedMotionData、MBR 常量、eMotionState |
src/dev_ng/game/task/System/MotionTaskData.h | MotionTaskType、sMotionTaskData(由 peds.meta 的运动任务数据集引用) |
src/dev_ng/game/Peds/Ped.cpp、ped.h | 运动任务选择、ApplyMovementRequests、动画接口、PreRender、UpdateLodState |
src/dev_ng/game/Peds/PedTuning.h、PedTuning.cpp | LOD 距离阈值 |
src/dev_ng/game/renderer/Entities/PedDrawHandler.h/.cpp | CPedDrawHandler::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/.cpp | ped 头像渲染与压缩 |
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.cpp | 0 字节 |
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(纯虚)与GetTurnRatessrc/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 / 3 | src/dev_ng/game/Peds/PedMotionData.cpp:13-18 |
MBR_WALK_BOUNDARY / MBR_RUN_BOUNDARY / MBR_SPRINT_BOUNDARY | 0.1 / 1.5 / 2.5 | PedMotionData.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 |
ProcessPostMovement | CGameWorld::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_WATER | CTaskMotionPed,或低 LOD 时 CTaskMotionPedLowLod | 在两者之间切换时以 0.25 s 插入网络,并复制旧任务的 on-foot/武器/strafing clip set 与所有有效的 alternate clip Ped.cpp:919-971 |
PED_ON_MOUNT、HORSE_ON_FOOT、HORSE_SWIMMING | CTaskMotionRideHorse / CTaskMotionAnimal | 仅 ENABLE_HORSE,GTAV 中关闭(见 ped-subsystems.md)Ped.cpp:973-992 |
BIRD_ON_FOOT / FLIGHTLESS_BIRD_ON_FOOT | CTaskBirdLocomotion / CTaskFlightlessBirdLocomotion | 使用模型的 movement clip set Ped.cpp:979-985 |
| 野猪/牛/美洲狮/郊狼/鹿/猪/鼠/寻回犬/罗威纳/猫/兔 | CTaskQuadLocomotion | Ped.cpp:993-1005 |
FISH_IN_WATER | CTaskFishLocomotion | Ped.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中直接走SimulatePhysicsForLowLodPed.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:
- 用
CPedMotionData的当前朝向/俯仰构造更新后的 ped 矩阵(ComputeMatrixFromHeadingAndPitch)Ped.cpp:17066-17070。 - 让当前运动任务
CalcDesiredAngularVelocity;站在移动物体上时叠加地面角速度,并把地面带来的朝向变化同时加到当前/期望朝向上Ped.cpp:17080-17118。 - 让任务
CalcDesiredVelocity并SetDesiredVelocityPed.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-32731 | ped 的 MoVE 参数对象(CMovePed src/dev_ng/game/animation/MovePed.h:7,池化版本 MovePed.h:452) |
GetAnimNetworkMoveInfo | CClipNetworkMoveInfo::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 / InitExpressions | Ped.cpp:32784-32788、Ped.cpp:32790-32827 | 注释要求面部绑定翻译表达式 "faceinit" 必须最先加入;随后按变体组件(每组件 2 个)加入表达式,HEAD/TEEF/BERD 组件与 faceinit 一样以 AddExpression(expr, true) 加入(局部变量名 mid,具体语义由 fwAnimDirector 定义),其余组件为 false |
StartAnimUpdate / StartAnimUpdateAfterCamera / EndAnimUpdate | Ped.cpp:6694-6779 | 都先查 CPedAILod::ShouldUpdateAnimsThisFrame;不更新时停止“通过动画队列更新 AI”;StartAnimUpdate 还处理被睡眠岛唤醒的死亡布娃娃、调用 ProcessVisemes、失效上半身修正 DOF |
InstantAnimUpdateStart/End | Ped.cpp:31253-31302 | 脚本/切换时立即刷新姿态(以 0 时间步调用 StartAnimUpdate) |
ProcessControl_AnimStateUpdate | Ped.cpp:30520-30547 | ProcessControl 中的动画状态更新阶段 |
ProcessControl_Animation | Ped.cpp:30591-30616 | 音频手势、语音口型、动作模式注视、次级混合、外部驱动 DOF、受伤 clip set(辅助函数 Ped.cpp:30618-30649) |
ProcessVisemes / ProcessBodyAndFacialGesturesEmbeddedInAudioAssets | Ped.cpp:8472、Ped.cpp:8066 | 语音资产中嵌入的口型/手势 |
模型设定时(SetModelId)初始化 IK、创建动画导演并设置面部 clip set Ped.cpp:2869-2882。
5.1 PreRender / PostPreRender#
CPed::PreRender Ped.cpp:23301-23526 的顺序:
UpdateLodState()(第 6 节)Ped.cpp:23307;游戏暂停时只更新伤害优先级/环境缩放后返回Ped.cpp:23309-23322。- 处理“取消左手握持 IK”请求,然后若设置了
ProcessPreRender2重置标志,调用任务的ProcessPreRender2(骨骼更新后、IK 前)Ped.cpp:23335-23361。 - 仅在本帧动画更新时:更新 FPS 相机相对矩阵、为 FPS 第二遍缓存输入帧、
PreIkUpdatePed.cpp:23368-23386;更新伤害优先级、应用骨骼覆盖Ped.cpp:23390-23394。 fwAnimDirector::StartUpdatePreRender,在车内时以车辆的运动树作为父级Ped.cpp:23416-23431。- 外观参数:按所在 popzone 的污垢颜色插值 ped 污垢色(死亡时加血色)
Ped.cpp:23434-23470;玩家按“澳大利亚体感温度”(温度/湿度/风)计算出汗程度,冲刺时加量Ped.cpp:23472-23494;车内相机时的车内材质模板位、脚步音频 LOD、运动模糊遮罩Ped.cpp:23497-23515。 - FPS 模式下返回
PRERENDER_NEED_SECOND_PASSPed.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 25 | src/dev_ng/game/modelinfo/PedModelInfo.h:73、Ped.cpp:8658-8669 |
| Low LOD | 30;IG 50 | PedModelInfo.h:74 |
| Super LOD | 80;IG 80 | PedModelInfo.h:75 |
| 流送 ped 的 SLOD 钳制 | 40(玩家模型用 80) | PedModelInfo.h:77、Ped.cpp:8784-8803 |
| 车内 / 玩家车内 / 驾车射击 / 道具 | 15 / 30 / 55 / 35 | src/dev_ng/game/Peds/PedTuning.cpp:17-20 |
| LOD / SLOD 交叉淡入距离 | 3 / 5 | PedTuning.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:
- 断言该 ped 的模型本身不是 SLOD 模型(
IsSuperLodFromIndex)且有变体信息PedDrawHandler.cpp:359。三个 SLOD 模型MI_PED_SLOD_HUMAN/SMALL_QUADPED/LARGE_QUADPED只作为共享代理几何Ped.cpp:10108-10111。 - 不可见或设置了
DontRenderThisFrame时不绘制——但正在构建 HUD 绘制列表时例外(用于头像/面部照片)PedDrawHandler.cpp:376-387。 - 打开原型批处理(
g_prototype_batch)时,非流送 ped 使用g_PedBigPrototypePedDrawHandler.cpp:393-399。 - 流送玩家 →
CDrawStreamPedDC,在船上禁用“笨重物品”(bulky items)PedDrawHandler.cpp:454-488;流送非玩家 →CDrawStreamPedDCPedDrawHandler.cpp:491-514;打包 ped →CDrawPedBIGDCPedDrawHandler.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_used | PedVariationStream.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。
- 发起:
CPedVariationStream::SetVariationPedVariationStream.cpp:2430-2673校验 blendshape、处理 alternate 变体(CAlternateVariations,最多MAX_NUM_ALTERNATES = 160PedVariationStream.h:152)PedVariationStream.cpp:2509-2617,对 FEET/UPPR/LOWR 强制刷新头部混合(皮肤色调需要与头部一致)PedVariationStream.cpp:2619-2645,然后发请求PedVariationStream.cpp:2647-2668。 - 生成文件名并请求:
RequestStreamPedFilesInternalPedVariationStream.cpp:994-1120替换 ped 上已有的请求扩展、与道具请求同步PedVariationStream.cpp:1031-1037;文件名形如<流送目录>[_<dlc>]/<槽位名>_<三位编号>_<u|m|r>[<alt>],其中m表示“与前一项匹配”、r表示使用种族纹理,默认uPedVariationStream.cpp:1083-1095。没有 UGC 权限的本地用户看不到帮派徽标,此时 DECL 组件强制用 0 号 drawable 以免出现灰块(-nosocialclub的 PC BANK 构建关闭此检查)PedVariationStream.cpp:1045-1060。 - 轮询:
ProcessStreamRequests每帧遍历请求池PedVariationStream.cpp:3374-3392,注册在"Ped and Vehicle assets"更新组,紧随其后是道具请求与MeshBlendManager::Updatesrc/dev_ng/game/core/game.cpp:2769-2773。 - 完成:
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。 - 分配渲染 gfx:
ApplyStreamPedFiles内部调用CPedStreamRequestGfx::RequestsCompletePedVariationStream.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调AddRefsToGfxDrawableDataStructs.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 = 4src/dev_ng/rage/framework/src/fwdrawlist/drawlistmgr.h:51。CDrawListMgr::RemoveAllRefs(假设绘制列表已渲染完)依次调用道具、ped、车辆三类 render gfx 的RemoveRefssrc/dev_ng/game/renderer/DrawLists/drawListMgr.cpp:487-495。RemoveRefs推进到下一个 fence 桶、对其中每个 gfx 递减计数,再扫描池删除!m_used && !m_refCount的条目PedVariationStream.cpp:4419-4449。SetUsed(false)时若计数已为 0 立即删除,否则延迟到RemoveRefsPedVariationStream.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。RenderShaderSetupPedVariationPack.cpp:469、RenderPedPedVariationPack.cpp:587-640、RenderSLODPedPedVariationPack.cpp:980。- 变体选择:
SetVarDefaultPedVariationPack.cpp:1145、SetVariationPedVariationPack.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::SetHeadBlendDataPed.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 63 | PedDamage.h:820-828 |
| 平台开关 | PC/Durango 在 GPU 线程压缩装饰;PC 支持多 GPU | PedDamage.h:36-37 |
12.2 类层次与流程#
CPedDamageSetBasePedDamage.h:784→CPedDamageSet(未压缩,持有渲染目标与 blit 列表)PedDamage.h:914;CCompressedPedDamageSet(MP 玩家的装饰被烘焙并压缩成纹理)PedDamage.h:1064-1100;CompressedDecorationBuilderPedDamage.h:1204;管理器CPedDamageManagerPedDamage.h:1400-1654。- 分配:
AllocateDamageSet先找空闲槽,否则在未锁定的集合中选“重用分数”最高者抢占;分数基于与相机距离和可见性;MP 网络玩家若使用压缩装饰则跳过装饰目标src/dev_ng/game/Peds/rendering/PedDamage.cpp:5403-5463。ped 在PreRender中每帧UpdateDamagePriority(见第 5.1 节),对应UpdatePriorityPedDamage.cpp:5571。 - 调优数据:
common:/data/effects/peds/PedDamage.xml(CPedDamageData,结构见PedDamage.psc)在初始化和 DLC 挂载时加载PedDamage.cpp:5190、PedDamage.cpp:5316,挂载逻辑见PedDamage.cpp:5165-5193;细节体积纹理ped_detail_atlasPedDamage.cpp:8508。 - 每帧:
StartRenderListPedDamage.cpp:6862-6885、PostPreRenderPedDamage.cpp:6887-6903、AddToDrawListPedDamage.cpp:6930-6970、按优先级排序SortPedDamageSetsPedDamage.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);PreRenderPedHeadshotManager.cpp:2605、AddToDrawListPedHeadshotManager.cpp:2618、渲染线程侧RenderUpdate/RunCompressionPedHeadshotManager.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,其中也标注了未验证部分。