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

82 lines
3.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.IO;
using System.Threading.Tasks;
using MyInfoMate.Vr.Net;
using UnityEngine;
namespace MyInfoMate.Vr.Menu
{
/// <summary>
/// Ce qu'il faut savoir pour faire d'une équirectangulaire un ciel — mis en commun
/// entre la lecture 360 d'une section (<see cref="SkyboxView"/>) et le fond immersif
/// d'une visite (<see cref="ImmersiveBackdrop"/>).
///
/// <b>Deux pièges vivent ici, et une seule fois.</b> Le shader absent du build (ciel
/// magenta sur le casque, correct dans l'éditeur) et la texture décodée en RGBA32
/// (134 Mo pour une 8192×4096) sont exactement le genre de choses qu'on corrige d'un
/// côté en oubliant l'autre.
/// </summary>
public static class PanoramicSky
{
const string PanoramicShader = "Skybox/Panoramic";
/// <summary>
/// Le matériau de ciel, ou null si le shader n'a pas survécu au build.
///
/// ⚠️ <c>Skybox/Panoramic</c> est construit au runtime : aucune scène ne le
/// référence, donc le build l'élimine s'il n'est pas dans <i>Project Settings →
/// Graphics → Always Included Shaders</i>. Le symptôme est un ciel magenta sur le
/// casque, et rien du tout dans l'éditeur — c'est le piège déjà payé avec glTFast.
/// </summary>
public static Material CreateMaterial()
{
var shader = Shader.Find(PanoramicShader);
if (shader == null)
{
Debug.LogError($"[Sky] Shader « {PanoramicShader} » absent du build. " +
"Project Settings → Graphics → Always Included Shaders.");
return null;
}
var material = new Material(shader);
material.SetFloat("_Mapping", 1f); // Latitude-longitude (équirectangulaire)
material.SetFloat("_ImageType", 0f); // 360°, pas 180°
return material;
}
/// <summary>
/// La texture d'une image équirectangulaire mise en cache, ou null.
///
/// ⚠️ <b>Mémoire.</b> <c>LoadImage</c> décode en RGBA32 : une 8192×4096 occupe
/// <b>134 Mo</b> de mémoire graphique, pour une seule image, sur un casque qui en
/// a quelques Go partagés avec le système. La compression matérielle (ASTC sur
/// Quest) divise ça par six à huit, au prix d'une seconde au chargement et d'une
/// perte invisible sur une photo. C'est elle qui décide si on peut enchaîner
/// plusieurs 360 sans que l'app se fasse tuer.
/// </summary>
public static async Task<Texture2D> LoadEquirectangularAsync(ApiClient client, string url)
{
var path = await ContentCache.MediaPathAsync(client, url);
if (path == null) return null;
// Pas de mipmaps : une équirectangulaire est vue à taille réelle, et les
// niveaux intermédiaires coûteraient un tiers de mémoire pour rien.
var texture = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false);
if (!texture.LoadImage(File.ReadAllBytes(path)))
{
Debug.LogError($"[Sky] Image illisible : {url}");
Object.Destroy(texture);
return null;
}
// Sans cela, la couture verticale du panorama apparaît comme un trait net.
texture.wrapModeU = TextureWrapMode.Repeat;
texture.wrapModeV = TextureWrapMode.Clamp;
texture.Compress(highQuality: true);
texture.Apply(updateMipmaps: false, makeNoLongerReadable: true);
return texture;
}
}
}