IsItABlueGem LogoIsItABlueGem
Увійти

Технічні Деталі

Освітня та історична документація про те, як працює система, що підтримує цей сайт

Counter-Strike 2 та Counter Strike Global Offensive використовують одну базову текстуру (наприклад, Case Hardened) для генерації сотень унікальних варіантів скінів через систему рандомізованих трансформацій. Це досягається за допомогою детермінованого механізму на основі сіду — ключ до розуміння та відтворення патернів Blue Gem поза грою.

Чому це називається Paint "Seed"?

Для генерації різноманітності текстур послідовним, але рандомізованим способом, Valve використовує псевдовипадковий рівномірний генератор, вбудований у Source Engine. Оскільки комп'ютери є детермінованими машинами, вони потребують початкового значення — сіду — для створення випадковості. У CS2 кожному скіну призначається paint seed від 1 до 999. Цей сід ініціалізує генератор випадкових чисел, який потім створює певний набір значень для застосування трансформацій до текстури.

Оскільки цей алгоритм є детермінованим і однаковим на всіх платформах, заданий paint seed завжди дасть ту саму трансформацію — гарантуючи, що скін виглядає ідентично на кожній системі.

TL;DR: Paint seed ініціалізує псевдовипадковий генератор Valve для визначення позиції, обертання та масштабу базової текстури для шарів патерну, зносу та бруду.

Як обчислюються трансформації текстур?

Щоб зрозуміти, як CS2 рандомізує вигляд скінів, корисно розуміти UV-розгортку: кожна модель зброї має UV-макет, який визначає, як 2D-текстура обгортається навколо 3D-сітки. Valve застосовує текстуру до цієї UV-карти, а потім використовує paint seed для рандомізації її переміщення, обертання та масштабу.

Це впливає не лише на основний патерн (як кольорові плями Case Hardened), але й на накладки зносу та бруду — текстури, які додають подряпини, старіння та забруднення.

Якщо стиль скіну не використовує випадкове зміщення (наприклад, можливо, однотонні скіни як Asiimov), він не буде підпадати під цю систему.

Концепція UV-розгортки

Наведіть на грані куба або UV-карту, щоб побачити, як 3D-поверхні відображаються на 2D-координати текстури

3D Model

1
2
3
4
5
6

UV Map (Unfolded)

5
4
1
3
6
2

Покроково: Paint Seed → Трансформація

1. Визначення базового масштабу

Кожна зброя та стиль фарбування мають певний масштаб, зазвичай визначений у файлі items_game.txt від Valve. Базовий масштаб обчислюється з довжини зброї та UV-масштабу, залежно від стилю фарбування:

if (paint_style == 3 || paint_style == 6) {
    scale = weapon_length * 0.027777778;
} else {
    scale = uv_scale;
}

Наприклад, Karambit має:

  • WeaponLength = 9.813000
  • UVScale = 0.438000
  • → що дає базовий масштаб 0.438

2. Генерація випадкових значень

Після визначення базового масштабу, paint seed використовується для генерації 11 псевдовипадкових float-значень, які визначають, як застосовується кожен шар текстури:

Шар текстуриПараметри
Патернмасштаб, зміщенняX, зміщенняY, обертання
Зносмасштаб × множник, зміщенняX, зміщенняY, обертання
Брудмасштаб × множник, зміщенняX, зміщенняY, обертання

Ці float-значення генеруються у фіксованому порядку та діапазонах:

Патерн
  • 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

Ці значення застосовуються незалежно до кожного шару за такою логікою:

Порядок трансформації: Масштаб → Зміщення → Обертання
(Повторюється для патерну, зносу та бруду)

Приклад: Karambit | Case Hardened — Paint Seed 633

Використовуючи наведену вище логіку, paint seed 633 на Karambit дає:

  • 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

Найранніша спроба зворотної інженерії цієї системи прийшла з відомого посту на Reddit від Step7750 у 2016 році. Цей пост виклав основну ідею роботи трансформацій на основі сіду — і роками він слугував де-факто довідкою для спільноти.

Згодом сайти як Broskins та csfloat.com створили робочі системи попереднього перегляду — але фактична логіка, яку використовує Valve, залишалася недокументованою для публіки.

Це змінилося на початку 2024 року, коли pattern.wiki опублікував повний розбір процесу трансформації — включаючи конкретні значення, математику та візуальні приклади. Це був значний крок вперед.

Що ми уточнили

