Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Assets/Scenes/Main.unity
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,7 @@ Light:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 410087039}
m_Enabled: 1
serializedVersion: 12
serializedVersion: 13
m_Type: 1
m_Color: {r: 1, g: 0.85882354, b: 0.40392157, a: 1}
m_Intensity: 2
Expand Down Expand Up @@ -952,7 +952,7 @@ Light:
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShadowRadius: 0
m_ShapeRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
Expand Down Expand Up @@ -1560,6 +1560,8 @@ MonoBehaviour:
m_EditorClassIdentifier:
baseMat: {fileID: 2100000, guid: 406504d4e46794b17941d2039cfd49c4, type: 2}
facialFeaturesMat: {fileID: 2100000, guid: 640484c930a9c425791b434dd2094bff, type: 2}
matcapPresets: {fileID: 11400000, guid: e77eccfdf49b4ea09a22904b9864a2a3, type: 2}
defaultMatcapName: matcap_01
configuratorController: {fileID: 3845507387249961957}
previewController: {fileID: 1667190199235124708}
debugUrl: ?mode=jesus&profile=0x04E77bA608Cc78aD8aEFfBc60a2Ea47ABdaEA7BA
Expand Down
9 changes: 9 additions & 0 deletions Assets/Scripts/Bootstrap.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Configurator;
using DCL.Rendering.DCL_Toon;
using GLTFast;
using Preview;
using UnityEngine;
Expand All @@ -8,6 +9,12 @@ public class Bootstrap : MonoBehaviour
[SerializeField] private Material baseMat;
[SerializeField] private Material facialFeaturesMat;

[Tooltip("Shared matcap library (MatcapPresets.asset from the unity-shared-dependencies package).")]
[SerializeField] private MatcapPresets matcapPresets;

[Tooltip("Preset name bound by default to metallic materials. Must match an entry in matcapPresets.")]
[SerializeField] private string defaultMatcapName = "matcap_01";

[SerializeField] private ConfiguratorController configuratorController;
[SerializeField] private PreviewController previewController;

Expand All @@ -18,6 +25,8 @@ private void Start()
// Common assets
CommonAssets.AvatarMaterial = baseMat;
CommonAssets.FacialFeaturesMaterial = facialFeaturesMat;
CommonAssets.MatcapPresets = matcapPresets;
CommonAssets.DefaultMatcapName = defaultMatcapName;

var url = Application.isEditor ? debugUrl : Application.absoluteURL;
AangConfiguration.RecreateFrom(url);
Expand Down
9 changes: 9 additions & 0 deletions Assets/Scripts/CommonAssets.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
using DCL.Rendering.DCL_Toon;
using UnityEngine;

public static class CommonAssets
{
public static Material AvatarMaterial { get; set; }
public static Material FacialFeaturesMaterial { get; set; }

// Shared stylized-metallic matcap library (lives in the unity-shared-dependencies package).
// Assigned once at boot; read by ToonMaterialGenerator to bind the default matcap, and by the
// LocalWearableOverride test tool to resolve a matcap by name.
public static MatcapPresets MatcapPresets { get; set; }

// Name of the preset ToonMaterialGenerator binds by default to metallic materials.
public static string DefaultMatcapName { get; set; }
}
123 changes: 123 additions & 0 deletions Assets/Scripts/Loading/ToonMaterialGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Data;
using GLTFast;
using GLTFast.Logging;
Expand All @@ -16,6 +17,27 @@ public class ToonMaterialGenerator : IMaterialGenerator
private static readonly int EMISSIVE_TEX_ID = Shader.PropertyToID("_Emissive_Tex");
private static readonly int EMISSIVE_COLOR_ID = Shader.PropertyToID("_Emissive_Color");

private static readonly int NORMAL_MAP_ID = Shader.PropertyToID("_NormalMap");
private static readonly int NORMAL_MAP_ARR_ID = Shader.PropertyToID("_NormalMapArr_ID");
private static readonly int BUMP_SCALE_ID = Shader.PropertyToID("_BumpScale");

private static readonly int IS_STYLIZED_METALLIC_ID = Shader.PropertyToID("_IsStylizedMetallic");
private static readonly int METALLIC_GLOSS_MAP_ID = Shader.PropertyToID("_MetallicGlossMap");
private static readonly int METALLIC_GLOSS_ARR_ID = Shader.PropertyToID("_MetallicGlossMapArr_ID");

