{"id":19088631,"url":"https://github.com/naelstrof/skinnedmeshdecals","last_synced_at":"2026-01-25T06:44:05.182Z","repository":{"id":43460962,"uuid":"259008720","full_name":"naelstrof/SkinnedMeshDecals","owner":"naelstrof","description":"An example of rendering decals on SkinnedMesh Renderers in Unity.","archived":false,"fork":false,"pushed_at":"2024-11-23T00:22:34.000Z","size":36439,"stargazers_count":159,"open_issues_count":9,"forks_count":24,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-01-02T22:17:45.322Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"ShaderLab","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/naelstrof.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-26T11:03:51.000Z","updated_at":"2024-12-30T15:25:30.000Z","dependencies_parsed_at":"2024-10-28T00:46:15.031Z","dependency_job_id":null,"html_url":"https://github.com/naelstrof/SkinnedMeshDecals","commit_stats":null,"previous_names":[],"tags_count":56,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/naelstrof%2FSkinnedMeshDecals","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/naelstrof%2FSkinnedMeshDecals/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/naelstrof%2FSkinnedMeshDecals/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/naelstrof%2FSkinnedMeshDecals/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/naelstrof","download_url":"https://codeload.github.com/naelstrof/SkinnedMeshDecals/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240137003,"owners_count":19753649,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T03:01:14.069Z","updated_at":"2026-01-25T06:44:05.175Z","avatar_url":"https://github.com/naelstrof.png","language":"ShaderLab","funding_links":["https://www.patreon.com/posts/skinned-mesh-22015350"],"categories":[],"sub_categories":[],"readme":"# Unity Skinned Mesh Renderer Decals\n\n[![openupm](https://img.shields.io/npm/v/com.naelstrof.skinnedmeshdecals?label=openupm\u0026registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.naelstrof.skinnedmeshdecals/)\n\nAn example of how to create efficient projected decals on moving skinned meshes. This should work on all render pipelines, though HDRP is what this project is built on.\n\n![some decals](showcase.gif)\n\n## Installation\n\nYou can install this package by adding `https://github.com/naelstrof/SkinnedMeshDecals.git#upm` through the Package Manager's \"Add package from git URL\".\n\nIt's good practice to version lock by replacing #upm with a tag so that your project remains portable.\n\n## Usage\n\n1. Enable \"Lightmap generation\" in the target models' import settings, or ensure that models have atlased uv2s so that decals can be mapped to any part of the mesh.\n2. Apply a material to the target model's mesh that has a `_DecalColorMap` input, and uses UV2 only for the `_DecalColorMap`.\nI use AmplifyShaderEditor to create the shaders for this material, this is the only pipeline dependant part! You can also use Shader Graph.\n3. Ensure your decal textures, the ones that you'll be splattering, each have mipmapping disabled, and have texture wrap set to clamped. **This is important!**\nOtherwise the decals will cover the whole model.\n4. Create a script that calls `PaintDecal.QueueDecal(...)` with the target being one of the renderers you've set up. It should let you draw decals!\n\n## Examples\n\nThis is a working unity project that can be opened to see the test scene seen above in the gif. You can also simply read some of the example usage scripts like [DecalShooter.cs here](https://github.com/naelstrof/SkinnedMeshDecals/blob/master/Assets/URPExample/Scripts/DecalShooter.cs).\n\n# Detailed explanation\n\n*PaintDecal.QueueDecal* takes three parameters, a *target*, a DecalProjector, and a DecalProjection. It adds the decal to a queue to be rendered later.\n\n## PaintDecal Target\n\nA target can be a Renderer or a Collider. In most games, decals are usually done due to a detected hit event via colliders, this is so common that SkinnedMeshDecals comes with a DecalableCollider class that directly associates a Collider with a set of Renderers.\n\nCollider targets *must* have a DecalableCollider monobehavior on it to work properly. Renderers are if you want to handle decal targets manually.\n\n## DecalProjector\n\nThis is a class designed to represent a shader with parameters to be ran as the decal routine. This is extremely complex so it comes with some built-in solutions that serve as examples and the base-line for basic decaling.\n\nHere's a few examples of DecalProjectors that do different things:\n- A blue sphere splat: `new DecalProjector(SphereAlpha, Color.blue)`\n- An \"eraser\" sphere: `new DecalProjector(SphereSubtractive, Color.white)`\n- A texture splat that ignores backfaces:\n```csharp\n[SerializeField] private Texture2D splatTexture;\n...\nnew DecalProjector(TextureAdditive, splatTexture, Color.red, true)\n```\n\nI highly recommend reading the constructors for the class, as they are XML documented.\n\n## DecalProjection\n\nThis is a class that represents a camera where the decal will be blitted. It is interally a View and Projection matrix, and comes with a few constructors that simplifies the decision-making on how you want the decal to be projected.\n\n\u003cimg width=\"852\" height=\"382\" alt=\"image\" src=\"https://github.com/user-attachments/assets/e36e10b6-3f20-475c-a88d-43c66ebc3fa7\" /\u003e\n\nHere's a few examples of projections:\n- 1x1x1 meter ortho projection on a surface from a raycast with a random rotation.\n```csharp\nVector3 hitPoint;\nVector3 hitNormal;\n...\nvar ortho1by1by1 = new DecalProjection(hitPoint, Quaternion.LookRotation(-hitNormal) * Quaternion.AngleAxis(Random.Range(0f,360f), Vector3.forward), Vector3.one);\n```\n- A one meter radius sphere projection, combos great with the Sphere-based DecalProjectors:\n```csharp\nvar oneMeterSphereProj = new DecalProjection(hitPoint, 0.5f);\n```\n\nYou can find more overloads for constructing projections in the DecalProjectors class. Working with Quaternions to specify the orientation of the projection is hard but worth it I promise!\n\n## DecalSettings\n\nThis struct represents any extraneous data needed for blitting the decal. You can specify the following within it:\n- The texture name, the name used in the shaders that it expects to override. Defaults to \"_DecalColorMap\".\n- The *RenderTextureFormat* that the splatmap should use, this can be useful if you need more precision (or less!) than RGBA8.\n- The *RenderTextureReadWrite* mode, allowing you to disable gamma/linear conversions to read/write raw values.\n- *DecalResolution*, to specify an exact texture dimensions, or allow SkinnedMeshDecals to guess based on the texels per meter setting found within SkinnedMeshDecalsSettings.\n- *DilationType*, to specify how dilation should be applied to avoid the rasterization error with seams.\n\nFor example if you wanted a high-resolution, single-channel floating point splatmap without dilation, you'd give QueueDecal a DecalSettings like so:\n\n```csharp\nnew DecalSettings(decalResolution = new DecalResolution(new Vector2Int(2048,2048)), dilation = DilationType.None, renderTextureFormat = RenderTextureFormat.R32);\n```\n\nSee the DecalSettings.cs script to find out more about these settings.\n\n# Technical Feature Description\n\nThis package essentially generates textures on meshes, this is an extremely powerful graphical effect that can be used in many ways. Here's a few features that can be added to your project with this technology and a short description on how you'd achieve it.\n\n## Texture Dilation\n\nThis decal strategy specifically suffers from a rendering artifact where UV seams won't get painted. The issue is described in great detail [here](https://youtu.be/c7HBxBfCsas?t=329).\n\nNormally this can be solved by enabling Conservative Rasterization, but that is a hardware dependent feature, so we opted for a dilation shader instead.\n\nAny splat maps that were decaled in a frame, will have their dilated versions generated at the end of the frame using the dilation shader specified within the SkinnedMeshDecalsSettings.\n\n## Memory Management\n\nSkinnedMeshDecals uses VRAM to store all the splat maps used, because VRAM is valuable and limited, SkinnedMeshDecals automatically attempts to budget splatmaps to fit within a specified budget.\n\n512MB allows for about 30 2048x2048 RGBA textures. Dilation being enabled halves it to about 15 2048x2048 RGBA textures. This calculation is an estimate though, as the true capacity will vary depending on compatible in-gpu compression.\n\nIt can be really easy to go over the budget, oldest splatmaps are removed first before new splatmaps are allocated. If you want to allow for more than the default memory budget, see *SkinnedMeshDecalsSettings.cs*, and adjust the values of the SO found at `Assets/SkinnedMeshDecals/Resources/SkinnedMeshDecalsSettings.asset`.\n\n(If it's not found, it is generated automatically as you use SkinnedMeshDecals for the first time in the editor.)\n\n## Fading decals\n\nIf you want decals to fade over time, this is easier than it seems! Simply blit a giant subtractive sphere at a tempo that gives it the fade time you desire, something like so:\n\n```csharp\nclass DecalFader : Monobehavior {\n  void Update() {\n    foreach(var renderer in GetComponentsInChildren\u003cRenderer\u003e()) {\n      PaintDecal.QueueDecal(renderer, new DecalProjector(SphereSubtractive, new Color(1f/256f,1f/256f,1f/256f, 1f)), new DecalProjection(transform.position, 100f));\n    }\n  }\n}\n```\nBe weary that the default decal texture format only supports 256 colors per pixel, so it will only take 256 frames to completely disappear. If you want it to take more time, space out the subtractive splats over time!\n\nDon't worry about overloading the system, QueueDecal specifically limits how many decals can render per-frame, and it will half-discard commands if it goes over the limit. It could mean that it takes extra time for the fade to take place, but its worth the performance gain generally.\n\nIf you prefer not to do decal splats for fading, you can also manually query every RenderTexture in-use by the decal system with `PaintDecal.GetDecalTextures()`, then manually modify them with your own CommandBuffer commands.\n\n## Saving splat state\n\nIn your game it might make sense to allow an object in the world to get dirty with splats, but also be able to store it away temporarily.\n\nPaintDecal comes with a `PaintDecal.GetDecalTexture()`, and a `PaintDecal.OverrideDecalTexture()` function, which allows you to save out the image and apply it back. This can be useful for inventory games where you need to remember how \"dirty\" an object is even if it's inside of an inventory.\n\nBe weary that this disables automatic memory management, and expects you to control the lifetime of the textures. This makes it BAD for saving and loading specifically. Please make a feature request if it's important for your game to be able to save and load without disrupting the memory management.\n\n## RGBA masking\n\nThese decals work best as masks since they're fairly low resolution and have no overdraw considerations. This leads to better utilizing the channels as individual masks for things like damage, age, wetness...\n\nLets say you want to arrange your texture channels to be used in shaders like so:\n\n- R: wetness\n- G: damage\n- B: unused\n- A: age\n\nSince the alpha channel isn't used as an alpha, this requires a custom DecalProjector shader. To create one you'll need to do the following:\n\n1. Install AmplifyShaderEditor (Required to easily edit the built-in projectors!)\n2. Install the AmplifyClipSpaceTemplate.unitypackage found in this package's Addon folder.\n3. Duplicate one of the existing projector shaders.\n4. Edit your custom projector shader so that Alpha blending blits like a regular color. [Check here for blending factor options.](https://docs.unity3d.com/530/Documentation/Manual/SL-Blend.html)\n5. Assign this new custom projector shader to a Material in your project.\n6. Supply this new material to a QueueDecal command by putting it inside a DecalProjector constructor. `new DecalProjector(material);`\n\nThis will allow you to create specific shaders with custom color masking, custom color blending, backface culling, while not worrying too much about how decals are actually projected onto meshes.\n\nPlease note that since drawing decals are deferred, configuring the material parameters might require cloning or instantiating the material at decal-time.\n\nYou will need to specify the dilation type to be DilationType.Additive in your QueueDecal command if alpha doesn't represent an alpha blend.\n\n# Inspired by\n\nThis patreon post and the youtube video were used as the basis for this technology.\n\nhttps://www.patreon.com/posts/skinned-mesh-22015350\n\nhttps://www.youtube.com/watch?v=c7HBxBfCsas\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnaelstrof%2Fskinnedmeshdecals","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnaelstrof%2Fskinnedmeshdecals","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnaelstrof%2Fskinnedmeshdecals/lists"}