CD Code Digest Hub /CS:GO

模型、动画与粒子

13 分钟阅读 7 个章节 1 张图

角色、武器与道具都是“工作室模型”(.mdl 及其附属文件)。绘制一个模型需要三个模块协作:datacache 按需加载并缓存模型数据,bonesetup 计算骨骼姿态,studiorender 进行蒙皮与提交绘制;粒子特效则由 particles 模块按 DMX 定义模拟与渲染。

相关页面:渲染管线总览 · 客户端视图渲染管线 · 引擎世界渲染(静态道具) · 资源与缓存 · DataModel 与 DMX

相关源码文件#

  • 客户端实体:game/client/c_baseanimating.cpp
  • 引擎模型渲染:engine/l_studio.cpp
  • 骨骼:bonesetup/(bone_setup.cpp、bone_ik.cpp、bone_decode.cpp 等)
  • 模型渲染:studiorender/studiorendercontext.cpp
  • 粒子:particles/particles.cpp
  • 缓存:datacache/mdlcache.cpp
  • 快速路径与 flex:game/client/modelrendersystem.cpp、game/client/c_baseflex.cpp、studiorender/r_studioflex.cpp、studiorender/r_studiodraw.cpp

1. 绘制一个动画模型#

证据:

  • 客户端实体先 DrawModelSetup 再 DrawModelExecute,两者都是引擎 IVModelRender 的方法:game/client/c_baseanimating.cpp:5132、game/client/c_baseanimating.cpp:4986;引擎实现 CModelRender::DrawModelExecute 最终调用 g_pStudioRender->DrawModel:engine/l_studio.cpp:2491、engine/l_studio.cpp:2674。
  • SetupBones 在禁止访问骨骼的阶段会节流告警,然后计算姿态:game/client/c_baseanimating.cpp:2983-3000。StandardBlendingRules 构造 IBoneSetup 并 InitPose,随后叠加序列、层与姿态参数:game/client/c_baseanimating.cpp:2112-2158、game/client/c_baseanimating.cpp:2104、game/client/c_baseanimating.cpp:1989;BuildTransformations 把局部姿态转为骨骼矩阵:game/client/c_baseanimating.cpp:1627。
  • CStudioRenderContext 以 IStudioRender 暴露,提供单模型、模型数组与静态道具的绘制入口:studiorender/studiorendercontext.cpp:151、studiorender/studiorendercontext.cpp:2458、studiorender/studiorendercontext.cpp:2558、studiorender/studiorendercontext.cpp:2611、studiorender/studiorendercontext.cpp:2701。
  • GetStudioHdr 在缓存中找不到模型头时进入临界区并同步加载;开启 mod_lock_mdls_on_load 时会把模型头强制锁定在缓存中:datacache/mdlcache.cpp:2935-2990。

骨骼设置有多线程/作业化路径(源码中出现 g_bInThreadedBoneSetup 与 PS3 骨骼作业 g_pBoneJobs):game/client/c_baseanimating.cpp:2985、game/client/c_baseanimating.cpp:4089-4091。

服务端的 CBaseAnimating 也需要骨骼(命中盒检测、延迟补偿),这是专用服务器仍加载 studiorender、datacache 的原因之一(推断,依据 专用服务器模块表)。

2. 粒子系统#

  • CParticleSystemMgr::Init 初始化管理器与属性表:particles/particles.cpp:3569、particles/particles.cpp:3620;客户端在初始化游戏系统时初始化粒子管理器:game/client/cdll_client_int.cpp:1673。
  • 粒子定义以 DMX 文件存储:ReadParticleDefinitions 用 dmxloader 的 UnserializeDMX 读入,根元素类型为 DmeParticleSystemDefinition 时加入字典并按需预缓存:particles/particles.cpp:3957-3975;配置文件读取入口:particles/particles.cpp:4137-4146。
  • 每个粒子集合 CParticleCollection 有 Simulate 与 Render:particles/particles.cpp:2361、particles/particles.cpp:2083。
  • 全帧更新到来时,客户端会移除所有新生成的效果,避免实体重建时残留:game/client/cdll_client_int.cpp:3432-3437。

3. 客户端不透明模型快速路径(CModelRenderSystem)#

第 1 节的 DrawModelSetup/DrawModelExecute 是“逐实体”路径。不透明阶段在 cl_modelfastpath(默认 1)开启时,把具备 IClientModelRenderable 的实体与静态道具交给 g_pModelRenderSystem->DrawModels 批量处理(调用方见 客户端视图渲染管线 第 4.3 节):game/client/viewrender.cpp:5736-5753、game/client/modelrendersystem.cpp:183。