private static readonly int MATCAP_SAMPLER_ID = Shader.PropertyToID("_MatCap_Sampler");
private static readonly int MATCAP_ARR_ID = Shader.PropertyToID("_MatCap_SamplerArr_ID");
private static readonly int MATCAP_COLOR_ID = Shader.PropertyToID("_MatCapColor");
private static readonly int BLUR_LEVEL_MATCAP_ID = Shader.PropertyToID("_BlurLevelMatcap");

// Warn only once when the shared matcap library isn't wired up, to avoid per-material spam.
private static bool _warnedMissingPresets;

// Cache of 1x1 masks keyed by quantized metallic value, so a uniform metallicFactor can flow
// through the same .b mask-sampling path without allocating a texture per material. Bounded to
// <= 256 tiny textures for the session; shared, so no per-material leak.
private static readonly Dictionary<byte, Texture2D> UniformMetallicMasks = new();

private static readonly int TWEAK_TRANSPARENCY_ID = Shader.PropertyToID("_Tweak_transparency");
private static readonly int CLIPPING_LEVEL_ID = Shader.PropertyToID("_Clipping_Level");
private static readonly int Z_WRITE_MODE_ID = Shader.PropertyToID("_ZWriteMode");
Expand Down Expand Up @@ -59,6 +81,60 @@ public Material GenerateMaterial(int materialIndex, GLTFast.Schema.Material gltf
mat.SetTexture(EMISSIVE_TEX_ID, gltf.GetTexture(gltfMaterial.emissiveTexture.index));
}

// Normal map + stylized-metallic mask are body-only features (facial features are flat).
if (!isFacialFeature)
{
// Normal map — read straight from the GLB. glTFast imports textures referenced through
// the glTF `normalTexture` slot as linear (see GltfImport.SetImageGamma), so GetTexture
// returns a correctly-sampled normal. The DCL_Toon non-array path samples _NormalMap;
// _NormalMapArr_ID gates it (>= 0 = enabled, -1 = fall back to the geometric normal).
if (gltfMaterial.normalTexture != null && gltfMaterial.normalTexture.index != -1)
{
mat.SetTexture(NORMAL_MAP_ID, gltf.GetTexture(gltfMaterial.normalTexture.index));
mat.SetInteger(NORMAL_MAP_ARR_ID, 0);
// NOTE: _BumpScale is a compile-time constant in DCL_ToonVariables.hlsl, so this is a
// no-op until that constant is promoted to a runtime property (see
// CHANGES_NormalMap_StylizedMetallic.md). Set anyway so it's correct once promoted.
mat.SetFloat(BUMP_SCALE_ID, gltfMaterial.normalTexture.scale);
}
else
{
mat.SetInteger(NORMAL_MAP_ARR_ID, -1);
}

// Stylized-metallic mask — "where do we put the matcap". Driven by the GLB's PBR
// metallic, which is how creators express it (Blender's Metallic slider = metallicFactor):
// - metallicRoughnessTexture -> per-texel mask, metallic in the .b channel (glTF ORM);
// - else metallicFactor > 0 -> uniform amount, baked into a flat mask so it flows
// through the same .b path (honors 0..1).
// Non-metal materials carry an explicit metallicFactor = 0, so they stay off. The shader
// also needs _MatCap_SamplerArr_ID >= 0 to render metal, so metallic materials get the
// default matcap from the shared MatcapPresets library bound here (see ApplyDefaultMatcap);
// the debug harness (LocalWearableOverride) can still override it at the renderer level.
var pbr = gltfMaterial.pbrMetallicRoughness;
var hasMetalTex = pbr.metallicRoughnessTexture != null && pbr.metallicRoughnessTexture.index != -1;

if (hasMetalTex)
{
mat.SetTexture(METALLIC_GLOSS_MAP_ID, gltf.GetTexture(pbr.metallicRoughnessTexture.index));
mat.SetInteger(METALLIC_GLOSS_ARR_ID, 0);
mat.SetInteger(IS_STYLIZED_METALLIC_ID, 1);
ApplyDefaultMatcap(mat);
}
else if (pbr.metallicFactor > 0f)
{
mat.SetTexture(METALLIC_GLOSS_MAP_ID, GetUniformMetallicMask(pbr.metallicFactor));
mat.SetInteger(METALLIC_GLOSS_ARR_ID, 0);
mat.SetInteger(IS_STYLIZED_METALLIC_ID, 1);
ApplyDefaultMatcap(mat);
}
else
{
mat.SetInteger(METALLIC_GLOSS_ARR_ID, -1);
mat.SetInteger(IS_STYLIZED_METALLIC_ID, 0);
}
}

