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>
82 lines
3.6 KiB
C#
82 lines
3.6 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|