CModelRenderSystem::DrawModels 的步骤:按 MDL 分桶 → 按依赖排序并按光照模型分组 → 计算 LOD → 按 body/LOD/skin 排序 → 批量骨骼 → 批量 flex(RTT 阴影模式跳过)→ 光照(含静态道具颜色网格)→ 投影光与贴花 → 每实例颜色调制 → RenderModels;结束后释放静态光照状态与临时骨骼矩阵:game/client/modelrendersystem.cpp:1498-1584。cl_skipfastpath 非零时整个快速路径不绘制:game/client/modelrendersystem.cpp:1500-1501。

4. LOD:CS:GO 固定使用最高细节#

路径行为证据
引擎逐模型 CModelRender::ComputeLODGetLOD() 在 CSTRIKE15 下恒返回 0(否则读 r_lod,-1 表示按屏幕尺寸自动);强制 LOD 时若 info.flags 带 STUDIOHDR_FLAGS_HASSHADOWLOD 则不超过倒数第二级;最终夹到 [m_RootLOD, m_NumLODs-1]engine/l_studio.cpp:2737-2791
引擎静态道具数组 DrawStaticPropArrayFast同样取 GetLOD();非 CS:GO 才允许 r_staticprop_lod 覆盖;强制 LOD 时夹到根 LOD 与最大 LOD 之间engine/l_studio.cpp:3276-3282、engine/l_studio.cpp:3336-3364
客户端快速路径 ComputeModelLODsRTT 阴影模式用最低 LOD;CSTRIKE15 下 nLOD = 0 并经 SlamModelLODs 夹到 [m_RootLOD, 最大非阴影 LOD];否则按屏幕尺寸计算game/client/modelrendersystem.cpp:505-541、game/client/modelrendersystem.cpp:484-499
根 LODr_rootlod(0 到 MAX_NUM_LODS-1)决定加载的最高细节级别,以上各路径都不会低于它engine/l_studio.cpp:83

含义(推断):CS:GO 中模型细节不随距离变化,只受根 LOD 影响。源码注释只说明“总是把 r_lod 固定为 0”,没有给出原因。

5. Flex(顶点动画 / 表情)#

  1. 权重来源:C_BaseFlex::SetupWeights 在模型没有 flex 控制器时清零权重;有缓存时直接复制缓存值;否则处理场景事件、建立本地到全局 flex 控制器的映射,把网络同步的 m_flexWeight(0..1)缩放到各控制器的 [min, max],再叠加场景事件与眨眼:game/client/c_baseflex.cpp:1301-1370。
  2. 调用点:逐实体路径中,DrawModelExecute 在未设置 STUDIO_SKIP_FLEXES 且模型有 flex 描述时,从渲染上下文分配临时缓冲并调用 SetupWeights:engine/l_studio.cpp:2497、engine/l_studio.cpp:2506-2524;快速路径的 SetupFlexes 对每个有 flex 的模型做同样的事(g_CV_FlexSmooth 决定是否需要延迟权重):game/client/modelrendersystem.cpp:732-756。
  3. 应用到顶点:studiorender 在网格组带 MESHGROUP_IS_DELTA_FLEXED 且配置启用 flex 时,调用 R_StudioFlexVerts 在 CPU 上把 flex 增量叠加到顶点缓存(每个网格只计算一次;顶点数据被压缩或 flex 未转换为定点格式时告警):studiorender/r_studiodraw.cpp:2355、studiorender/r_studiodraw.cpp:2396-2400、studiorender/r_studioflex.cpp:196-240。

约束与边界#

  • 模型文件(.mdl/.vvd/.vtx/.phy)由 studiomdl 离线编译,不在仓库中(见 内容工具链)。
  • CS:GO 构建中 LOD 固定为 0(受根 LOD 约束),r_lod、r_staticprop_lod 对 CS:GO 无效(第 4 节)。
  • 快速路径与逐实体路径并存:没有 IClientModelRenderable 的实体、画刷实体和 NPC 仍走慢速路径(game/client/viewrender.cpp:5853-5986)。
  • 静态道具的加载、可见性注册与绘制分派见 引擎世界渲染;其烘焙光照格式未展开。
  • 未展开:硬件 morph(pMorphInternal->Bind)与 flex 的 GPU 路径、眼球/眼睑 flex 细节(r_studioflex.cpp 前半部分)、布料/软体、模型合并(mdlcombine.cpp)、细分曲面(r_studiosubd*.cpp)、粒子渲染器与各算子实现。