// Alpha
if (isFacialFeature)
{
Expand Down Expand Up @@ -98,6 +174,53 @@ public Material GenerateMaterial(int materialIndex, GLTFast.Schema.Material gltf
}


// Binds the default stylized-metal matcap and opens the shader gate (_MatCap_SamplerArr_ID >= 0)
// so metallic materials render without an external matcap being supplied. The matcap (and its
// tint/blur) come from the shared MatcapPresets library (CommonAssets.MatcapPresets), resolved
// by name (CommonAssets.DefaultMatcapName) with a fall back to the first preset. A missing or
// empty library degrades gracefully (metal simply stays unlit) with a one-time warning.
private static void ApplyDefaultMatcap(Material mat)
{
var presets = CommonAssets.MatcapPresets;
if (presets == null || presets.Count == 0)
{
if (!_warnedMissingPresets)
{
Debug.LogWarning("[ToonMaterialGenerator] No MatcapPresets assigned " +
"(CommonAssets.MatcapPresets is null/empty); stylized metallic will " +
"not render until one is set on the Bootstrap component.");
_warnedMissingPresets = true;
}

return;
}

if (!presets.TryGet(CommonAssets.DefaultMatcapName, out var preset))
preset = presets[0];

mat.SetTexture(MATCAP_SAMPLER_ID, preset.texture);
mat.SetInteger(MATCAP_ARR_ID, 0);
mat.SetColor(MATCAP_COLOR_ID, preset.tint);
mat.SetFloat(BLUR_LEVEL_MATCAP_ID, preset.blur);
}


// Returns a shared 1x1 linear texture whose channels all equal the (quantized) metallic value,
// so the shader's .b mask read yields that uniform amount. Cached per value.
private static Texture2D GetUniformMetallicMask(float metallic)
{
var key = (byte)Mathf.Clamp(Mathf.RoundToInt(metallic * 255f), 0, 255);
if (UniformMetallicMasks.TryGetValue(key, out var tex) && tex != null)
return tex;

var v = key / 255f;
tex = new Texture2D(1, 1, TextureFormat.RGBA32, false, true) { name = $"UniformMetallicMask_{key}" };
tex.SetPixel(0, 0, new Color(v, v, v, 1f));
tex.Apply(false, true);
UniformMetallicMasks[key] = tex;
return tex;
}

private static bool IsFacialFeature(string gltfMaterialName)
{
return gltfMaterialName is "AvatarEyebrows_MAT" or "AvatarEyes_MAT" or "AvatarMouth_MAT"
Expand Down
3 changes: 3 additions & 0 deletions Assets/StreamingAssets/character/PuffyJacket.glb
Git LFS file not shown
7 changes: 7 additions & 0 deletions Assets/StreamingAssets/character/PuffyJacket.glb.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Packages/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"dependencies": {
"com.atteneder.gltfast": "https://github.com/decentraland/unity-gltf.git",
"com.decentraland.unity-shared-dependencies": "git@github.com:decentraland/unity-shared-dependencies.git#main",
"com.decentraland.unity-shared-dependencies": "git@github.com:decentraland/unity-shared-dependencies.git#feat/toon-normalmap-stylized-metallic",
"com.unity.cinemachine": "3.1.4",
"com.unity.ide.rider": "3.0.39",
"com.unity.ide.visualstudio": "2.0.27",
Expand Down
4 changes: 2 additions & 2 deletions Packages/packages-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
"hash": "e4587451389b6cf93b084ae434d0cc3d32a45076"
},
"com.decentraland.unity-shared-dependencies": {
"version": "git@github.com:decentraland/unity-shared-dependencies.git#main",
"version": "git@github.com:decentraland/unity-shared-dependencies.git#feat/toon-normalmap-stylized-metallic",
"depth": 0,
"source": "git",
"dependencies": {},
"hash": "f040030e82c51bdddce67ae18149e22b5b4c4c0d"
"hash": "9eda18fb6e1f6112a568d4a82e8ff69871ea8ba6"
},
"com.unity.burst": {
"version": "1.8.28",
Expand Down
Loading