Aller au contenu

FOS Mirror

Version 1.0.1 · exige FOS Essentials Core 1.0.0 ou plus récent

Allume et éteint les miroirs — par distance, par bouton, par interaction — depuis un unique manager central, avec des presets de qualité et une estimation indicative du coût. Exige FOS Essentials Core 1.0.0.

Les miroirs sont la première chose qui tue le framerate d’un monde VRChat. Ce pack ne les rend pas bon marché : rien ne le peut. Il les rend maîtrisés — éteints par défaut, allumés seulement là et quand quelqu’un l’a réellement demandé.


  1. Créez un GameObject vide et ajoutez FOS Essentials > Mirror > Mirror Manager.
  2. Cliquez sur Scan the scene. Chaque miroir VRChat reçoit un FOSMirrorTarget, son composant miroir et son manager sont câblés, et la liste du manager est remplie.
  3. Choisissez le Mode : exclusif, ou chaque miroir indépendant.
  4. Ajoutez un contrôle là où un joueur doit pouvoir commuter un miroir : Mirror Control (UI Button) ou Mirror Control (Interact).

Relancez le scan après avoir ajouté des miroirs. Les cibles existantes ne sont jamais écrasées : tout réglage manuel survit.


Composant Rôle
FOSMirrorManager Un par scène. Possède la boucle, le test de distance et le mode exclusif.
FOSMirrorTarget Un par miroir. Porte les réglages et l’état allumé/éteint — aucun travail par frame.
FOSMirrorButton Commute un miroir depuis un Button d’interface. Câble le OnClick pour vous.
FOSMirrorInteract Commute un miroir quand un joueur utilise l’objet qui le porte.

Le pack ne déclare volontairement aucun inspecteur sur mesure pour VRC_MirrorReflection. Unity n’en accepte qu’un par type, et VRWorldToolkit en livre déjà un. Tout passe ici par le composant FOS, si bien que les deux outils continuent de fonctionner côte à côte.


Le composant VRC_MirrorReflection, par son .enabled — jamais le GameObject.

SetActive(false) relancerait tous les OnEnable de la hiérarchie, Udon compris, ce qui peut réinitialiser l’état de scripts sans rapport. Cela masquerait aussi l’objet miroir lui-même, alors qu’un miroir est en général un cadre décoré qui doit rester visible quand il s’éteint.

Les Extra components et les Extra colliders sont commutés avec lui.

Collider ne dérive pas de Behaviour — il dérive directement de Component, avec sa propre propriété enabled. C’est pourquoi les colliders ont leur propre liste.

Ne mettez jamais dans cette liste le collider qui sert à rallumer un miroir : il serait désactivé en même temps que lui et le miroir ne pourrait plus jamais être rallumé. L’inspecteur de la cible signale ce cas comme une erreur.


Désactiver le composant miroir arrête le reflet, mais la surface continue d’être rendue — et un shader de miroir sans rien à refléter dessine du noir pur. Un miroir éteint est donc un rectangle noir, sauf indication contraire.

Deux champs de la cible décident de l’apparence :

Mirror surface Off material Résultat
vide — Le rectangle noir. Rien n’est touché.
renseigné vide La surface est masquée. Son draw call disparaît aussi.
renseigné renseigné La surface bascule sur ce matériau : verre sombre, panneau dépoli, plaque peinte.

Le scan remplit Mirror surface avec le renderer posé sur le miroir : le masquage est donc le comportement par défaut. Relancez le scan sur une scène plus ancienne pour en profiter, ou utilisez le bouton Use the mirror’s renderer dans l’inspecteur de la cible.

Le pack livre un matériau d’extinction tout prêt, Materials/FOSMirrorOff.mat — une plaque sombre et brillante. Il utilise le shader Standard d’Unity, choisi pour que le matériau ne dépende de rien d’extérieur à Unity. Sur Quest, remplacez son shader par un shader mobile tel que VRChat/Mobile/Standard Lite, ou laissez simplement Off material vide : masquer la surface ne coûte rien et ne demande aucun matériau.

La bascule écrit renderer.material, jamais sharedMaterial — écrire sharedMaterial à l’exécution modifie l’asset matériau sur le disque, ce qui, dans ClientSim, laisserait le matériau du miroir définitivement écrasé dans votre projet.

Renderer dérive de Component, pas de Behaviour, et porte sa propre propriété enabled — la même raison qui donne aux colliders leur propre liste.

