# CS2 3D viewer

`https://woksteamapi.com/cs2-3d-viewer` is WOK's embeddable, browser-based
viewer for **prepared CS2 weapon models and supported composite materials**. It uses the CS2 installation
on the WOK host; the visitor does not install the game or a plugin. The
viewer itself does not spend WOK API credits or call a Steam proxy.

The 2026-10-03 catalog covers **all 2,029 weapon/knife paint pairs in WOK's
current inventory-picture and finish catalogs**, with browser-side float and
seed composition. That coverage does not include wearable/glove models or
new paints added by a later game update before their assets are rebuilt.

## Access and branding

An active WOK key is required. Free keys include **10 item openings per 30-day
key period**. Paid plans include viewer access without a monthly view cap and
custom branding without an additional branding subscription. Viewer opens do
not spend ordinary API credits. A separate per-minute opening limit follows the
key's rate limit, capped at 1,500.

Open **Account → CS2 3D · views and branding** on the required subscription, or
visit `/account/viewer`. The panel shows remaining views and renewal date,
configures allowed origins, uploads a logo/watermark/background (PNG/JPEG/WebP,
up to 10 MiB each), changes colours/theme and generates an iframe.

For server-side integration:

```bash
curl 'https://woksteamapi.com/v1/cs2/viewer-session' \
  -H "Authorization: Bearer $WOK_API_KEY" -H 'Content-Type: application/json' \
  -d '{"def_index":7,"paint_index":282,"float_value":0.21,"paint_seed":582}'
```

The JSON response contains `viewer_url`, `expires_at` and `access`. A URL is an
item-bound session valid for up to 30 minutes. Reopening/reloading it, rotating,
changing float/seed with sliders and saving PNG do not spend another view.
Opening another item spends one view. Invalid or unavailable items are rejected
before charging the viewer allowance. The monthly API quota is independent.

`GET /v1/cs2/viewer` reads access/settings; `PUT /v1/cs2/viewer` saves
`{"origins":["https://example.com"],"settings":{"brand_name":"Example"}}`.
Branding is available to paid keys. Use `"rotate":true` to replace the public
viewer key and revoke existing viewer sessions; it does not reset usage.

## Open and embed

Open a prepared base model by item definition index:

```text
https://woksteamapi.com/cs2-3d-viewer?def_index=7
```

For a modern self-contained CS2 inspect link, URL-encode the link as the
`inspect_link` parameter. The viewer reads the item definition, paint index,
float, seed and applied attachment IDs from the link. It selects the matching
composite material when available, otherwise a prepared preview or base model. For example, to
preview the AK-47 Redline game texture without an inspect link:

```text
https://woksteamapi.com/cs2-3d-viewer?def_index=7&paint_index=282
```

Add `float_value=0.63&paint_seed=321` to set item parameters without an inspect
link. Float also selects the inventory picture's wear tier. Prepared material
recipes control the browser-side 3D preview; available effects depend on the
pipeline reported in `viewer_visual_accuracy`.
For recipes marked `game_shader_preview`, the browser executes a WebGL2
adaptation of the composite pixel program extracted from the installed CS2
shader package. The selected static features, texture colour spaces, paint
parameters, colour and roughness/metalness passes are preserved. Float and
seed control the material; the float slider follows the paint kit's wear range.
An explicitly supplied float outside that range is preserved, and the control
range expands to include it. The original kit range remains visible in the item
details. Slider values are not rounded to a fixed step before rendering.
The on-page sliders recalculate the preview from already downloaded
maps; changing it creates no new server asset and spends no API credits.

`POST /v1/cs2/inspect` returns `item.visuals` for decoded links. It includes
`viewer_url`, `inventory_icon_url`, `inventory_icon_wear_tier`, `model_url`,
`model_visual_accuracy`, `viewer_visual_accuracy`, and
`exact_item_rendered=false`. `viewer_composites_seed` and
`viewer_composites_surface_finish` identify recipes using the new pipeline.
`model_url` continues to name the static colour-preview GLB or base model;
`viewer_visual_accuracy=uv_wear_preview` means the iframe additionally
composites wear in the browser. An unavailable
asset URL is `null`; clients should display the inventory icon as an
illustration, and should not present the model as the exact inspected item.
Known `stickers[]` and `keychains[]` also include a self-hosted `image_url`
when their game inventory artwork is available. These images show the
unattached sticker or charm, not its position on the weapon.

To embed the viewer:

```html
<iframe
  src="https://woksteamapi.com/cs2-3d-viewer?viewer_key=YOUR_VIEWER_KEY&amp;def_index=7&amp;embed=1"
  width="100%" height="600" loading="lazy" referrerpolicy="origin"
  title="WOK CS2 3D viewer"></iframe>
```

The page supports mouse, touch, zoom and browser-side PNG capture of the
current view. Register up to 16 exact HTTPS origins in the panel. Iframes send an origin referrer and are restricted by Content-Security-Policy. Use the separate `wv_…` public viewer key in embeds, never the main API secret. Direct item URLs without a viewer session display the sign-in/key entry page. Other WOK
pages remain frame-denied.

