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é.
Mise en place
Section intitulée « Mise en place »- Créez un GameObject vide et ajoutez FOS Essentials > Mirror > Mirror Manager.
- 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. - Choisissez le Mode : exclusif, ou chaque miroir indépendant.
- 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.
Composants
Section intitulée « Composants »| 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.
Ce qui est réellement commuté
Section intitulée « Ce qui est réellement commuté »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.
Colliderne dérive pas deBehaviour— il dérive directement deComponent, 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.
À quoi ressemble un miroir éteint
Section intitulée « À quoi ressemble un miroir éteint »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.
Rendererdérive deComponent, pas deBehaviour, 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.
Les deux moitiés de l’état d’un miroir
Section intitulée « Les deux moitiés de l’état d’un miroir »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é.
La distance
Section intitulée « La distance »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.
Les modes
Section intitulée « Les modes »| 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 de la commutation, miroir par miroir
Section intitulée « Portée de la commutation, miroir par miroir »| 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
SendCustomNetworkEventn’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.
La qualité
Section intitulée « La qualité »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.
L’estimation du coût
Section intitulée « L’estimation du coût »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 :
-
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.
-
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.
-
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.
Le gizmo
Section intitulée « Le gizmo »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.
Scripting
Section intitulée « Scripting »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à.
Notes et limites
Section intitulée « Notes et limites »- 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
targetset appelezRefreshTargets(). - 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.
