模型、动画与粒子
角色、武器与道具都是“工作室模型”(.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::ComputeLOD | GetLOD() 在 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 |
客户端快速路径 ComputeModelLODs | RTT 阴影模式用最低 LOD;CSTRIKE15 下 nLOD = 0 并经 SlamModelLODs 夹到 [m_RootLOD, 最大非阴影 LOD];否则按屏幕尺寸计算 | game/client/modelrendersystem.cpp:505-541、game/client/modelrendersystem.cpp:484-499 |
| 根 LOD | r_rootlod(0 到 MAX_NUM_LODS-1)决定加载的最高细节级别,以上各路径都不会低于它 | engine/l_studio.cpp:83 |
含义(推断):CS:GO 中模型细节不随距离变化,只受根 LOD 影响。源码注释只说明“总是把 r_lod 固定为 0”,没有给出原因。
5. Flex(顶点动画 / 表情)#
- 权重来源:
C_BaseFlex::SetupWeights在模型没有 flex 控制器时清零权重;有缓存时直接复制缓存值;否则处理场景事件、建立本地到全局 flex 控制器的映射,把网络同步的m_flexWeight(0..1)缩放到各控制器的[min, max],再叠加场景事件与眨眼:game/client/c_baseflex.cpp:1301-1370。 - 调用点:逐实体路径中,
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。 - 应用到顶点: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)、粒子渲染器与各算子实现。