Paste an inspect link into the opening field, or expand **Open by parameters**
to enter a weapon ID, paint index, float and seed. The camera reset button
restores the initial angle and zoom. **About this preview** describes the
current render's limitations; the authenticated embed snippet is available in `/account/viewer`.

The page first displays a picture extracted from the installed CS2 game
files. When the requested weapon/paint pair has an inventory picture, the
viewer picks its light, medium or heavy wear tier from the decoded float.
The image and model are shown one at a time; the image remains visible while
the model and its material load. When a painted 3D preview is unavailable, the
skin picture opens at full stage size and the user can switch to the base 3D
model. The save button downloads the picture as WebP in picture mode, or
captures the rendered model as PNG in 3D mode. This is an official game inventory illustration
for a **wear tier**, not a render of that exact float, seed or attachments.
When no finish picture exists, the base weapon poster is used. Base models use
a smaller GLB on mobile. Prepared finish previews use the compact model on
all devices to keep the payload practical. The picture remains visible if
WebGL cannot load, with an error message and a retry button. The page varies by User-Agent
and sends `Vary: User-Agent` so caches do not mix mobile and desktop HTML.

## Visual accuracy and availability

`game_shader_preview` is the preferred pipeline for compatible recipes. It
uses the game's composite program and material data, including seeded wear
and grunge transforms. The random stream was compared against the installed
game library for seeds 0–1000 (11,011 identical draws). This establishes the
random generator's compatibility, **not** complete visual equivalence with the
game. A same-item comparison against the game renderer is still required.
The material builder resolves both legacy and modern composite assemblies,
including template overrides, conditional parameters and separate colour and
surface passes. Published per-paint normal and ambient-occlusion overrides are
applied to the model. Procedural coatings also use the game's position maps.
Game lighting, pearlescence, applied stickers, charms and StatTrak are not fully
reproduced. An unavailable or newly introduced assembly still retains the
colour/wear preview or inventory-picture fallback below.

Only model IDs present in the generated manifest are served. Unknown or
unfinished IDs show an explicit unavailable state.

### Earlier fallback pipelines

The following pipelines remain available when no composite recipe exists;
they are not the rendering path for the 2,029 pairs covered above. The offline finish builder
maps CS2 `paint_kits` and weapon combinations from `items_game.txt` to
`vcompmat`/`vmat` sources in the installed VPK. Custom Paint Job (`style=7`)
and Gunsmith (`style=9`) kits with UV-space colour textures are exported as
their own GLBs. Every preview is marked `uv_colour_preview` in the local
catalogue. The game colour texture is visible on the 3D model, but this is
**not an exact per-instance render**. For Custom Paint recipes the browser
blends game pattern, weapon colour and packed mask, wear and grunge maps for
the supplied float. The calculation follows the installed game's Custom
Texture shader where available, but random seed rolls, exact normal and
roughness outputs, stickers, charms and StatTrak are not reproduced. Float,
seed and attachment IDs next to other models are decoded item data only.
Other unsupported paint styles open the inventory picture and allow switching
to the base model. Inventory pictures cover additional paint styles,
knives and wear tiers; they do not turn the base 3D model into a painted one.

The existing `POST /v1/cs2/screenshot` remains an inspect-data PNG card.
The viewer's browser PNG button captures the currently displayed 3D preview,
including its composited material when available. Neither
output should be represented as a game-rendered screenshot of the exact item.

## Asset update procedure

Game files live in `/var/lib/wok-cs2/game` under the separate `wok-cs2` user.
The Source 2 Viewer CLI lives in `/var/lib/wok-cs2/tools`. The builder reads
`items_game.txt` and VPK data, then atomically publishes model GLBs and a
manifest in `/var/lib/wok-cs2/assets`:

```sh
runuser -u wok-cs2 -- python3 /opt/wok-api/tools/build_cs2_models.py --only 7
runuser -u wok-cs2 -- python3 /opt/wok-api/tools/build_cs2_models.py
runuser -u wok-cs2 -- /var/lib/wok-cs2/tools/Source2Viewer-CLI \
  -i /var/lib/wok-cs2/game/game/csgo/pak01_dir.vpk \
  -f panorama/images/econ/weapons/base_weapons/ \
  -o /var/lib/wok-cs2/assets/poster-export -d --threads 4
runuser -u wok-cs2 -- python3 /opt/wok-api/tools/build_cs2_mobile_assets.py
```

The first command limits extraction to AK-47 for a small smoke run. The
second exports all weapon models for which CS2's `items_game.txt` has a
supported model path. Files are emitted outside Git and never exposed through
a raw directory listing. Updating CS2 assets is an operator job, not an HTTP
request.

After updating the game files, regenerate the base GLBs, then rerun the poster
and mobile builder. It rebuilds compact assets when their source files change.
Then regenerate finish previews and reusable material maps as the dedicated
CS2 user:

```sh
runuser -u wok-cs2 -- python3 /opt/wok-api/tools/build_cs2_paint_names.py
runuser -u wok-cs2 -- python3 /opt/wok-api/tools/build_cs2_finishes.py --force
runuser -u wok-cs2 -- python3 /opt/wok-api/tools/build_cs2_material_bundle.py --force
runuser -u wok-cs2 -- python3 /opt/wok-api/tools/build_cs2_finish_icons.py --force
runuser -u wok-cs2 -- python3 /opt/wok-api/tools/build_cs2_attachment_icons.py --force
```

`--only 282` limits the operation to the Redline paint kit. Omit `--force`
to fill only missing previews; include it after a game asset update. The builder publishes
one GLB per compatible weapon/paint pair and updates `finishes/catalog.json`
atomically. It runs offline; HTTP requests only read prepared files and never
invoke Steam, a third-party renderer or a live VPK extraction job.
The material builder reads the existing finish catalogue and extracts each
unique pattern and weapon map once. It currently prepares Custom Paint kits
with a compatible compiled weapon material and skips unsupported layouts.
Float and seed values never create stored GLBs or textures: the browser
composites from shared maps when a specific preview opens. `--only 7 282`
limits material extraction to AK-47 Redline. The icon builder extracts the game's three inventory illustrations per
available weapon/paint pair and publishes WebP files with an atomic catalog.
The attachment builder extracts sticker and charm inventory artwork, keyed by
CS2 definition ID, into a separate atomic catalog.

### Composite shader bundles

```sh
runuser -u wok-cs2 -- python3 /opt/wok-api/tools/build_cs2_shader_bundle.py --only 7 282
runuser -u wok-cs2 -- nice -n 10 python3 /opt/wok-api/tools/build_cs2_shader_bundle.py
```

This requires the patched Source 2 Viewer exporter; see
`ops/CS2_GAME_MATERIALS_2026-10-03.md` for its source build and
`tools/cs2-vrf-shader-export.patch` for shader reflection support. The expanded
builder uses `/var/lib/wok-cs2/tools/Source2Viewer-assembly/Source2Viewer-CLI`.
Build the private texture worker against that same library version:

```sh
DOTNET_ROOT=/var/lib/wok-cs2/tools/dotnet \
  /var/lib/wok-cs2/tools/dotnet/dotnet build \
  /opt/wok-api/tools/cs2-texture-worker/TextureWorker.csproj -c Release \
  -o /var/lib/wok-cs2/tools/texture-worker
```

The offline Python builder needs Pillow and NumPy; its isolated NumPy location
is `/var/lib/wok-cs2/tools/python`. Two persistent texture workers share the
build workload, reading pixels directly from VPK and selecting native mip levels
where available. No texture worker runs inside the HTTP service.

The bundle directory is `/var/lib/wok-cs2/assets/shader-materials`. Recipes,
programs, geometry and lossless texture maps use content hashes. One weapon
body is shared by its paints. No float/seed combinations are persisted.
Position maps retain half-float channels in `.bin` assets with precompressed
`.bin.gz` companions; nginx `gzip_static` reduces their transfer size.
The builder enforces a **4 GiB** directory budget by default (`--max-bytes`),
publishes its catalogue atomically and reports unsupported assemblies.
Reaching the budget stops new asset publication; it does not delete models or
affect existing API data. Updating float/seed in the viewer uses no network
requests or API credits after the initial material has loaded.

The viewer selects only published recipes. Static asset URLs are immutable;
updating the game creates new hashes, so clients never mix a new recipe with
an old map cached under the same URL. The builder records the source VPK hash
per pair, invalidates its texture cache when that source changes and resumes
interrupted updates without marking older pairs as regenerated.

Build into a staging asset tree before replacing the production catalogue.
The following audit checks coverage against all existing inventory-picture and
finish pairs, every material binding, uniforms, shared model selection and file
hashes. It exits unsuccessfully if a pair is missing or invalid:

```sh
python3 /opt/wok-api/tools/audit_cs2_shader_catalog.py \
  --assets /path/to/staging/assets --hashes --require-all \
  --output /path/to/catalog-audit.json
```

After that audit, exercise all published shader programs and shared models in
the browser. Copy immutable assets first and atomically replace `catalog.json`
last; retain previous content hashes for clients that have already opened a page.

## Technical references

- [SteamWebAPI's CS2SCREEN iframe integration](https://www.steamwebapi.com/cs2-3d-viewer)
- [Source 2 Viewer CLI export options](https://github.com/ValveResourceFormat/ValveResourceFormat/blob/master/docs/guides/command-line.md)
- [model-viewer](https://modelviewer.dev/), bundled locally under its package license

## Server-rendered screenshots

See [CS2 Screenshot API](cs2-screenshot-api.md) for front/back/both PNG renders, transparent output and a bounded asynchronous queue. Browser PNG export captures the model canvas; viewer branding overlays are not baked into that PNG.
