Customize dice and looks in Dice View v3
Build face atlases and a 3D theme, layer a skin and particles, and save a reusable look for the physics viewer.
This article covers the v3 alpha preview, which uses physics only and is not yet published on npm. V2 ↗ Licenses ↗
This guide builds an obsidian theme for the physics based Dice View 3.0.0-alpha.0. You will edit face maps, publish theme.config.json, verify d20 and d2 results, then add a skin and effects without changing the atlases. V3 uses physics for every presentation. Follow the installation guide for this revision first if your viewer is not running yet. Review the v3 terms before distributing your integration.
1. Publish the assets and copy the atlases
Copy node_modules/@erpg/dice3dview/dist/assets/dice-box/ into public/assets/dice-box/. Keep themes/default/: it contains the model, textures, and coin used as the starting point. V3 does not need the v2 havok/ directory. Create themes/obsidian/ and copy diffuse-light.webp, diffuse-dark.webp, and normal.webp there from the default theme:
public/assets/dice-box/themes/
├── default/
│ ├── default.json
│ ├── diffuse-light.webp
│ ├── diffuse-dark.webp
│ ├── normal.webp
│ └── glyph-orientation.json
└── obsidian/
├── theme.config.json
├── diffuse-light.webp
├── diffuse-dark.webp
└── normal.webp
Open /assets/dice-box/themes/default/theme.config.json in your browser. If that URL fails, fix asset publishing before editing the theme. The default assetPath is /assets/dice-box/; you can change it in the constructor. The texture gallery displays the original v3 atlases together and offers the historical v2 specular sample.
2. Edit the maps without swapping faces
The default model’s atlases are 1024 × 1024 pixels. Redraw each numeral on its UV island while preserving placement, scale, and transparency. diffuse-light.webp has light glyphs for dark bodies; diffuse-dark.webp has dark glyphs for light bodies. The viewer picks a variant from the brightness of themeColor or the skin. The atlas is not a result ordered grid: moving a numeral to a different island can show a face that differs from the supplied value.
| Map | Role | When to include it |
|---|---|---|
diffuse-light.webp and diffuse-dark.webp |
Face artwork for different body colors | When replacing numerals or symbols |
normal.webp |
Relief in the same UV layout | When the new design needs relief; update it with the glyphs |
specularTexture |
Optional map modulating highlights | When you make your own specular image; the v3 default theme does not ship one |
Use material.type: "color" to keep the body recolorable through themeColor and to enable v3 skins. "standard" uses the atlas’s own full color artwork and does not receive a skin. See the complete material and path contract.
3. Write the manifest
Save this JSON as themes/obsidian/theme.config.json. Without meshFile, v3 uses themes/default/default.json; the maps therefore need to keep the same UV islands. Texture paths start at the obsidian/ directory.
{
"name": "Obsidian",
"systemName": "obsidian",
"material": {
"type": "color",
"diffuseTexture": {
"light": "diffuse-light.webp",
"dark": "diffuse-dark.webp"
},
"diffuseLevel": 1,
"bumpTexture": "normal.webp",
"bumpLevel": 0.5
},
"diceAvailable": ["d2", "d4", "d6", "d8", "d10", "d12", "d20", "d100"],
"coin": {
"front": { "value": 1, "texture": "../default/coin-1.svg" },
"back": { "value": 2, "texture": "../default/coin-2.svg" },
"colorize": true,
"diameter": 1,
"thickness": 0.12
}
}
material and diceAvailable are required. The d2 coin is procedural: the front has value 1, the back has value 2, and changing artwork does not change those values. If you make your own full color coin, replace the SVGs and use colorize: false; edgeColor controls its rim. For glyphs that need controlled orientation, v3 accepts faceAtlas.orientation pointing to a JSON file of per face directions. Reuse the default theme’s orientation file only if you keep its layout and artwork orientation. See theme structure and orientation.
Verify the manifest and every referenced image over HTTP. If you make a custom model, provide coherent visual dN mesh, dN_collider, and colliderFaceMap.dN for each polyhedron. In v3, the collider also defines the simulated body; test every face before publishing.
4. Verify the theme in a physics roll
import { DiceResultViewer } from '@erpg/dice3dview'
import '@erpg/dice3dview/style.css'
const viewer = new DiceResultViewer({
container: '#dice-stage',
assetPath: '/assets/dice-box/',
theme: 'obsidian',
themeColor: '#251c34'
})
await viewer.display({
id: 'obsidian-test',
dice: [
{ id: 'd20', sides: 20, value: 18 },
{ id: 'coin', sides: 2, value: 1 }
]
})
The viewer displays values already chosen by your application. Repeat the test for the shapes listed in diceAvailable, compare the visible face with value, and try a light body color to check the dark atlas. If you change an already loaded manifest under the same name, call viewer.dispose() and create a new instance to refresh material and model caches.
5. Layer a skin over the color
V3 projects a skin image onto the body with triplanar mapping. This is independent of the UV islands; the color atlas’s numerals remain above the image. The change takes effect on the next presentation:
await viewer.updateOptions({
themeColor: '#284b9b',
skin: {
texture: '/textures/marble.webp',
blend: 'multiply',
opacity: 0.85,
scale: 1.3,
labels: 'auto'
}
})
await viewer.display({
id: 'marble-test',
dice: [{ id: 'd6', sides: 6, value: 4 }]
})
blend accepts normal, multiply, screen, and overlay; opacity ranges from 0 to 1, scale from 0.05 to 20, and labels accepts auto, light, or dark. The texture accepts HTTP(S), data:, and blob: URLs. Enable CORS for another origin. To return to the theme’s surface, call await viewer.updateOptions({ skin: null }). See the skin API.
6. Add particles and glow
Particles and glow can change immediately, including while dice are visible. Start with a preset and configure roll moments:
await viewer.updateOptions({
particles: {
preset: 'sparkle',
intensity: 1.2,
color: '#ea5b93',
moments: { ground: false }
},
glow: { color: '#ea5b93', intensity: 0.8, light: true, pulse: false }
})
You can also define your own emitter without a preset. This example limits the impact effect to a d20 whose result is its maximum value:
await viewer.updateOptions({
particles: {
effect: {
impact: {
amount: 24,
life: [0.2, 0.6],
size: [0.08, 0.2],
speed: [1, 3],
colors: ['#ffffff', '#ea5b93aa', '#ea5b9300'],
when: { sides: [20], faces: 'max' }
}
}
}
})
particles: null and glow: null disable their effects. See presets, moments, conditions, and limits in the v3 API.
7. Save the look separately from the theme
The skins and theme studio includes the original workshop’s 18 procedural surfaces (marble, wood, stone, and more), 15 ready-made looks, and controls for every particle emitter. Edit trails, impacts, collisions, settling, aura, explosions, and critical effects, including force, speed, faces, sides, chance, and cooldown conditions. The preview uses the actual v3 viewer; export and import dice-look.json to continue editing.
A .dice-look.json file bundles color, skin, particles, and glow. It does not include theme.config.json, the model, or the atlases: keep distributing obsidian/ and selecting theme: 'obsidian' in the viewer.
import { createDiceLook } from '@erpg/dice3dview'
const look = createDiceLook({
name: 'Obsidian Marble',
themeColor: '#284b9b',
skin: { texture: '/textures/marble.webp', blend: 'multiply', opacity: 0.85 },
particles: { preset: 'sparkle', intensity: 1.2 },
glow: { color: '#ea5b93', intensity: 0.8 }
})
const json = JSON.stringify(look, null, 2) // save as obsidian-marble.dice-look.json
await viewer.applyLook(look)
createDiceLook() adds format: "dice3dview-look" and version: 1 and validates the fields. applyLook() validates a file before applying it. A skin URL must still be available when you open the look elsewhere; use a public URL or data: for a self contained file. See the look contract.
V2 compatibility
V2 manifests, models, and maps keep their format in v3. Test every shape with a physics roll when migrating, especially if the theme has a custom collider. faceAtlas.orientation is optional in v3; skins, particles, glow, and look files belong to this v3 integration and should be configured only in code that loads v3. The npm published v2 remains documented separately, as does the v2 → v3 migration. The versions have different licenses.