Le matériau « allumé » est capturé depuis le renderer au démarrage du monde. Ne laissez pas le matériau d’extinction affecté au renderer dans la scène : il serait capturé comme apparence du miroir et le reflet ne reviendrait jamais. L’inspecteur signale ce cas comme une erreur.

Cela vaut aussi pour le culling par distance, ce qui est en général ce que l’on veut : un miroir hors de portée perd à la fois son reflet et sa surface.


Un miroir n’est rendu que si les deux sont vraies :

Moitié Propriétaire Décidé par
État manuel — quelqu’un veut-il qu’il soit allumé ? La cible Bouton, interaction, startsOn
Portée — le joueur est-il assez près ? Le manager Test de distance, budget par frame

Un miroir que le joueur a éteint reste éteint, aussi près qu’il s’approche. La portée n’allume jamais un miroir d’elle-même — elle laisse seulement revenir celui qui est déjà allumé.

Même logique que FOS UI Culling : distances au carré, deux seuils, vérifications à tour de rôle, et une écriture uniquement quand l’état change réellement.

Option Effet
Default distance Mètres en deçà desquels un miroir peut être rendu, sauf surcharge par la cible.
Show distance (cible) Surcharge par miroir. -1 signifie « hériter de la valeur du manager ».
Distance reference (cible) Point depuis lequel la distance est mesurée. Vide signifie le miroir lui-même.
Hysteresis Mètres supplémentaires avant de s’éteindre à nouveau.
Checks per frame Nombre de miroirs testés à chaque image. 2 par défaut.

L’inspecteur de la cible énonce le résultat — « Effective distance: 12 m (inherited from the manager) » — pour que vous n’ayez jamais à vous rappeler ce que veut dire -1.

Les distances sont lues une seule fois au Start. En changer une à l’exécution demande d’appeler RefreshTargets() sur le manager.

Un miroir que le joueur vient d’allumer est mesuré immédiatement plutôt que d’attendre son tour : se tenir devant un miroir qui met quatre images à apparaître donne une impression de panne.


Mode Comportement
Free Chaque miroir est indépendant. Rien n’empêche un joueur de tous les allumer.
Exclusive En allumer un éteint tous les autres.

Le mode exclusif est le réglage le plus efficace de ce pack, parce que le second facteur de coût d’un monde est le nombre de miroirs rendus en même temps — et que ce nombre est sinon décidé par le premier curieux venu.

Il est également appliqué au démarrage : si plusieurs miroirs sont réglés pour démarrer allumés, seul le premier l’est, et l’inspecteur du manager le dit.


Portée Réseau Qui paie
Local Aucun Seulement le joueur qui a commuté
Synchronized [UdonSynced], synchro manuelle Tout le monde dans l’instance

Local est la valeur par défaut, et c’est la bonne pour un miroir public : un joueur qui veut vérifier son avatar en obtient un, et personne d’autre ne le paie.

Synchronized est fait pour un miroir qui appartient au monde — une scène de danse, un vestiaire commun. L’ownership est pris avant l’écriture de la variable, et l’état est reconstruit pour les joueurs qui arrivent plus tard depuis OnDeserialization.

Un SendCustomNetworkEvent n’est pas rejoué pour les arrivants tardifs. C’est pourquoi l’état vit dans une variable synchronisée et non dans un événement.

Comme l’état est stocké sur la cible, les deux portées se mélangent librement dans une même scène. En mode exclusif, sachez ce que ce mélange implique : éteindre un miroir local ne l’éteint que pour vous.

La synchro manuelle est limitée à environ une sérialisation par seconde et par comportement. Allumer un miroir synchronisé en mode exclusif écrit aussi dans les autres miroirs synchronisés : évitez donc de rendre une douzaine de miroirs synchronisés quand un seul en a besoin.


Deux réglages du miroir VRChat sont exposés sur la cible : les layers reflétés et la désactivation des lumières par pixel.

Preset Layers reflétés
Untouched (défaut) Rien n’est appliqué ; le miroir garde ses propres réglages.
Players only Player + MirrorReflection — les avatars sur la skybox.
Players and world Default + Player + Environment + MirrorReflection.
World only Default + Environment. Aucun avatar n’est jamais dessiné deux fois.
Nothing Un masque vide. Le miroir rend un reflet vide.
Custom Ce que vous réglez à la main dans Reflected layers.

Le preset se résout dans le champ Reflected layers dès que vous le choisissez : le masque que vous voyez est donc celui qui sera appliqué. Le manager l’écrit dans le miroir au Start ; l’inspecteur de la cible propose aussi Apply to the mirror now pour l’écrire depuis l’éditeur, ce qui est pratique quand vous voulez que les vérifications de VRWorldToolkit le voient.

