Szczegóły Techniczne
Dokumentacja edukacyjna i historyczna dotycząca działania systemu obsługującego tę stronę
Counter-Strike 2 i Counter Strike Global Offensive używają pojedynczej tekstury bazowej (jak Case Hardened) do generowania setek unikalnie wyglądających wariantów skinów poprzez system losowych transformacji. Osiąga się to za pomocą deterministycznego mechanizmu opartego na ziarnie — klucz do zrozumienia i odtworzenia wzorów Blue Gem poza grą.
Dlaczego nazywa się to Paint "Seed"?
Aby generować różnorodność tekstur w spójny, ale losowy sposób, Valve wykorzystuje pseudolosowy generator równomierny wbudowany w silnik Source. Ponieważ komputery są maszynami deterministycznymi, wymagają wartości początkowej — ziarna — do produkcji losowości. W CS2 każdemu skinowi przypisywane jest paint seed od 1 do 999. To ziarno inicjalizuje generator liczb losowych, który następnie produkuje określony zestaw wartości używanych do zastosowania transformacji do tekstury.
Ponieważ ten algorytm jest deterministyczny i jednolity na wszystkich platformach, dane paint seed zawsze da tę samą transformację — zapewniając, że skin wygląda identycznie na każdym systemie.
TL;DR: Paint seed inicjalizuje pseudolosowy generator Valve, aby określić, jak tekstura bazowa jest pozycjonowana, obracana i skalowana dla warstw wzoru, zużycia i brudu.
Jak są obliczane transformacje tekstur?
Aby zrozumieć, jak CS2 losuje wygląd skinów, pomocne jest zrozumienie mapowania UV: każdy model broni ma układ UV, który definiuje, jak tekstura 2D owija się wokół siatki 3D. Valve nakłada teksturę na tę mapę UV, a następnie używa paint seed do losowania jej translacji, rotacji i skali.
Wpływa to nie tylko na główny wzór (jak kolorowe plamy Case Hardened), ale także na nakładki zużycia i brudu — tekstury dodające zarysowania, starzenie i brud.
Jeśli styl skina nie używa losowego przemieszczenia (np. prawdopodobnie jednokolorowe skiny jak Asiimov), nie będzie podlegał temu systemowi.
Koncepcja mapowania UV
Najedź na ściany sześcianu lub mapę UV, aby zobaczyć, jak powierzchnie 3D są mapowane na współrzędne tekstury 2D
3D Model
UV Map (Unfolded)
Krok po kroku: Paint Seed → Transformacja
1. Określenie skali bazowej
Każda broń i styl malowania ma określoną skalę, zwykle zdefiniowaną w pliku items_game.txt Valve. Skala bazowa jest obliczana z długości broni i skali UV, w zależności od stylu malowania:
if (paint_style == 3 || paint_style == 6) {
scale = weapon_length * 0.027777778;
} else {
scale = uv_scale;
}Na przykład, Karambit ma:
- WeaponLength = 9.813000
- UVScale = 0.438000
- → co daje skalę bazową 0.438
2. Generowanie wartości losowych
Po określeniu skali bazowej, paint seed jest używany do generowania 11 pseudolosowych wartości float, które definiują, jak każda warstwa tekstury jest stosowana:
| Warstwa tekstury | Parametry |
|---|---|
| Wzór | skala, translacjaX, translacjaY, rotacja |
| Zużycie | skala × mnożnik, translacjaX, translacjaY, rotacja |
| Brud | skala × mnożnik, translacjaX, translacjaY, rotacja |
Te wartości float są generowane w stałej kolejności i zakresach:
Wzór
- translateX → 0.0 – 1.0
- translateY → 0.0 – 1.0
- rotate → 0.0 – 360.0
Zużycie
- scaleMult → 1.6 – 1.8
- translateX → 0.0 – 1.0
- translateY → 0.0 – 1.0
- rotate → 0.0 – 360.0
Brud
- scaleMult → 1.6 – 1.8
- translateX → 0.0 – 1.0
- translateY → 0.0 – 1.0
- rotate → 0.0 – 360.0
Te wartości są stosowane niezależnie do każdej warstwy według następującej logiki:
Kolejność transformacji: Skala → Translacja → Rotacja(Powtarzane dla wzoru, zużycia i brudu)Przykład: Karambit | Case Hardened — Paint Seed 633
Używając powyższej logiki, paint seed 633 na Karambit daje:
- 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
Warstwy zużycia i brudu otrzymują własne wartości, również pochodzące z ziarna, ale z różnymi mnożnikami i kątami.
Pozostająca zagadka: Środek rotacji
Przez długi czas pozostawało jedno pytanie: gdzie dokładnie znajduje się punkt obrotu? Podczas gdy kąty <180° wydawały się obracać wokół lewego górnego rogu UV, wyższe kąty zachowywały się niespójnie przy próbie symulacji wyniku poza grą.
Od Reddit do pattern.wiki
Najwcześniejsza próba inżynierii wstecznej tego systemu pochodziła ze słynnego posta na Reddit od Step7750 w 2016 roku. Ten post przedstawił podstawową ideę działania transformacji opartych na ziarnie — i przez lata służył jako de facto odniesienie dla społeczności.
Ostatecznie strony takie jak Broskins i csfloat.com zbudowały działające systemy podglądu — ale faktyczna logika używana przez Valve pozostawała nieudokumentowana publicznie.
To zmieniło się na początku 2024 roku, kiedy pattern.wiki opublikował pełną analizę procesu transformacji — w tym konkretne wartości, matematykę i przykłady wizualne. To był znaczący krok naprzód.
Co wyjaśniliśmy
Chociaż implementacja pattern.wiki wyraźnie działa w praktyce, ich opis kolejności transformacji i punktu obrotu nie w pełni odpowiadał zachowaniu Source pod maską. Poprzez testowanie i debugowanie wyjaśniliśmy kilka subtelnych (ale ważnych) punktów:
- Wszystkie transformacje są stosowane wokół początku układu (0, 0) — nie po translacji
- Efektywna kolejność macierzy to: T₂ × R × S × T₁
Aplikacja transformacji szablonu (sfinalizowany model)
Aby pobrać próbkę właściwego obszaru tekstury za pomocą paint seed, stosujemy sekwencję transformacji afinicznych — każda modyfikuje współrzędne UV przed pobraniem próbki tekstury bazowej.
Krok po kroku łańcuch transformacji
1. Wyśrodkowana translacja
Przesunięcie wzoru przy użyciu przesunięć paint seed, ale wyśrodkowanie transformacji wokół początku UV:
translate(offsetX - 0.5, offsetY - 0.5)2. Skala (wokół początku)
Jednolite skalowanie wzoru przy użyciu skali bazowej — wokół początku (0, 0):
scale(scaleX, scaleY)3. Rotacja (wokół początku)
Obracanie wzoru przeciwnie do ruchu wskazówek zegara od początku:
rotate(rotation)4. Dodatkowe przesunięcie (ostateczna korekta)
Po rotacji i skali, zastosuj ostateczne przesunięcie, które kompensuje rotację opartą na narożniku:
invScale = 0.5 / scaleangle = -rotationDeg * (π / 180)extraX = invScale * cos(angle) - invScale * sin(angle)extraY = (extraX * sin(angle)) + (invScale * cos(angle))translate(extraX, extraY)Ostateczna sekwencja macierzy
W kategoriach afinicznych tworzy to pełną macierz transformacji:
A = T₂ × R × S × T₁Lub prostym językiem:
- Wyśrodkuj przesunięcie UV
- Zastosuj skalę
- Obróć wokół początku
- Skoryguj dodatkowym przesunięciem
Ostateczne przekształcone UV są następnie zawijane przy użyciu modulo:
uv_final = (A × uv_coords) % 1.0Jak generowane jest zużycie sterowane wartością float
Wyższe wartości float sprawiają, że skin wygląda na bardziej zużyty. Odtwarzamy to dokładnie tak, jak robi to Counter-Strike — łącząc trzy tekstury na wzorze Case Hardened w przestrzeni liniowej, sterowane wartością float przedmiotu. Przy float 0 (Factory New) widzisz czysty wzór; przy float 1 (Battle-Scarred) metal jest stłumiony, przyciemniony i ziarnisty.
Wzór bazowy
Wzór Case Hardened z paletą kolorów (niebieski / złoty / fioletowy), umieszczony za pomocą tej samej transformacji seed opisanej powyżej.
Mapa zużycia
Mapa w skali szarości, która decyduje, które części ostrza zużywają się pierwsze — wystające krawędzie i odsłonięte obszary przed tymi chronionymi.
Mapa brudu
Drobna mapa brudu i nakrapiania, która dodaje realistyczne zniszczenia powierzchni i zabrudzenia w miarę wzrostu wartości float.
Wartość float steruje całym efektem za pomocą jednego parametru. Przeciąganie jej od 0 do 1 stopniowo odsyca, przyciemnia i zabrudza wzór, zachowując go widocznym — Case Hardened starzeje się poprzez zmianę koloru i nabieranie brudu, nie ściera się aż do gołego metalu.
worn = compose(base, wear, grunge, AO, float) // float 0 = Factory New … 1 = Battle-Scarred
Ten sam silnik zużycia sterowany floatem, na żywo — ten Karambit #387 od Fabrycznie nowego do Zniszczonego w boju.
Ziarno ('Maserung') pochodzi z geometrii broni
Każda broń ma wypaloną mapę wnęk / ambient occlusion. Zużycie i brud koncentrują się w szczelinach i wzdłuż krawędzi, co tworzy charakterystyczne cętkowane ziarno widoczne na ostrzu w stanie battle-scarred. Każdy nóż używa własnej mapy AO, więc wzór zużycia jest unikalny dla kształtu danej broni.
- Umieść wzór Case Hardened, mapę zużycia i mapę brudu za pomocą transformacji seed przypisanych do wzoru.
- Użyj wartości float, aby określić, jak mocno zużywa się każdy piksel, ważąc to mapą wnęk / AO broni.
- Odsyć i przyciemnij zużyte obszary w kierunku tonu brudu i dodaj drobny brud z mapy brudu.
- Połącz wszystko w przestrzeni liniowej i wyświetl na żywo na modelu 3D podczas przeciągania suwaka.
To dokładnie ta sama matematyka z własnego shadera wykończeń Counter-Strike, odzyskana i odtworzona — więc zużyty wygląd, który podglądasz, odpowiada temu, jak przedmiot faktycznie wygląda w grze.
Aby lepiej zrozumieć to podejście, możesz pobawić się naszym studiem transformacji tekstur, które stosuje dokładnie te transformacje na żywo w twojej przeglądarce.