Хоча реалізація pattern.wiki чітко працює на практиці, їхній опис порядку трансформації та точки обертання не повністю відповідав поведінці Source під капотом. Через тестування та налагодження ми уточнили кілька тонких (але важливих) моментів:

  • Всі трансформації застосовуються навколо початку координат (0, 0) — не після зміщення
  • Ефективний порядок матриці: T₂ × R × S × T₁

Застосування трансформації шаблону (фіналізована модель)

Щоб взяти зразок правильної області текстури за допомогою paint seed, ми застосовуємо послідовність афінних трансформацій — кожна з яких модифікує UV-координати перед взяттям зразка базової текстури.

Покроковий ланцюг трансформацій

1. Центроване зміщення

Зміщення патерну за допомогою зсувів paint seed, але центрування трансформації навколо початку UV:

translate(offsetX - 0.5, offsetY - 0.5)
2. Масштаб (навколо початку)

Рівномірне масштабування патерну за допомогою базового масштабу — навколо початку (0, 0):

scale(scaleX, scaleY)
3. Обертання (навколо початку)

Обертання патерну проти годинникової стрілки від початку:

rotate(rotation)
4. Додатковий зсув (фінальне налаштування)

Після обертання та масштабу застосувати фінальний зсув, який компенсує обертання на основі кута:

invScale = 0.5 / scale
angle = -rotationDeg * (π / 180)
extraX = invScale * cos(angle) - invScale * sin(angle)
extraY = (extraX * sin(angle)) + (invScale * cos(angle))
translate(extraX, extraY)

Фінальна послідовність матриць

В афінних термінах це створює повну матрицю трансформації:

A = T₂ × R × S × T₁

Або простою мовою:

  1. Центрування UV-зсуву
  2. Застосування масштабу
  3. Обертання навколо початку
  4. Корекція додатковим зсувом

Фінальні трансформовані UV потім обгортаються за допомогою модуля:

uv_final = (A × uv_coords) % 1.0

Як зношеність генерується на основі float

Вищі значення float роблять скін більш зношеним на вигляд. Ми відтворюємо це так само, як це робить Counter-Strike — накладаючи три текстури поверх патерну Case Hardened у лінійному просторі освітлення, керуючись значенням float предмета. При float 0 (Factory New) ви бачите чистий патерн; при float 1 (Battle-Scarred) метал приглушений, затемнений і зернистий.

Базовий патерн

Патерн Case Hardened із кольоровим градієнтом (синій / золотий / фіолетовий), розміщений тим самим перетворенням за сідом, описаним вище.

Карта зносу

Карта у відтінках сірого, яка визначає, які частини леза зношуються першими — виступаючі краї та відкриті ділянки раніше за захищені.

Карта бруду

Карта дрібного бруду та крапок, яка додає реалістичну нерівність поверхні та забруднення зі зростанням float.

Значення float керує всім ефектом через єдиний параметр. Перетягуючи його від 0 до 1, ви поступово знебарвлюєте, затемнюєте та забруднюєте патерн, зберігаючи його видимим — Case Hardened старіє, змінюючи колір і набираючи бруду, а не стирається до голого металу.

worn = compose(base, wear, grunge, AO, float) // float 0 = Factory New … 1 = Battle-Scarred
Karambit Case Hardened патерн 387, blue gem першого тиру
Прямо з заводу · флоат 0.000

Той самий рушій зносу на основі флоата, наживо — цей Karambit #387 від Прямо з заводу до Загартованого в боях.

Зернистість ('Maserung') походить від геометрії зброї

Кожна зброя має запечену карту порожнин / ambient occlusion. Знос і бруд концентруються у западинах і вздовж країв, що й створює характерну плямисту зернистість, яку ви бачите на лезі Battle-Scarred. Кожен ніж використовує власну AO-карту, тож патерн зносу унікальний для форми саме цієї зброї.

  1. Розмістіть патерн Case Hardened, карту зносу та карту бруду, використовуючи перетворення за сідом для кожного патерну.
  2. Використовуйте значення float, щоб визначити, наскільки сильно зношується кожен піксель, зважено за картою порожнин / AO зброї.
  3. Знебарвте та затемніть зношені ділянки до тону бруду й додайте дрібний бруд із карти бруду.
  4. Скомпонуйте все в лінійному просторі освітлення та показуйте наживо на 3D-моделі, коли ви тягнете повзунок.

Це точна математика з власного фінішного шейдера Counter-Strike, відновлена та відтворена — тож зношений вигляд, який ви переглядаєте, відповідає тому, як предмет насправді виглядає у грі.

Щоб краще зрозуміти цей підхід, ви можете погратися з нашою студією трансформації текстур, яка застосовує саме ці трансформації в реальному часі у вашому власному браузері.