texture-packing-variant-stripping — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited texture-packing-variant-stripping (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use this skill when the user is dealing with texture optimization, channel packing, shader variant bloat, build size, shader compilation time, or shader keyword management in Unity. Triggers include: "channel packing", "texture packing", "mask map", "MODS map", "RMA", "shader variants", "variant stripping", "multi_compile", "shader_feature", "build size", "shader keywords", "Always Included Shaders", "IPreprocessShaders", or complaints about slow shader compilation or large build sizes.
Channel packing stores multiple grayscale maps in a single RGBA texture. This reduces both texture sample count and memory bandwidth — the primary bottleneck on mobile.
Mask Map (single RGBA texture):
R = Metallic
G = Occlusion (AO)
B = Detail mask (or custom)
A = Smoothness // BAD — 4 texture samples
half metallic = SAMPLE_TEXTURE2D(_MetallicMap, sampler_MetallicMap, uv).r;
half ao = SAMPLE_TEXTURE2D(_AOMap, sampler_AOMap, uv).r;
half detail = SAMPLE_TEXTURE2D(_DetailMask, sampler_DetailMask, uv).r;
half smooth = SAMPLE_TEXTURE2D(_SmoothnessMap, sampler_SmoothnessMap, uv).r;Use:
// GOOD — 1 texture sample
half4 masks = SAMPLE_TEXTURE2D(_MaskMap, sampler_MaskMap, uv);
half metallic = masks.r;
half ao = masks.g;
half detail = masks.b;
half smooth = masks.a;Unity's Shader Graph 17.4 terrain samples demonstrate advanced packing that reduces 12 texture samples to 5 for a 4-layer terrain:
// Pack 4 grayscale values into RGBA
half4 PackChannels(half metallic, half ao, half detail, half smoothness)
{
return half4(metallic, ao, detail, smoothness);
}
// Reconstruct normal Z from XY (for normal map packing)
half3 UnpackNormalRG(half2 normalRG)
{
half3 n;
n.xy = normalRG * 2.0h - 1.0h;
n.z = sqrt(1.0h - saturate(dot(n.xy, n.xy)));
return n;
}Each multi_compile keyword doubles variant count multiplicatively:
#pragma multi_compile _ _A → 2 variants
#pragma multi_compile _ _A _B → 3 variants
#pragma multi_compile _ _A
#pragma multi_compile _ _B → 2 × 2 = 4 variants
#pragma multi_compile _ _A
#pragma multi_compile _ _B
#pragma multi_compile _ _C → 2 × 2 × 2 = 8 variantsWith URP's built-in keywords (shadows, fog, additional lights, etc.), a single shader can generate thousands of variants.
shader_feature | multi_compile | |
|---|---|---|
| Compiles | Only variants used by materials in build | ALL combinations |
| Use for | Material-specific toggles | Global state (fog, shadows) |
| Unused variants | Stripped automatically | Included regardless |
| Local version | shader_feature_local (64 max) | multi_compile_local (64 max) |
| Global version | shader_feature (256 max) | multi_compile (256 max) |
Rule: Use shader_feature for everything you control (material toggles, feature flags). Only use multi_compile for things Unity controls globally (fog, lightmaps, instancing).
Strip these keywords if you're not using the features:
// In an IPreprocessShaders implementation:
static readonly ShaderKeyword[] STRIP_KEYWORDS = new[]
{
new ShaderKeyword("_ADDITIONAL_LIGHTS"), // If using main light only
new ShaderKeyword("_ADDITIONAL_LIGHT_SHADOWS"), // If no additional light shadows
new ShaderKeyword("_SHADOWS_SOFT"), // If using hard shadows only
new ShaderKeyword("_MIXED_LIGHTING_SUBTRACTIVE"), // If not using mixed lighting
new ShaderKeyword("_SCREEN_SPACE_OCCLUSION"), // If not using SSAO
new ShaderKeyword("LIGHTMAP_SHADOW_MIXING"), // If not mixing lightmaps + realtime
new ShaderKeyword("_LIGHT_LAYERS"), // If not using light layers
new ShaderKeyword("_REFLECTION_PROBE_BLENDING"), // If not blending reflection probes
new ShaderKeyword("_REFLECTION_PROBE_BOX_PROJECTION"), // If not using box projection
};In URP Asset settings, disable unused features to prevent their variants from being compiled:
// Include features only on platforms that can handle them
#if defined(SHADER_API_MOBILE) || defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)
// Mobile/WebGL: simplified path
#define MOBILE_SHADER 1
#else
// Desktop: full features
#define MOBILE_SHADER 0
#endif
half4 frag(Varyings i) : SV_Target
{
half4 color = SampleAlbedo(i.uv);
#if !MOBILE_SHADER
// Desktop-only: normal mapping, specular, etc.
color.rgb += SampleNormalAndLight(i);
#endif
return color;
}| Format | Platform | Bits/Pixel | Quality | Notes |
|---|---|---|---|---|
| ASTC 4×4 | Android (ES 3.0+), iOS | 8 | Best | Preferred for all mobile |
| ASTC 6×6 | Android (ES 3.0+), iOS | 3.56 | Good | Use for less important textures |
| ASTC 8×8 | Android (ES 3.0+), iOS | 2 | Acceptable | Background/distant textures |
| ETC2 | Android (ES 3.0+), WebGL 2.0 | 4-8 | Good | WebGL 2.0 default |
| ETC1 | Android (ES 2.0) | 4 | OK | No alpha channel support |
| PVRTC | iOS (older) | 2-4 | Variable | Legacy, prefer ASTC |
For channel-packed textures: Use ASTC 4×4 or ETC2 with linear color space. Avoid PVRTC for mask maps — its block compression causes visible artifacts on sharp mask boundaries.
shader_feature used instead of multi_compile for material togglesshader_feature_local)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.