vr-app/unity-overlay/Assets/Scripts/Editor/IncludeRuntimeShaders.cs
Thomas Fransolet 0310d28b5e Canal VR Meta Quest : projet Unity, viewer web et documentation
Premier commit du cinquième front. Trois parties :

- `unity/MyInfoMateVR/` : le projet Unity (6000.0.83f1, URP, Meta XR SDK 205),
  un APK unique pour tous les clients. Menu flottant à sélection au regard,
  appairage, chargement de scène GLB, POI, cache de contenu, télémétrie.
- `unity-overlay/` : les mêmes scripts à recopier sur un projet Unity neuf, avec
  les pièges rencontrés consignés dans son README.
- `viewer/` : viewer et éditeur de scène web autonome (Vite, TypeScript,
  three.js), partagé avec les autres fronts.
- `docs/` : état des lieux, setup Unity, décisions d'architecture et plan
  d'exécution en 9 étapes.

La scène est décrite par un `scene.json` poussé par `adb push` : l'app le
préfère à celui embarqué dans l'APK.

Les binaires (GLB, textures de l'échantillon Sponza, DLL Meta XR) passent par
Git LFS dès ce premier commit — les y faire entrer après coup demanderait de
réécrire l'historique. Les artefacts régénérés par l'éditeur et par CMake
(`Library/`, `.utmp/`, Burst debug) sont ignorés.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 15:26:07 +02:00

130 lines
5.6 KiB
C#

using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace MyInfoMate.Vr.EditorTools
{
/// <summary>
/// Déclare dans <b>Always Included Shaders</b> les shaders que ce projet ne
/// charge que par code.
///
/// <b>Le problème que ça règle.</b> Unity n'embarque dans un build que les
/// shaders référencés par un matériau présent dans une scène ou dans
/// <c>Resources</c>. Un shader obtenu par <c>Shader.Find</c> au runtime n'est
/// référencé nulle part au moment du build : il est retiré. Dans l'éditeur tout
/// est disponible, donc <b>tout est correct en Play Mode et magenta sur le
/// casque</b> — la combinaison la plus coûteuse à diagnostiquer.
///
/// C'est le cas de tous les shaders listés ici :
/// <list type="bullet">
/// <item>glTFast résout ses shaders par <c>Shader.Find("Shader Graphs/…")</c>
/// (<c>ShaderGraphMaterialGenerator.cs:594</c>) — donc tout GLB chargé au
/// runtime, c'est-à-dire <i>tout le contenu de l'app</i> ;</item>
/// <item><c>NavigationBounds</c> et <c>HotspotInstance</c> font le même appel
/// pour URP/Unlit ;</item>
/// <item><c>SceneMessage</c> utilise un <c>TextMesh</c>, dont le matériau par
/// défaut pointe sur <c>GUI/Text Shader</c>. Sans lui, le message d'erreur
/// destiné au visiteur serait lui-même illisible.</item>
/// </list>
///
/// Fait par script et pas à la main pour la même raison que
/// <see cref="BuildBootScene"/> : c'est versionné, rejouable, et ça survit à une
/// réimportation du projet.
/// </summary>
public static class IncludeRuntimeShaders
{
/// <summary>
/// Les noms sont ceux que le code résout au runtime, relevés dans les
/// sources — pas devinés. Un nom faux ici est silencieux : le script dira
/// « introuvable » au lieu d'ajouter le shader.
/// </summary>
static readonly string[] RequiredShaders =
{
// glTFast en URP — le rendu de tout GLB chargé au runtime
"Shader Graphs/glTF-pbrMetallicRoughness",
"Shader Graphs/glTF-unlit",
"Shader Graphs/glTF-pbrSpecularGlossiness",
// Repères et garde-fous dessinés par code
"Universal Render Pipeline/Unlit",
// ⚠️ Surtout pas "Universal Render Pipeline/Lit" : 2 359 296 variantes,
// au-delà de ce qu'Unity accepte d'embarquer — le build échoue sur
// « has too many Shader variants » après 35 minutes de compilation.
// Aucun Shader.Find du projet ne le demande : tout ce qui est dessiné
// par code est en Unlit, et les GLB passent par les graphes glTFast.
// La 360° (E6) — même piège que les autres : magenta sur le casque,
// correct dans l'éditeur
"Skybox/Panoramic"
// ⚠️ Pas de "GUI/Text Shader" non plus, et pour une raison différente :
// il vit dans « Library/unity default resources », marqué
// HideFlags.DontSave. Le mettre dans cette liste fait échouer le build
// sur « An asset is marked with HideFlags.DontSave but is included in
// the build » puis « Failed to write file: …/unity_builtin_extra ».
// Le TextMesh de SceneMessage n'en a pas besoin : son matériau vient de
// la police référencée par le composant, qui est embarquée normalement.
};
[MenuItem("MyInfoMate/Embarquer les shaders du runtime")]
public static void Include()
{
var settings = AssetDatabase
.LoadAllAssetsAtPath("ProjectSettings/GraphicsSettings.asset")
.FirstOrDefault();
if (settings == null)
{
Debug.LogError("[Shaders] GraphicsSettings.asset introuvable.");
return;
}
var serialized = new SerializedObject(settings);
var list = serialized.FindProperty("m_AlwaysIncludedShaders");
var already = new HashSet<Shader>();
for (var i = 0; i < list.arraySize; i++)
{
if (list.GetArrayElementAtIndex(i).objectReferenceValue is Shader shader)
already.Add(shader);
}
var added = 0;
var missing = new List<string>();
foreach (var name in RequiredShaders)
{
var shader = Shader.Find(name);
if (shader == null)
{
// Un shader introuvable dans l'éditeur signale soit un package
// absent, soit un renommage en amont. Dans les deux cas, se
// taire reviendrait à livrer le magenta.
missing.Add(name);
continue;
}
if (!already.Add(shader)) continue;
list.InsertArrayElementAtIndex(list.arraySize);
list.GetArrayElementAtIndex(list.arraySize - 1).objectReferenceValue = shader;
added++;
}
serialized.ApplyModifiedProperties();
AssetDatabase.SaveAssets();
Debug.Log($"[Shaders] {added} shader(s) ajouté(s), " +
$"{already.Count} au total dans Always Included Shaders.");
if (missing.Count > 0)
Debug.LogError("[Shaders] Introuvables, donc toujours magenta au build : " +
string.Join(", ", missing) +
". Vérifier que le package correspondant est bien importé.");
}
}
}