Untouched existe parce qu’un outil ne doit jamais écraser en silence des réglages faits à la main. Rien n’est écrit tant que vous n’avez pas choisi un preset.

L’inspecteur avertit des erreurs classiques : le masque par défaut laissé intact, UiMenu reflété, PlayerLocal reflété, et les joueurs reflétés sans le layer MirrorReflection, ce qui fait que les joueurs se voient entre eux mais pas eux-mêmes.


Les inspecteurs de la cible et du manager affichent une note indicative sur 100. C’est une estimation pondérée, pas une mesure, et elle est construite dans cet ordre :

  1. Les layers reflétés, facteur dominant. Un miroir dessine la scène une seconde fois — deux fois en VR. Ce qu’il coûte, c’est donc ce qu’il redessine. Refléter les avatars double l’élément le plus lourd d’une instance peuplée, et cet élément grandit avec le nombre de joueurs, pas avec le monde. Un miroir « monde seul » coûte une fraction du même miroir reflétant les joueurs, à taille et position identiques.

  2. Le nombre de miroirs pouvant être rendus en même temps. Deux miroirs allumés font deux passes de rendu supplémentaires ; rien n’est partagé entre eux. Le mode exclusif supprime ce facteur d’emblée, si bien que la note de la scène retient le pire miroir plutôt que la somme.

  3. La couverture d’écran, en dernier et faiblement pondérée. VRChat rend un miroir dans une texture dimensionnée par la résolution du casque, pas par le miroir : un grand miroir ne coûte donc pas mécaniquement plus qu’un petit. La taille compte indirectement — un grand miroir est visible de plus loin, se remplit d’avatars plus souvent et se cull moins bien par la distance.

Servez-vous de cette note pour comparer deux réglages d’un même monde. Ce n’est pas une valeur absolue et elle ne remplace pas un profilage.


Sélectionner un miroir dessine son plan, une flèche le long de la face réfléchissante et une croix rouge sur le dos. Un miroir est un objet plat : rien dans la vue de scène ne distingue un miroir correctement placé d’un miroir tourné de 180°, et on s’en aperçoit d’ordinaire en jeu, planté devant un rectangle noir.

La normale est lue depuis le maillage, pas devinée depuis le transform : elle reste donc juste quel que soit le maillage utilisé par le miroir.

Le gizmo vit dans un script éditeur utilisant [DrawGizmo]. Ce n’est volontairement pas un OnDrawGizmos dans le comportement Udon : Udon n’appelle jamais cette méthode, et UdonSharp devrait tout de même la compiler — avec Gizmos et Handles, qui ne sont pas exposés.


Chaque point d’entrée public est sans paramètre : n’importe quel script peut donc l’atteindre avec SendCustomEvent.

Appel Sur Effet
FOSRequestToggle() FOSMirrorTarget Inverse le miroir
FOSRequestOn() / FOSRequestOff() FOSMirrorTarget Force un état
TriggerMirror() FOSMirrorButton / FOSMirrorInteract Exécute l’action du contrôle
FOSTurnAllOff() FOSMirrorManager Bouton panique : éteint tous les miroirs
RefreshTargets() FOSMirrorManager Reconstruit les caches après un changement de scène

Le câblage OnClick dont a besoin un bouton d’interface est SendCustomEvent("TriggerMirror") sur l’UdonBehaviour du contrôle. Le réussir à la main suppose de viser l’UdonBehaviour plutôt que le composant visible et de taper le nom de l’événement sans faute — une faute de frappe ne lève aucune erreur, le bouton ne fait simplement rien. L’inspecteur le câble en un clic et vous dit s’il l’est déjà.


  • Un manager par scène. Plusieurs managers se disputeraient les mêmes miroirs ; l’inspecteur le signale comme une erreur.
  • Les miroirs hérités sont ignorés. Seul le composant miroir du SDK3 est exposé à Udon. Le scan les compte et vous le dit.
  • Les miroirs créés à l’exécution ne sont pas pris en compte automatiquement. Remplissez targets et appelez RefreshTargets().
  • Un miroir sans manager ne peut pas être commuté. Le scan le lui affecte ; l’inspecteur de la cible le signale quand il manque.
  • Quest et PC se comportent à l’identique ici — il n’y a pas de code spécifique à une plateforme, et il ne pourrait pas y en avoir : Udon est compilé une seule fois pour les deux.