Zone Variation & Palette System

Per-Instance Color Variation for the VCCore Shader

This system gives individually-placed props their own color variation without breaking GPU instancing — no MaterialPropertyBlocks, no per-object materials. Instead, each instance's color choices are matched against a shared master palette texture and packed into a single per-instance value the VCCore shader reads at render time. It's made up of three pieces: the DPT_ZoneVariation component you place on instances, the DPT_PaletteMatcher utility that does the color-to-index lookup, and the DPT_PaletteGenerator editor tool that builds the master palette texture itself.

Why palette indices instead of raw color? Packing raw RGB per instance costs more bits than packing a small index into a shared palette. Indexing into a palette also keeps every instance's variation reading from the exact same finite set of colors, which is what lets this ride along on Unity's GPU Resident Drawer instancing path — MaterialPropertyBlocks break that batching, so this system deliberately avoids them.

The DPT_ZoneVariation Component

Add DPT_ZoneVariation to any instance of a VCCore-shaded prop to give it independent color variation. It exposes four zone color fields (Zone 0–3) plus a single object-wide Emissive intensity slider.

The component automatically finds the renderers it controls (including children), and re-applies whenever you edit values in the Inspector (OnValidate) or the object is enabled. In Play Mode, if Update Every Frame is off, it applies its values once on Start and then disables itself — it isn't ticking every frame doing nothing.

Requires the VCCore shader: DPT_ZoneVariation checks whether any of its renderers use a material with a _ZonesPalette property. If none do, it logs a warning at runtime (once) and the component has no visible effect — the values it packs have nowhere to be read from.

Editor Cleanup

When the component is removed or destroyed in the Editor (not in Play Mode), it resets the instance's packed value back to zero so the renderer doesn't keep showing a stale variation after the component is gone.


How Palette Matching Works

DPT_PaletteMatcher.FindNearestIndex takes a target color and a palette texture, and returns the index of the closest matching color in that palette. Distance is measured using Rec.601 luma-weighted channel differences (0.299 / 0.587 / 0.114 for R/G/B) rather than flat Euclidean distance, so the match favors perceptual closeness — the same weighting the rest of the VCCore pipeline uses for luminance.

Palette pixel data is cached per-texture after the first lookup, so repeated matches against the same palette (e.g. matching all four zone colors on one instance) don't re-read the texture each time. If a palette texture isn't marked Read/Write Enabled in its import settings, the matcher falls back to a GPU blit + readback to get pixel data instead of failing outright.

Cache invalidation: If you regenerate or reimport a palette texture, call DPT_PaletteMatcher.ClearCache() (or pass the specific texture) so subsequent matches read the updated palette instead of the stale cached one.

Generating a Master Palette

Open the generator via Tools → DPTTools → Tools → Generate Palette. It builds a 64-color master palette as a full-gamut lattice across R/G/B — not a gradient path through color space — so the resulting palette actually spans the color cube rather than one line through it.

The preview strip and the "Resulting levels per channel" readout update live as you adjust the weight sliders, so you can see exactly how many shades each channel is getting before baking.


RSUV Packing Reference

Each instance's four zone indices and emissive intensity are packed into a single 32-bit value written to the GPU Resident Drawer's per-instance user value (RSUV) — this is what makes the whole system instancing-compatible with no MaterialPropertyBlocks.

Bits Field Contents
0–5 Zone 0 Index 6-bit index (0–63) into the 64-color master palette.
6–11 Zone 1 Index 6-bit palette index.
12–17 Zone 2 Index 6-bit palette index.
18–23 Zone 3 Index 6-bit palette index.
24–31 Emissive 8-bit intensity (0–255), with the lowest bit always forced on.
Why the packed value can never be zero: The emissive byte's lowest bit is always forced to 1, and the whole packed value is nudged to 1 if it would otherwise land on exactly zero. This keeps an all-default instance (zone 0, no emission) distinguishable from an instance with no per-instance value set at all.

Canvas Blending & Replacement Controls (Tint vs. Recolor)

The VCCore shader features customizable blend strengths that dictate how instance variations interact with the underlying surface canvas (such as baked vertex colors and ambient occlusion).

Unscripted objects or instances left at default values bypass these blends automatically, preserving original asset data cleanly.


Troubleshooting & FAQ

My DPT_ZoneVariation colors have no effect in-game.
Check the Console for a "no renderer using the custom VCCore shader" warning. This means none of the renderers under that object have a material with a _ZonesPalette property — assign a VCCore material to fix it.
I regenerated my palette and now all my zone colors look wrong.
Palette indices are only meaningful relative to the exact palette texture they were matched against. Regenerating the palette changes which color sits at each index, so every existing DPT_ZoneVariation instance re-resolves against the new layout. Re-select your zone colors (or re-trigger ApplyVariation) after swapping palettes, and call DPT_PaletteMatcher.ClearCache() if you replaced the texture asset in place rather than creating a new one.
Should I turn on "Update Every Frame"?
Only if the zone colors genuinely change during gameplay (damage states, faction recoloring, etc.). For static set-dressing props, leave it off — the component already re-applies automatically whenever you edit it in the Editor.

Support, Contact & Custom Integration

For all technical assistance, bug reports, asset configuration help, and general support, please reach out via email using the Contact Form. This is the primary and most reliable way to get help with any problems you encounter with the packages.

You can also use this form to inquire about custom freelance technical art services. If your project requires external 3D models to be integrated into the DPT ecosystem—including bespoke vertex-data packing, procedural wood grain mapping, GPU wind systems, or game-ready LOD budgeting—get in touch to discuss your specific needs and availability.

Email Contact:
For troubleshooting, bug reports, publisher licensing, and freelance inquiries, please use the Contact Form on the DPT Games Hub.
DPT Community Discord:
While the contact form is the best way to get direct help with issues, you are also welcome to join the Discord Server to share feedback and connect with other users.