Decalery manual
Contents
What is Decalery
Decalery is a mesh-based decal system. That is, every decal created with it is a regular mesh made of triangles. This way they can be rendered on any platform and render pipeline, and the only thing the decal shader needs to do is to offset vertices slightly towards the camera (Decalery includes such shaders, and existing shaders can be easily extended).
Decalery projects source decal mesh onto a set of receiver meshes to generate the final decal mesh:
This way Decalery is not limited to simple quad-like decals. For example, mesh decals can be used for:
- Classic quad projections (graffities, bullet holes, paint splats, etc).
- Projected roads/paths on top of curved geometry.
- Edge decals like this.
- As a 3D "clone stamp" to hide texture seams or unwanted tiling.
- etc...
When generating final decal meshes, Decalery takes:
- Normals and lightmap UV from the receiver mesh;
- Texture UVs and vertex colors from the source decal mesh.
Decal triangles are perfectly aligned to the receiver mesh, not pushed away, so it is impossible to see them "floating". To prevent Z-fighting, a compatible shader is used. Everything such shader does amounts to:
On top of that, a little bit of regular slope-scaled depth bias is applied.
This way decals perfectly replicate underlying surface shading and lightmapping, while keeping their own UVs and vertex color. Vertex color can be used to control opacity, and default Decalery shaders assume that. Per-vertex alpha values take less memory than texture masks most of the time.
Installation
Simply import the package into Unity. Files will be unpacked to Assets/Decalery. If project uses URP, also unpack DecaleryURP.unitypackage; If project uses HDRP, unpack DecaleryHDRP.unitypackage. There are two example scenes: example_decals_editor for in-editor level design usage and example_decals_runtime for in-game decal spawning. Example scenes are made in Unity 2019 and require this version or newer; the decal system itself works on older versions.
Quickstart
Using decals in Editor
1. Position decal mesh (e.g. a default Quad or anything else) in front of the receiver mesh (or meshes).
2. Set a decal-compatible shader on the decal mesh. Decalery includes some:
- DecalerySimple: just a basic textured decal with alpha channel; blend is configurable.
- DecaleryStandardPBR: Standard-like PBR shader for decals.
- DecaleryURPBasicPBR: Lit-like PBR shader for decals in URP.
- ??? for HDRP.
3. Add Decal Group component to it.
4. Click Pick in scene, then click on receiver meshes in Scene View. They will be added to the receiver list.
5. Click Live Preview and try moving the object around. Decal should become visible. If it's not, then it's probably not close enough to the receivers; increase Forward distance until the decal appears or move it closer.
6. Try placing more decals. You can also just click Update once instead of going to Live Preview, or click Decalery -> Update all decals to regenerate all decals in the scene.
Current limitations
Built-in (5.6) | Built-in (2017.X) | Built-in (2018.X) | Built-in (>= 2019.1) | URP 15.0.7 (tested on 2023.1.5) | HDRP 15.0.6 (tested on 2023.1.5) | |
---|---|---|---|---|---|---|
Editor and Runtime CPU decals | Yes | Yes | Yes | Yes | Yes | Yes |
Runtime decals: GPU with readback | No | Yes (slower mesh API) | Yes (slower mesh API) | Yes | Yes | Yes |
Runtime decals: Full GPU | No | It does something? | Yes, but local lights only work correctly in Deferred | Yes, but local lights only work correctly in Deferred | No (can't copy per-object data to decals) | Not tested yet |
Components
Decal Group
Represents a decal projector, defined by the MeshFilter/MeshRenderer on the same object. Decal shape can be anything from a simple quad, to a complex shape, such as e.g. a corner, a ring around another object, etc. MeshRenderer should be normally disabled after the decal is projected.
Decal gets position, normals and lightmap UVs from the receiver, but retains its own texture UVs, tangents and vertex color. Decalery assumes that vertex color is used as a single opacity value (fade).
Settings:
- Receiver selection mode: defines how the decal-receiving objects are selected. Possible options:
- Manual: receivers are selected by hand. This is the most precise and predictable method. Objects can be chosen via both the standard Unity selector or by pressing the "Pick in Scene" button and directly clicking receivers in the scene. To the side of every added object there are X and M buttons:
- Pressing X will remove the receiver from the list.
- Pressing M will show a material field which will be used instead of the decal's own material only for the selected receiver. This is useful when, for example, the same decal is applied to a dry and a wet surface, where different shaders might be desirable.
- Box Intersection: receivers are chosen by overlapping a box with nearby colliders.
- Box scale: scales the selection box. Final box size is affected both by decal mesh bounds, Forward/Backward distance and Box scale.
- Raycast from vertices: receivers are chosen by performing a physics raycast from every vertex of the decal's mesh, in the inverse normal direction. As with the previous option, it only picks up objects having colliders.
- Manual: receivers are selected by hand. This is the most precise and predictable method. Objects can be chosen via both the standard Unity selector or by pressing the "Pick in Scene" button and directly clicking receivers in the scene. To the side of every added object there are X and M buttons:
- Forward distance: decal projection distance (inside).
- Backward distance: decal projection distance (outside).
- Angle clip: maximum allowed angle between each decal triangle and projector triangle. The value is from -1 (-180) to 1 (180). This is to prevent texture stretching on surfaces parallel to the projection.
- Angle fade: fades vertex color/alpha to 0 as the projection approaches Angle clip value.
- Layer mask: a simple layer mask to filter out receiving objects.
- Generate tangents: does decal geometry need per-vertex tangents generated? Tangents are required for normal-mapping, parallax and other effects.
- Link to parent: if enabled, decal meshes will be parented to their receivers.
- Optimize: performs vertex welding and vertex order optimization.
Buttons:
- Live Preview: when enabled, decal will be rebuilt interactively, as it is transformed, or its settings are tweaked.
- Update: (re)builds the decal geometry once.
- Remove: removes previously generated decal geometry.
Runtime API
Decals can be created at runtime. While it is possible to directly use the DecalGroup component, it is not recommended for performance reasons. Decalery has a special fast mode of generating and rendering simple quad-like and trail-like decals at runtime. The architecture is as follows:
DecalSpawner
DecalSpawner class is the main building block that manages pools of decals using identical settings and materials. For example, in a shooter, a single DecalSpawner for bullet holes can be used by all guns of all characters. Internally DecalSpawner will create lower-level DecalUtils.Group objects for every combination of shader variant / lightmap index / parent transform.
Properties:
- InitData initDataMain spawner settings. InitData class contains following members:
- Material materialBase material used by all spawned decals. Use shaders made specifically for decals (offsetting position to camera to prevent Z-fighting).
- bool isTrailIs this decal a trail (e.g. a tire track)? Trails connect edge-to-edge instead of being separated quads. Trails also have a unique continuous UV generation style.
- float trailIntervalIf isTrail is enabled, controls interval between trail edges (smaller interval = rounder trails).
- float trailVScaleIf isTrail is enabled, controls vertical texture coordinate tiling.
- bool tangentsShould decals have tangents (do they need normal mapping)?
- bool inheritMaterialPropertyBlocksShould these decals inherit MaterialPropertyBlocks from receivers?
- bool useShaderReplacementUse shader replacement feature? (see next)
- ShaderReplacement[] shaderReplacementAllows optionally overriding decal shader based on the receiver's shader. Can be used when applying decals to e.g. special surfaces with vertex deformation. ShaderReplacement structure contains following members
- Shader src
- Shader destIf receiver has shader src, decal will use shader dest.
- Dictionary<int, DecalUtils.Group> staticGroupsA map of all static (that is, not having any parent transform) DecalUtils.Groups created by this spawner, indexed by their lightmap ID.
- Dictionary<Transform, DecalUtils.Group> movableGroupsA map of all movable DecalUtils.Groups created by this spawner, indexed by their parent transform.
Methods:
- void Init(int maxTrisTotal, int maxTrisInDecal, DecalUtils.Mode preferredMode, bool preferDrawIndirect, int preferredShaderPass)Initializes the spawner using its properties. Must be called only once.
- int maxTrisTotalMaximum amount of triangles used in a single DecalUtils.Group spawned. Decal triangle count depends on receiver geometry detail and decal size. Older decals will disappear when new decals are added above the limit.
- int maxTrisInDecalMaximum allowed triangle count for one decal. Similarly, it depends on receiver geometry detail and decal size. While this number can be set to maxTrisTotal for safety, using a lower realistic value will reduce processing time.
- DecalUtils.Mode preferredModePreferred decal generation mode. Possible values are:
- DecalUtils.Mode.CPUGeneration is done on the CPU, similar to how the DecalGroup component works. This is the slowest, but also the most platform-compatible option. Requires receiver meshes to have Read/Write Enabled.
- DecalUtils.Mode.GPUGeneration is done on the GPU. This option requires hardware support for Unordered Access Views and Geometry Shaders (i.e. sm5_0). Receivers do not need to have Read/Write Enabled.
- bool preferDrawIndirectAre these decals supposed to be rendered with DrawProceduralIndirect? Such decals do not require costly VRAM->RAM->VRAM memory transfers, as the generated buffer is used directly by the drawing shader. Shader must be aware of this method.
- int preferredShaderPassShader pass used by indirect-drawing shaders. If the pass is not present in the shader, it will be clamped to the highest available value.
- void AddDecal(Vector3 position, Quaternion rotation, GameObject hitObject, float decalSizeX, float decalSizeY, float distance, float opacity, float angleClip, Transform rootObject)Spawns a decal on hitObject from a projector placed at position in the direction of rotation.
- Vector3 positionProjection origin.
- Quaternion rotationProjection rotation. Decals are projected along the positive Z axis.
- GameObject hitObjectObject receiving the decal. Must have either a MeshRenderer, a SkinnedMeshRenderer or a DecalMeshRef (containing a list of renderers). When the decal is applied to a SkinnedMeshRenderer, hitObject (bone) matrix will be used to transform into bind pose correctly.
- float decalSizeXDecal width.
- float decalSizeYDecal height.
- float distanceDecal projection distance (same as Forward distance in DecalGroup).
- float opacityOpacity multiplier.
- float angleClipSame as Angle clip in DecalGroup.
- Transform rootObjectOptional transform used as a parent for this decal. Assign movable objects' transforms here.
- void AddDecalToQueue(Vector3 position, Quaternion rotation, GameObject hitObject, float decalSizeX, float decalSizeY, float distance, float opacity, float angleClip, Transform rootObject)Similar to AddDecal, but adds the decal to the queue instead (see next).
- void UpdateQueue(int maxDecalsPerFrame)Takes maxDecalsPerFrame elements from the queue and executes them. This is useful to prevent too many decals spawning in one frame, thus affecting performance.
- void Clear()Removes all visible decals, created with this spawner. Doesn't release memory.
- void Release()Removes all visible decals, created with this spawner and releases all used memory.
Static properties:
- static List<DecalSpawner> AllA list of all spawners ever created.
DecalManager
DecalManager manages the creation and reuse of DecalSpawners. Its main purpose is to let different scripts to access the same shared spawners.
Static methods:
- static void SetPreferredMode(DecalUtils.Mode mode, bool drawIndirect, int pass)Sets preferred generation/rendering settings for all newly created spawners (see Init).
- static DecalSpawner GetSpawner(DecalSpawner.InitData initData, int maxTrisTotal, int maxTrisInDecal)Gets an existing spawner or creates a new one using provided settings.
- static DecalSpawner GetSpawner(string name, int maxTrisTotal, int maxTrisInDecal)Same, but will attempt to load initData from Resources/DecalTypes/name.asset. This data can be stored in DecalType assets, as seen in the included example scenes.
- static DecalSpawner CreateUniqueSpawner(DecalSpawner.InitData initData, int maxTrisTotal, int maxTrisInDecal)Similar to GetSpawner, but always creates a new spawner. Normally. this is only useful for trail decals, as they connect to each other within one DecalUtils.Group.
DecalUtils
DecalUtils.Group is a lower-level object, representing a fixed-sized pool of decals, sharing a common material, lightmap index and parent transform. Most of the time there is no need to use it directly. DecalUtils class uses static methods to work with DecalUtils.Group. Underneath, most methods branch to either CPUDecalUtils or GPUDecalUtils.
CPUDecalUtils
CPUDecalUtils are used internally for both runtime CPU decals and in-editor DecalGroups.
GPUDecalUtils
GPUDecalUtils are used internally for runtime GPU decals. There are some GPU-specific methods that are useful for indirect drawing:
- static CommandBuffer CreateDrawIndirectCommandBuffer(DecalUtils.Group group, int pass)Creates a CommandBuffer drawing the decals directly from generated data on the GPU, with the specified shader pass. Decal group must be created witn indirectDraw enabled.
- static void UpdateDrawIndirectCommandBuffer(DecalUtils.Group group, Matrix4x4 mtx, int pass)Updates the command buffer using a new transformation matrix.
Examples of GPUDecalUtils usage can be found in DecalGlobalRuntimeSettings.cs.