using System.IO; using System.Threading.Tasks; using MyInfoMate.Vr.Net; using UnityEngine; namespace MyInfoMate.Vr.Menu { /// /// Ce qu'il faut savoir pour faire d'une équirectangulaire un ciel — mis en commun /// entre la lecture 360 d'une section () et le fond immersif /// d'une visite (). /// /// Deux pièges vivent ici, et une seule fois. 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. /// public static class PanoramicSky { const string PanoramicShader = "Skybox/Panoramic"; /// /// Le matériau de ciel, ou null si le shader n'a pas survécu au build. /// /// ⚠️ Skybox/Panoramic est construit au runtime : aucune scène ne le /// référence, donc le build l'élimine s'il n'est pas dans Project Settings → /// Graphics → Always Included Shaders. 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. /// 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; } /// /// La texture d'une image équirectangulaire mise en cache, ou null. /// /// ⚠️ Mémoire. LoadImage décode en RGBA32 : une 8192×4096 occupe /// 134 Mo 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. /// public static async Task 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; } } }