技术洞察
关于本站系统工作原理的教育和历史文档
《反恐精英2》和《反恐精英:全球攻势》使用单一基础纹理(如硬化皮肤)通过随机变换系统生成数百种独特外观的皮肤变体。这是通过确定性的种子机制实现的——这是理解和在游戏外重现蓝宝石图案的关键。
为什么叫涂装"种子"?
为了以一致但随机的方式生成纹理多样性,Valve使用了内置于Source引擎的伪随机均匀生成器。由于计算机是确定性机器,它们需要一个初始值——种子——来产生随机性。在CS2中,每个皮肤都被分配了一个1到999之间的涂装种子。这个种子初始化随机数生成器,然后产生一组特定的值,用于对纹理应用变换。
因为这个算法是确定性的,并且在所有平台上都是统一的,给定的涂装种子总是会产生相同的变换——确保皮肤在每个系统上看起来都一样。
简而言之:涂装种子初始化Valve的伪随机生成器,以定义基础纹理如何为图案、磨损和污垢层进行定位、旋转和缩放。
纹理变换是如何计算的?
要理解CS2如何随机化皮肤外观,有助于理解UV映射:每个武器模型都有一个UV布局,定义了2D纹理如何包裹到3D网格上。Valve将纹理应用到这个UV贴 图上,然后使用涂装种子来随机化其平移、旋转和缩放。
这不仅影响主要图案(如硬化皮肤的颜色斑块),还影响磨损和污垢叠加层——添加划痕、老化和污垢的纹理。
如果皮肤的样式不使用随机位移(例如,可能像Asiimov这样的纯色皮肤),它就不会受到这个系统的影响。
UV 映射概念
将鼠标悬停在立方体面或 UV 图上,查看 3D 表面如何映射到 2D 纹理坐标
3D Model
UV Map (Unfolded)
分步说明:涂装种子 → 变换
1. 确定基础缩放
每种武器和涂装样式都有特定的缩放比例,通常在Valve的items_game.txt中定义。基础缩放是根据武器长度和UV缩放计算的,具体取决于涂装样式:
if (paint_style == 3 || paint_style == 6) {
scale = weapon_length * 0.027777778;
} else {
scale = uv_scale;
}例如,爪子刀有:
- WeaponLength = 9.813000
- UVScale = 0.438000
- → 得出基础缩放为0.438
2. 生成随机值
一旦知道基础缩放,涂装种子就用于生成11个伪随机浮点值,这些值定义了每个纹理层的应用方式:
| 纹理层 | 参数 |
|---|---|
| 图案 | 缩放、X平移、Y平移、旋转 |
| 磨损 | 缩放 × 倍数、X平移、Y平移、旋转 |
| 污垢 | 缩放 × 倍数、X平移、Y平移、旋转 |
这些浮点数按固定顺序和范围生成:
图案
- translateX → 0.0 – 1.0
- translateY → 0.0 – 1.0
- rotate → 0.0 – 360.0
磨损
- scaleMult → 1.6 – 1.8
- translateX → 0.0 – 1.0
- translateY → 0.0 – 1.0
- rotate → 0.0 – 360.0
污垢
- scaleMult → 1.6 – 1.8
- translateX → 0.0 – 1.0
- translateY → 0.0 – 1.0
- rotate → 0.0 – 360.0
这些值通过以下逻辑独立应用于每一层:
变换顺序:缩放 → 平移 → 旋转(对图案、磨损和污垢重复)示例:爪子刀 | 硬化皮肤 — 涂装种子 633
使用上述逻辑,爪子刀上的涂装种子633产生:
- Base Scale: 0.438
- Translate X: 0.471 → shifts the pattern ~47% left
- Translate Y: 0.624 → shifts the pattern ~62% up
- Rotation: 96.7° counterclockwise
磨损和污垢层获得它们自己的值,也是从种子派生的,但使用不同的倍数和角度。
剩余的谜团:旋转中心
很长一段时间以来,有一个问题仍然存在:旋转轴心到底在哪里?虽然<180°的角度似乎围绕UV的左上角旋转,但在尝试在游戏外模拟结果时,更高的角度表现不一致。
从Reddit到pattern.wiki
逆向工程这个系统的最早尝试来自Step7750在2016年发布的著名Reddit帖子。那篇帖子阐述了基于种子的变换如何工作的基本思想——多年来,它一直是社区的事实参考。
最终,像Broskins和csfloat.com这样的网站建立了可工作的预览系统——但Valve使用的实际逻辑仍然没有公开记录。
这在2024年初发生了变化,当时pattern.wiki发布了变换过程的完整分解——包括具体值、数学和视觉示例。这是一个重大飞跃。
我们澄清的内容
虽然pattern.wiki的实现在实践中明显有效,但他们对变换顺序和旋转轴心的描述并没有完全符合Source引擎的底层行为。通过测试和调试,我们澄清了几个微妙(但重要)的点:
- 所有变换都围绕原点(0, 0)应用——而不是在平移之后
- 有效的矩阵顺 序是:T₂ × R × S × T₁
模板变换应用(最终模型)
要使用涂装种子采样纹理的正确区域,我们应用一系列仿射变换——每个变换在采样基础纹理之前修改UV坐标。
分步变换链
1. 中心平移
使用涂装种子的偏移量移动图案,但将变换围绕UV原点居中:
translate(offsetX - 0.5, offsetY - 0.5)2. 缩放(围绕原点)
使用基础缩放均匀缩放图案——围绕原点(0, 0):
scale(scaleX, scaleY)3. 旋转(围绕原点)
从原点逆时针旋转图案:
rotate(rotation)4. 额外偏移(最终调整)
在旋转和缩放之后,应用最终偏移来补偿基于角落的旋转:
invScale = 0.5 / scaleangle = -rotationDeg * (π / 180)extraX = invScale * cos(angle) - invScale * sin(angle)extraY = (extraX * sin(angle)) + (invScale * cos(angle))translate(extraX, extraY)最终矩阵序列
用仿射术语来说,这创建了一个完整的变换矩阵:
A = T₂ × R × S × T₁或者用通俗的话说:
- 居中UV偏移
- 应用缩放
- 围绕原点旋转
- 用额外偏移校正
然后使用模运算包裹最终变换的UV:
uv_final = (A × uv_coords) % 1.0磨损度如何驱动磨损效果的生成
磨损度越高,皮肤看起来越破旧。我们采用与《反恐精英》完全相同的方式重现这一效果——在 Case Hardened 图案之上,于线性光照空间叠加三层纹理,并由物品的磨损度驱动。磨损度为 0(崭新出厂)时,你看到的是干净的图案;磨损度为 1(战痕累累)时,金属变得暗淡、发黑且布满颗粒。
基础图案
经过颜色渐变映射的 Case Hardened 图案(蓝/金/紫),由上文所述的同一种子变换进行定位。
磨损贴图
一张灰度贴图,决定刀身的哪些部位最先磨损——凸起的边缘和外露区域会先于受保护的部位磨损。
污垢贴图
一张精细的污渍与斑点贴图,随着磨损度上升,为表面增添真实的破损感与污垢。
磨损度通过单一参数驱动整个效果。将其从 0 拖到 1,会逐步使图案去饱和、变暗并沾染污垢,同时仍保持图案可见——Case Hardened 的老化体现为颜色偏移和污垢增多,而不会磨损到露出裸金属。
worn = compose(base, wear, grunge, AO, float) // float 0 = Factory New … 1 = Battle-Scarred
同一套基于磨损值的引擎,实时运行——将这把爪子刀 #387 从崭新出厂扫到战痕累累。
颗粒纹理("Maserung")源自武器的几何结构
每把武器都有一张烘焙好的凹腔/环境光遮蔽(AO)贴图。磨损与污垢会集中在缝隙和边缘处,这正是战痕累累的刀身上那种标志性斑驳颗粒纹理的来源。每把刀都使用自己的 AO 贴图,因此磨损分布与该武器的形状一一对应、独一无二。
- 使用每个图案各自的种子变换,定位 Case Hardened 图案、磨损贴图和污垢贴图。
- 用磨损度决定每个像素的磨损强度,并以武器的凹腔/AO 贴图进行加权。
- 将磨损区域向污垢色调去饱和并加深,再从污垢贴图叠加精细污渍。
- 在线性光照空间中合成所有图层,并在你拖动滑块时于 3D 模型上实时显示。
这正是从《反恐精英》自身的涂层着色器中还原并复现的精确数学逻辑——因此你预览到的磨损外观与该物品在游戏中的实际呈现完全一致。
为了更好地理解这种方法,您可以使用我们的纹理变换工作室,在您自己的浏览器中实时应用这些变换。