-
Notifications
You must be signed in to change notification settings - Fork 11
Materials & TextureSets
This tutorial explains how Material and TextureSet files work in Assassin's Creed games — how a material controls the way a surface looks and reacts to light, how it pulls its textures from a TextureSet or from direct overrides, and how the whole chain from mesh to pixel fits together.
Materials and TextureSets are part of the AnvilNext engine and are consistent in concept across titles (AC3, AC4, Rogue, Unity, Syndicate, Origins, Odyssey, Mirage, etc.). The exact set of render flags and TextureSet slots that a file stores changes between engine revisions, but the core structure — a material referencing a template and a texture set, and a texture set bundling texture maps by channel — is the same everywhere.
All examples are taken from real exported AC4: Black Flag files (Edward Kenway's default outfit Fabric material and its texture set), but the patterns apply to any AnvilNext title.
A note on
Pathvs ID: Throughout the XML you'll seePathattributes on references. These are not used by the game engine — it locates and loads every file by its numeric ID. AnvilToolkit fills thePathin as a human-readable convenience. When modding, the ID is what matters.
- Core Concept: The Material Chain
- TextureSet — Bundling Textures by Channel
- Material — Anatomy of a Material File
- TextureSelector & TextureSpecificationMethod — TextureSet vs Override
- MaterialTemplate — The Shader Behind the Material
- Standard vs Schema-Based Exporter
- Practical: Retexture Through the TextureSet
- Practical: Retexture With an Override
- Practical: Convert a TextureSet Call Into an Override
- Practical: Make a Material Two-Sided
- Practical: Make a Material Transparent
- Practical: Create a Brand-New Material
- Practical: Duplicate a Material With a Unique ID
- Quick Reference Summary
A model's surface is not described by a single file. It is a small chain, each link pointing at the next by ID:
Mesh
│ references
▼
Material ← how the surface looks (shiny, transparent, two-sided…) + which textures to use
├─ references ─► MaterialTemplate ← the shader/technique (the actual lighting math)
└─ references ─► TextureSet ← a bundle of texture maps, one per channel
│ references
▼
TextureMap(s) ← the actual images (Diffuse, Normal, Specular…)
- A Material is the hub. It points at a MaterialTemplate (the shader that decides how pixels are lit) and at a TextureSet (the textures fed into that shader), and it stores a pile of flags that tweak rendering (transparency, culling, shadows…).
- A TextureSet is just an organised bundle: "Diffuse = this image, Normal = that image, Specular = this other image."
- A TextureMap is the actual image data (the DDS you edited back in the Textures lesson).
Everything below is about the middle two links — the Material and the TextureSet.
A TextureSet answers one question: "for this material, which image is the Diffuse, which is the Normal, which is the Specular, and so on?" It is a fixed list of slots, one per texture channel, where each slot holds a reference (by ID) to a TextureMap file — or nothing.
Exported with the Standard exporter, a TextureSet is beautifully clear — every slot is a FileReference whose Name tells you exactly which channel it is. Here is the real CHR_P_EdwardKenway_Fabric_Set.TextureSet:
<TextureSet ID="11927604453">
<FileReference Name="Diffuse" IsGlobal="1" Path="...\CHR_P_EdwardKenway_Fabric_DiffuseMap...TextureMap">16223198188</FileReference>
<FileReference Name="Normal" IsGlobal="1" Path="...\CHR_P_EdwardKenway_Fabric_NormalMap...TextureMap">16223198192</FileReference>
<FileReference Name="Specular" IsGlobal="1" Path="...\CHR_P_EdwardKenway_Fabric_SpecularMap...TextureMap">29154175471</FileReference>
<FileReference Name="SpecularPower" IsGlobal="0" Path="0">0</FileReference>
<FileReference Name="OffsetBump" IsGlobal="0" Path="0">0</FileReference>
<FileReference Name="Emissive" IsGlobal="0" Path="0">0</FileReference>
<FileReference Name="Transmission" IsGlobal="0" Path="0">0</FileReference>
<FileReference Name="Occlusion" IsGlobal="0" Path="0">0</FileReference>
<FileReference Name="Mask1" IsGlobal="0" Path="0">0</FileReference>
<FileReference Name="Mask2" IsGlobal="0" Path="0">0</FileReference>
<FileReference Name="Generic" IsGlobal="0" Path="0">0</FileReference>
<Value Name="Source" Type="UInt64">11927604452</Value>
</TextureSet>Read it top to bottom: this material's Diffuse is texture 16223198188, its Normal is 16223198192, its Specular is 29154175471, and every other channel is empty (0). To change a texture, you change the ID in the matching slot. That's the whole file.
-
IsGlobal—1when the slot points at a real (global) file,0for an empty slot. Leave it matching the reference: a filled slot isIsGlobal="1", an empty one isIsGlobal="0" Path="0">0. -
Source— the ID of theTextureFilethis set was generated from (a build-time source record). You normally leave it alone.
This is why the Standard exporter is the friendlier one for TextureSets — the channel of every slot is written right there in the
Name.
The Schema-Based exporter stores the exact same data, but as an unlabeled positional array — the slot's meaning comes from its position, not a name:
<Object ID="11927604453" Type="TextureSet">
<StaticArray Name="Maps" Type="Reference">
<Reference Type="TextureBase" ...>16223198188</Reference> <!-- position 0 = Diffuse -->
<Reference Type="TextureBase" ...>16223198192</Reference> <!-- position 1 = Normal -->
<Reference Type="TextureBase" ...>29154175471</Reference> <!-- position 2 = Specular -->
<Reference Type="TextureBase" PointerType="Null" /> <!-- position 3 = SpecularPower (empty) -->
<Reference Type="TextureBase" PointerType="Null" /> <!-- ... and so on -->
...
</StaticArray>
<Handle Name="Source" Type="TextureFile" ...>11927604452</Handle>
</Object>Same three textures, same order — just harder to read at a glance because you have to count positions. Both formats import back identically; pick whichever you find clearer (Standard, for texture sets).
The set of slots a TextureSet stores depends on the game. AC4: Black Flag stores these 11 channels (in this order), followed by Source:
| # | Slot | Typical use |
|---|---|---|
| 0 | Diffuse | Base colour / albedo |
| 1 | Normal | Surface detail (bumps, wrinkles) |
| 2 | Specular | Reflectivity / gloss (may pack masks in its channels) |
| 3 | SpecularPower | Sharpness of highlights |
| 4 | OffsetBump | Parallax/height |
| 5 | Emissive | Self-illumination |
| 6 | Transmission | Light passing through (skin, cloth) |
| 7 | Occlusion | Baked ambient occlusion |
| 8 | Mask1 | Generic mask |
| 9 | Mask2 | Generic mask |
| 10 | Generic | Catch-all |
Newer titles add more slots (Unity adds
Cookie/EnvLighting; Origins/Odyssey/Mirage/Shadows addMask0/DiffuseAlpha; Mirage appends neural-texture slots). The three you'll touch 95% of the time are the same everywhere: Diffuse, Normal, Specular.
A Material has three parts: two top-level references (template + texture set), a block of render flags, and a Parameters list that holds per-texture selectors and tunable shader values.
Here is the top of the real CHR_P_EdwardKenway_Fabric.Material (Standard exporter, trimmed):
<Material ID="11927604587">
<FileReference Name="MaterialTemplate" IsGlobal="1" Path="...\CHR_Unique.MaterialTemplate">1870870824</FileReference>
<FileReference Name="TextureSet" IsGlobal="0" Path="...\CHR_P_EdwardKenway_Fabric_Set.TextureSet">11927604453</FileReference>
...
<Value Name="BlendMode" Type="Enum" EnumName="BlendMode" ValueName="Copy">0</Value>
<Value Name="AlphaTestValue" Type="Byte">0</Value>
<Value Name="AlphaTestEnabled" Type="Bool">False</Value>
<Value Name="TwoSided" Type="Bool">True</Value>
...
<List Name="Parameters" Type="DynamicProperty">
...
</List>
</Material>| Reference | What it points at | Notes |
|---|---|---|
| MaterialTemplate | The shader/technique file | Defines how the surface is lit. You usually reuse an existing template (see below). |
| TextureSet | The material's texture set | The default source for the material's textures. Can be left empty (0) if every texture is supplied by overrides in the Parameters list instead. |
Between the references and the Parameters list sits a block of flags. These are the knobs Lesson 2 introduced (TwoSided, transparency) plus many more. The most useful:
| Flag | Type | What it does |
|---|---|---|
| TwoSided | Bool | Renders the surface on both sides of each face. Turn on for thin geometry (sashes, capes, leaves) that would otherwise vanish from one side. |
| AlphaTestEnabled | Bool | Enables hard cut-out transparency (each pixel is either fully visible or fully gone). Needed for masked textures. |
| AlphaTestValue | Byte | The cut-off threshold (0–255) used when AlphaTestEnabled is true. 128 is the common value. |
| BlendMode | Enum | How the surface blends with what's behind it (Copy = opaque; other modes give soft/additive transparency). |
| AlphaDisplayMode | Enum | How the alpha channel is interpreted (Standard is the default). |
| InvertCulling | Bool | Flips which face is culled (front vs back). |
| FlipNormalOnBackFace | Bool | On a two-sided material, flips the normal on the back face so lighting looks correct from both sides. |
| ShadowCasterOpaque / ShadowCasterAlpha | Bool | Whether the material casts shadows (opaque vs alpha-tested). |
| ZWriteDisabledOpaque / ZWriteDisabledAlpha / ZTestDisabled | Bool | Depth-buffer controls — mostly for transparent/special materials; leave default unless you know you need them. |
| MaterialDisabled | Bool | Skips the material entirely (an easy way to hide a surface). |
Not every material stores every flag — the serialized flag set is per-game, so an AC2 material has fewer flags than an AC4 one. Edit the flags that are present; don't add ones the game doesn't write.
The Parameters list is where a material's textures and tunable shader values live. Each entry (DynamicProperty) has a NameHash (a hashed parameter name — AnvilToolkit shows the readable HashName when it knows it) and a value whose type depends on the parameter. The ones you'll come across:
- A TextureSelector — binds a texture to a shader input (this is the important one, covered next).
- A Vector4 — e.g.
DiffuseTileMap A UVScale=(30, 15, 0, 0)controls how a detail texture tiles. - A Float — e.g.
ForceWetness,NG normal detail a Strenght. - A UVTransform — controls a texture's UV coordinates:
ScaleU/ScaleV(tiling),TranslationU/TranslationV(offset),Rotation, and their animated counterpartsTranslationSpeedU/TranslationSpeedV/RotationSpeedwithAnimateTranslation/AnimateRotationtoggles. Used for scrolling or rotating textures (flowing water, moving banners, spinning emblems). - A TimeOscillator — drives a value that oscillates over time via
Frequency,Amplitude,BiasandTimeBias(plus aType). Used for pulsing effects such as a throbbing emissive glow.
The exact parameter set — and therefore which of these types appear — is defined by the material's MaterialTemplate and varies by material. Edit the parameters that are present rather than inventing new ones.
For example, the Fabric material's diffuse binding:
<DynamicProperty>
<Value Name="NameHash" Type="UInt32" HashName="Layer0_Diffuse">1560926545</Value>
<Value Name="DataType" Type="UInt32" HashName="TextureSelector">2097694221</Value>
<Object IsManagedObject="true" IsManaged="false">
<TextureSelector ID="0">
<Value Name="SpecificationMethod" Type="Enum" EnumName="TextureSpecificationMethod" ValueName="MaterialTextureSet">0</Value>
<Value Name="MapType" Type="Enum" EnumName="TextureMapType" ValueName="MapDiffuse">0</Value>
<FileReference Name="TextureSet" IsGlobal="0" Path="0">0</FileReference>
<FileReference Name="TextureMap" IsGlobal="1" Path="...\CHR_P_EdwardKenway_Fabric_DiffuseMap...TextureMap">16223198188</FileReference>
</TextureSelector>
</Object>
</DynamicProperty>This is the mechanism behind the "override" trick from Lesson 2, explained in full.
Each texture the shader needs (diffuse, normal, specular, detail tiles…) is bound by a TextureSelector inside the material's Parameters list. A selector has two important fields:
-
MapType— which channel this selector feeds:MapDiffuse=0,MapNormal=1,MapSpecular=2, … ,MapGeneric=10. -
SpecificationMethod— where the texture comes from. This is the key decision:
| SpecificationMethod | Value | Meaning |
|---|---|---|
| MaterialTextureSet | 0 | Pull this channel's texture from the material's TextureSet (the normal path). The selector's own TextureMap field is ignored by the engine. |
| OverridenTextureSet | 1 | Pull from a different whole TextureSet specified on the selector. |
| OverridenTextureMap | 2 | Use the single TextureMap specified directly on the selector, bypassing the set for this one channel. |
| ViewSurfaceTexture / ScreenshotTexture / FireItemTexture | 3 / 4 / 5 | Special engine-supplied textures; not used for normal outfit modding. |
In the Fabric material you can see both approaches side by side:
-
Layer0_Diffuse,Layer0_Normal,Layer0_MaskinguseMaterialTextureSet(0) — their textures come from theFabric_Set.TextureSet. -
DiffuseTileMap A,DiffuseTileMap B,NG_NormalTileMap AuseOverridenTextureMap(2) — each points directly at a shared detail/tiling texture, no texture set involved.
Why overrides are handy for modding: if a channel uses OverridenTextureMap, its texture ID lives right there on the selector — you change one number and you're done, no separate TextureSet file to edit. This is exactly what Lesson 2 meant by "convert a TextureSet call into an override." The trade-off: a TextureSet keeps a character's textures organised in one place, while overrides scatter them across the material.
The MaterialTemplate reference points at the file that actually defines the shading — the technique, the shader inputs, the default parameter values. In the example it's CHR_Unique.MaterialTemplate (1870870824), the template shared by unique/hero characters.
For modding you never author a template from scratch. You reuse an existing one that matches the surface type you want:
- A character-skin template for skin (these often can't do transparency — see the warning in Lesson 2).
- A cloth/fabric template for clothing.
- A metal template for weapons and armour.
If a swapped mesh looks wrong (flat, unlit, wrong response to light), a mismatched or missing MaterialTemplate is a common cause — point the material at the same template the original outfit used.
Both exporters carry identical data; they just format it differently. Which one appears by default depends on the game (Standard for pre-Origins, Schema-Based for Origins+), and you can reveal the other with Shift+right-click → Export.
| Concept | Standard Exporter | Schema-Based Exporter |
|---|---|---|
| Root (material) | <Material ID="..."> |
<Object ID="..." Type="Material"> |
| Root (texture set) | <TextureSet ID="..."> |
<Object ID="..." Type="TextureSet"> |
| Template / set refs | <FileReference Name="MaterialTemplate"> |
<Reference Name="MaterialTemplate" Type="MaterialTemplate"> |
| Flags | <Value Name="TwoSided" Type="Bool"> |
<Bool Name="TwoSided"> |
| Enums | <Value Name="BlendMode" Type="Enum" ValueName="Copy">0</Value> |
<Enum Name="BlendMode" Value="Blend_Copy">0</Enum> |
| Parameters | <List Name="Parameters" Type="DynamicProperty"> |
<DynamicProperties> |
| TextureSet slots |
named <FileReference Name="Diffuse">
|
positional <StaticArray Name="Maps"> of <Reference>
|
| Encoding | UTF-16, type="0"
|
UTF-8, type="1"
|
Recommendation: for TextureSets, use the Standard exporter — named slots make it far easier to see and edit the right channel. For Materials, either works; the flag and parameter names are readable in both.
The cleanest way to reskin a part whose material uses a TextureSet:
- Export the character's TextureSet (Shift+right-click → Standard XML export).
- Import your new image as a
TextureMap(see the Textures lesson) and note its ID. - In the TextureSet XML, put your new ID into the matching named slot:
<FileReference Name="Diffuse" IsGlobal="1" Path="...">YOUR_NEW_DIFFUSE_ID</FileReference>
- Import the XML back and repack.
Every material that shares this TextureSet now uses your texture — you edited one file, not many.
If the channel you want to change is bound with OverridenTextureMap (or you'd rather not touch the shared set), edit the material instead:
- Export the Material.
- Find the
DynamicPropertyfor the channel (e.g.Layer0_Diffuse). - In its
TextureSelector, setSpecificationMethodtoOverridenTextureMap(2) and pointTextureMapat your new texture ID:<Value Name="SpecificationMethod" ... ValueName="OverridenTextureMap">2</Value> <FileReference Name="TextureMap" IsGlobal="1" Path="...">YOUR_NEW_TEXTURE_ID</FileReference>
- Import and repack.
Both edits are required. If
SpecificationMethodis stillMaterialTextureSet(0), the engine reads the channel's texture from the material's TextureSet and ignores theTextureMapyou set on the selector — so your override silently does nothing. The method must be2for the selector'sTextureMapto be used. (If the channel is alreadyOverridenTextureMap, you only need to change the ID.)
This is the Lesson 2 tip, now with the full picture. To stop a channel resolving through the TextureSet and instead use a texture you specify directly:
- In the channel's
TextureSelector, change the method fromMaterialTextureSet(0) toOverridenTextureMap(2):<!-- Before --> <Value Name="SpecificationMethod" ... ValueName="MaterialTextureSet">0</Value> <!-- After --> <Value Name="SpecificationMethod" ... ValueName="OverridenTextureMap">2</Value>
- Set the selector's
TextureMapreference to your texture's ID (and make sureMapTypematches the channel). - Once every channel a material needs is supplied by overrides, the material no longer needs its top-level
TextureSetreference — you can set it to0, and theTextureSetfile can be removed if nothing else uses it.
This makes a material self-contained: all its textures are named right on the material, so you never have to hunt down and edit a separate TextureSet when swapping textures.
For thin surfaces (sashes, capes, flags, leaves) that disappear when viewed from behind:
<Value Name="TwoSided" Type="Bool">True</Value>If lighting looks wrong on the new back face, also confirm:
<Value Name="FlipNormalOnBackFace" Type="Bool">True</Value>Not every template supports two-sided rendering — skin templates in particular may ignore it.
To use a texture's alpha channel for hard cut-out transparency (holes, frayed edges, hair cards):
- Make sure the Diffuse texture actually has proper alpha.
- Enable alpha testing and set the cut-off:
<Value Name="AlphaTestEnabled" Type="Bool">True</Value> <Value Name="AlphaTestValue" Type="Byte">128</Value>
For soft/blended transparency instead of a hard cut-out, you additionally change the BlendMode away from Copy — but soft transparency interacts with sort order and depth writes, so start with alpha test (above), which is what most outfit pieces use.
Transparency support depends entirely on the MaterialTemplate. Most templates support only alpha test — changing
BlendModeon them does nothing (or breaks rendering), so alpha test is the reliable option. Only certain templates handle the soft/blendedBlendModemodes. And some templates — skin being the classic example — support no transparency at all, alpha test included. If your flags seem to have no effect, the template is the limiter, not your edits: to get transparency there you'd need to reassign the material to a template that supports it (see MaterialTemplate).
You don't have to start from an existing material — AnvilToolkit can generate a fresh one for you, built from a template:
- In the Game Explorer, right-click and choose New → Material. (This option only appears when a supported game and its schema are loaded.)
- A "Select MaterialTemplate file" picker opens. Choose the
.MaterialTemplatethe material will be built on. This decides the shader and which parameters the new material will have — its texture slots, floats, UV transforms, oscillators, etc. are all generated from the template's definition. (This is exactly why the template matters: it dictates what the material can do and what you can tune.) - A save dialog appears. Save it as a
.Material(binary, ready to repack) or as.xmlif you'd rather edit it first. The material's ID is generated from the file name you give it, so name it something unique. - Open the new material and fill it in: point its
TextureSet(or per-channel overrides) at your textures and set whatever flags you need, exactly as covered in the sections above.
This is the cleanest way to make a material for a custom mesh — pick the template that matches the surface (cloth, metal, skin…) and you get a correctly-structured material with all the right parameter slots already in place; you just supply the textures.
When you'd rather base a new material on an existing one (e.g. a variant of an outfit's material) than build from a template:
- Export the material to XML.
- Change the root
IDto a new number not used by any other file in the game:<Material ID="YOUR_UNIQUE_ID">
- Edit whatever you want (textures, flags).
- Import it as a new file into the data file, and point the mesh/BuildTable that should use it at
YOUR_UNIQUE_ID.
The same "give it a unique ID" rule applies to TextureSets and TextureMaps — any file you add must have an ID that doesn't collide with an existing one, because the engine resolves everything by ID.
| Concept | What it does |
|---|---|
| Material | The hub: references a template + a texture set, holds render flags, and binds textures/params via a Parameters list. |
| MaterialTemplate | The shader/technique — how the surface is lit. Reuse an existing one that matches the surface type. |
| TextureSet | A bundle of TextureMap references, one per channel (Diffuse, Normal, Specular…). Standard export names each slot. |
| TextureMap | The actual image (DDS) for one channel. |
| TextureSelector | Binds one texture to one shader channel inside a material's Parameters. |
| SpecificationMethod |
MaterialTextureSet (0) = use the set; OverridenTextureMap (2) = use a texture named directly on the selector. |
| MapType | Which channel a selector feeds (MapDiffuse, MapNormal, MapSpecular, …). |
| TwoSided | Render both faces — for thin geometry. |
| AlphaTestEnabled / AlphaTestValue | Hard cut-out transparency and its threshold (commonly 128). |
| BlendMode | Opaque (Copy) vs soft/additive blending. |
| Standard vs Schema export | Same data, different format. Prefer Standard for TextureSets (named slots). Reveal the alternate with Shift+right-click. |
| Golden rule | The engine loads by ID; Path text is just a label. Any file you add needs a unique ID. |