Guide de développement du décalque projeté vidéo
Aperçu de la fonctionnalité
Le décalque projeté vidéo est une extension du décalque projeté : le contenu projeté n'est plus une image statique, mais une vidéo. L'approche consiste à décoder la vidéo avec le VideoPlayer intégré de Unity et à mettre à jour dynamiquement chaque image sur la texture du décalque, de sorte que la vidéo soit projetée sur la surface du nuage de points 3DGS comme un projecteur de diapositives.
Comme le décalque lui-même est une projection en perspective fixée dans l'espace mondial, l'image vidéo est « collée » sur la surface du nuage de points selon la pose du projecteur. Les points à l'intérieur du frustum du projecteur sont colorés par l'image vidéo actuelle, et les points à l'extérieur du frustum ne sont pas affectés.
Remarque :
Comme le décalque normal, le décalque vidéo ne prend effet que sur PC / Mac (pipeline URP). L'appel des interfaces associées sur les plateformes mobiles ou VR est ignoré par le SDK et affiche un avertissement.
Ce guide n'explique que les parties incrémentales liées à la « vidéo ». Pour les bases — configuration de la scène (Caméra principale / LCCManager / LCCRenderer / Projector), ajout de la Decal Depth Feature au Renderer URP, réglage de l'occlusion culling et les API détaillées du décalque — veuillez d'abord lire le Guide de développement du décalque projeté.
Principe de mise en œuvre
L'interface de décalque du SDK SetDecalTexture(Texture2D tex) n'accepte que Texture2D, mais elle prend en charge la mise à jour dynamique de cette texture à l'exécution. La sortie du décodage de VideoPlayer de Unity est un RenderTexture (sur le GPU). Par conséquent, tout le pipeline de données est :
VideoPlayer décode → RenderTexture (GPU) → copie vers Texture2D → SetDecalTexture(texture d'image) → projection sur le nuage de points
Il y a deux points clés, qui sont également les deux « alignements » que ce guide met en avant :
- Alignement spatial : la position, la taille et l'orientation de l'image vidéo projetée sur le nuage de points sont déterminées par les paramètres du projecteur (pose, FOV, Aspect, Near/Far) ; voir Alignement spatial de la vidéo et de la scène.
- Alignement temporel : la fréquence d'images de la vidéo est souvent bien inférieure à la fréquence d'images de rendu de la scène (la scène peut tourner à des centaines de FPS), vous ne pouvez donc pas copier et pousser la texture une fois par image de rendu. Utilisez
VideoPlayer.framepour détecter si une nouvelle image décodée est apparue, et ne mettez à jour que lorsqu'une nouvelle image arrive ; voir Aligner la fréquence d'images de la vidéo et la fréquence d'images de rendu.

Pipeline de données du décalque projeté vidéo : VideoPlayer → RenderTexture → Texture2D → SetDecalTexture
Prérequis
Le décalque vidéo partage le même ensemble de prérequis que le décalque normal. Avant de commencer, confirmez ce qui suit :
- La scène comporte déjà Caméra principale, LCCManager, LCCRenderer et Projector configurés (voir Guide de développement du décalque projeté · Étape 1).
- Le pipeline URP est actuellement utilisé ; si l'occlusion culling est nécessaire, la Decal Depth Feature a été enregistrée dans l'URP Renderer Asset.
- Si la Console signale des erreurs et qu'aucun effet n'est visible, désactivez le mode de compatibilité Render Graph (voir la section FAQ du Guide de développement du décalque projeté).
- Préparez un actif vidéo : il peut s'agir d'un actif
VideoClipà l'intérieur du projet, ou d'une URL de fichier vidéo sur disque / réseau (par ex.file:///D:/sample.mp4).
Étape 1 : Écrire le script
Créez un nouveau script C# nommé VideoStreaming.cs. Basé sur le squelette du Decal.cs normal, il remplace la « texture de décalque statique » par « la création d'un VideoPlayer à l'exécution pour décoder la vidéo, et la mise à jour de la texture du décalque dans Update() selon les images décodées ». Il peut être copié directement dans votre projet.
using LCCCore;
using UnityEngine;
using UnityEngine.Video;
public class VideoStreaming : MonoBehaviour
{
[Header("Requis")]
[Tooltip("L'instance LCCManager de la scène")]
public LCCManager m_manager;
[Tooltip("Un chemin de scène LCC valide")]
public string m_filePath;
[Header("Source vidéo (les images décodées sont projetées comme décalque)")]
[Tooltip("Clip vidéo à projeter. Prioritaire sur l'URL ci-dessous")]
public VideoClip m_videoClip;
[Tooltip("Chemin / URL du fichier vidéo, utilisé lorsqu'aucun clip n'est assigné (par ex. file:///D:/xxx.mp4)")]
public string m_videoUrl;
[Tooltip("Lecture en boucle")]
public bool m_videoLoop = true;
[Header("Projecteur (projette le long de +Z)")]
[Tooltip("Pose du projecteur. Laissez vide pour utiliser le transform de cet objet")]
public Transform m_projector;
[Range(1f, 179f)] public float m_fov = 60f;
[Tooltip("Ratio d'aspect de la projection ; <=0 utilise automatiquement le ratio d'aspect de la vidéo")]
public float m_aspect = 0f;
public float m_near = 0.1f;
public float m_far = 100f;
[Header("Apparence du décalque")]
[Range(0f, 1f)] public float m_opacity = 1.0f;
[Range(0f, 0.5f)][Tooltip("Largeur du fondu de bord (ratio UV). 0 = bord dur")]
public float m_edgeFade = 0.1f;
[Tooltip("Actualiser la projection à chaque image, afin de pouvoir faire glisser le projecteur / ajuster les paramètres à l'exécution")]
public bool m_updateEveryFrame = true;
[Header("Occlusion culling (PC/Mac uniquement)")]
[Tooltip("Lorsqu'il est activé, les points de face arrière/occlus derrière la géométrie avant ne sont plus colorés par le décalque (effet projecteur de diapositives) ; lorsqu'il est désactivé, c'est une radiographie")]
public bool m_occlusion = false;
[Range(0f, 1f)][Tooltip("Seuil d'opacité : seuls les splats avec une opacité >= cette valeur contribuent à la carte de profondeur, filtrant les points flottants/brumeux. 0 = tous participent")]
public float m_occlusionCutoff = 0f;
[Range(0f, 0.5f)][Tooltip("Tolérance de profondeur relative. Trop petite -> auto-occlusion près de la surface ; trop grande -> coloration radiographique qui fuit. Défaut 0.05")]
public float m_occlusionBias = 0.05f;
[Range(0, 16)][Tooltip("Plafond de rayon d'empreinte carrée (px), le principal réglage pour supprimer la transparence radiographique. 0 = un seul texel, défaut 2")]
public int m_occlusionFootprint = 2;
[Tooltip("Résolution de la carte de profondeur (px, puissance de deux 256~4096). Réduisez-la pour combler les trous en angle rasant. Défaut 1024")]
public int m_occlusionResolution = 1024;
// -- État d'exécution --
private bool m_loaded;
private bool m_lastOcclusion;
private float m_lastCutoff = float.NaN;
private float m_lastBias = float.NaN;
private int m_lastFootprint = -1;
private int m_lastResolution = -1;
// -- État vidéo --
private VideoPlayer m_videoPlayer;
private RenderTexture m_videoRT; // VideoPlayer décode la vidéo ici (GPU)
private Texture2D m_frameTex; // l'image actuelle est copiée ici et fournie au décalque
private bool m_videoReady;
private long m_lastVideoFrame = -1; // le dernier index d'image poussé vers le décalque
void Start()
{
if (m_manager == null || string.IsNullOrEmpty(m_filePath))
{
Debug.LogError("[VideoStreaming] m_manager ou m_filePath n'est pas assigné.");
enabled = false;
return;
}
// 1) Initialiser le pipeline de rendu en mode éditeur PC à scène unique
m_manager.SetPlatformType(PlatformType.PC);
m_manager.SwitchRenderPass(ActiveRenderMode.SingleRender);
m_manager.SetEditorMode(true);
// 2) Démarrer le décodage de la vidéo en parallèle avec le chargement de la scène
SetupVideo();
// 3) Charger la scène du nuage de points, puis activer le décalque (le décalque est un état global ; il est plus sûr de le définir après que la scène est prête)
var renderer = m_manager.GetRender(this.transform);
Debug.Log($"[VideoStreaming] Début du chargement : {m_filePath}");
renderer.Load(m_filePath,
_ => { /* callback de progression, inutilisé dans cet exemple */ },
OnSceneLoaded);
}
/// <summary>Créer le VideoPlayer à l'exécution et commencer à préparer le clip/l'URL.</summary>
private void SetupVideo()
{
m_videoPlayer = gameObject.AddComponent<VideoPlayer>();
m_videoPlayer.playOnAwake = false;
m_videoPlayer.isLooping = m_videoLoop;
m_videoPlayer.renderMode = VideoRenderMode.RenderTexture;
m_videoPlayer.audioOutputMode = VideoAudioOutputMode.None; // démo : pas d'audio
if (m_videoClip != null)
{
m_videoPlayer.source = VideoSource.VideoClip;
m_videoPlayer.clip = m_videoClip;
}
else
{
m_videoPlayer.source = VideoSource.Url;
m_videoPlayer.url = m_videoUrl;
}
// Allouer le tampon d'image une fois la résolution connue, puis démarrer la lecture.
m_videoPlayer.prepareCompleted += OnVideoPrepared;
m_videoPlayer.Prepare();
}
private void OnVideoPrepared(VideoPlayer vp)
{
int w = (int)vp.width;
int h = (int)vp.height;
m_videoRT = new RenderTexture(w, h, 0, RenderTextureFormat.ARGB32);
m_videoRT.Create();
vp.targetTexture = m_videoRT;
m_frameTex = new Texture2D(w, h, TextureFormat.RGBA32, false);
m_videoReady = true;
vp.Play();
Debug.Log($"[VideoStreaming] Vidéo préparée : {w}x{h}, lecture en cours.");
}
private void OnSceneLoaded()
{
Debug.Log("[VideoStreaming] Chargement terminé, activation de la projection du décalque.");
// -- Projection de base : 5 API --
// Le contenu de la texture est piloté par les images décodées dans Update() ; ici, nous poussons d'abord l'image actuellement disponible.
if (m_frameTex != null) m_manager.SetDecalTexture(m_frameTex);
m_manager.SetDecalOpacity(m_opacity); // opacité globale
m_manager.SetDecalEdgeFade(m_edgeFade); // fondu de bord
ApplyProjection(); // pose du projecteur (appelle en interne SetDecalProjection)
m_manager.SetDecalEnabled(true); // activer le décalque (mot-clé _DECAL)
// -- Occlusion culling : 2 API -- doit être après ApplyProjection (la carte de profondeur a besoin d'une matrice VP de projecteur valide)
m_manager.SetDecalOcclusionSettings(BuildOcclusionSettings());
m_manager.SetDecalOcclusion(m_occlusion);
CacheOcclusionState();
m_loaded = true;
}
void Update()
{
if (!m_loaded) return;
// --- Mise à jour de l'image vidéo, découplée des FPS de rendu ---
// VideoPlayer.frame ne s'incrémente qu'à la vitesse de lecture propre de la vidéo. Même si la scène rend à des centaines de FPS,
// l'index reste le même entre deux images vidéo adjacentes, donc ce n'est que lorsqu'une nouvelle image décodée apparaît réellement
// que nous copions cette image et poussons à nouveau la texture du décalque.
if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
{
m_lastVideoFrame = m_videoPlayer.frame;
// Copier le RenderTexture sur le GPU vers le Texture2D accepté par l'API du décalque.
// Cela ne s'exécute qu'à la fréquence d'images de la vidéo, donc le coût de lecture est gérable.
RenderTexture prev = RenderTexture.active;
RenderTexture.active = m_videoRT;
m_frameTex.ReadPixels(new Rect(0, 0, m_videoRT.width, m_videoRT.height), 0, 0, false);
m_frameTex.Apply(false);
RenderTexture.active = prev;
m_manager.SetDecalTexture(m_frameTex);
}
// basculer l'occlusion à l'exécution
if (m_occlusion != m_lastOcclusion)
{
m_manager.SetDecalOcclusion(m_occlusion);
m_lastOcclusion = m_occlusion;
}
// si un paramètre de qualité d'occlusion a changé -> pousser la struct une fois
if (!Mathf.Approximately(m_occlusionCutoff, m_lastCutoff) ||
!Mathf.Approximately(m_occlusionBias, m_lastBias) ||
m_occlusionFootprint != m_lastFootprint ||
m_occlusionResolution != m_lastResolution)
{
m_manager.SetDecalOcclusionSettings(BuildOcclusionSettings());
CacheOcclusionState();
}
// actualiser le projecteur / l'apparence à chaque image pour un réglage pratique à l'exécution
if (!m_updateEveryFrame) return;
m_manager.SetDecalOpacity(m_opacity);
m_manager.SetDecalEdgeFade(m_edgeFade);
ApplyProjection();
}
/// <summary>Construire la matrice de projection à partir de la pose actuelle du projecteur et des paramètres de perspective.</summary>
private void ApplyProjection()
{
Transform p = m_projector != null ? m_projector : this.transform;
float aspect = m_aspect > 0f
? m_aspect
: (m_videoRT != null ? (float)m_videoRT.width / m_videoRT.height : 1920.0f / 1080.0f);
m_manager.SetDecalProjection(p.position, p.rotation, m_fov, aspect, m_near, m_far);
}
/// <summary>Rassembler les 4 paramètres d'occlusion dans une struct.</summary>
private DecalOcclusionSettings BuildOcclusionSettings()
{
return new DecalOcclusionSettings
{
opacityCutoff = m_occlusionCutoff,
depthBias = m_occlusionBias,
footprintMaxPx = m_occlusionFootprint,
resolution = m_occlusionResolution,
};
}
private void CacheOcclusionState()
{
m_lastOcclusion = m_occlusion;
m_lastCutoff = m_occlusionCutoff;
m_lastBias = m_occlusionBias;
m_lastFootprint = m_occlusionFootprint;
m_lastResolution = m_occlusionResolution;
}
private void OnDestroy()
{
if (m_videoPlayer != null) m_videoPlayer.prepareCompleted -= OnVideoPrepared;
if (m_videoRT != null)
{
m_videoRT.Release();
Destroy(m_videoRT);
}
if (m_frameTex != null) Destroy(m_frameTex);
}
// -- Points d'entrée de débogage pratiques (clic droit dans l'Inspecteur) --
[ContextMenu("Décalque activé")] private void DecalOn() { m_manager?.SetDecalEnabled(true); }
[ContextMenu("Décalque désactivé")] private void DecalOff() { m_manager?.SetDecalEnabled(false); }
[ContextMenu("Occlusion activée")] private void OcclusionOn() { m_occlusion = true; m_manager?.SetDecalOcclusion(true); m_lastOcclusion = true; }
[ContextMenu("Occlusion désactivée")] private void OcclusionOff() { m_occlusion = false; m_manager?.SetDecalOcclusion(false); m_lastOcclusion = false; }
}
Remarque :
- La vidéo et le chargement de la scène s'exécutent en parallèle : dans
Start(),SetupVideo()est d'abord appelé pour démarrerVideoPlayer.Prepare(), puis leLoadde la scène du nuage de points est lancé. Les deux ne se bloquent pas mutuellement.- Le tampon d'image n'est alloué qu'une fois la vidéo prête : les tailles de
RenderTextureet deTexture2Dsont tirées de la résolution réelle de la vidéo (vp.width/vp.height), elles sont donc créées dans le callbackprepareCompletedOnVideoPrepared.- Le décalque n'est activé qu'après la fin du chargement de la scène : conformément au décalque normal,
SetDecalTexture/SetDecalEnabledsont placés dans le callbackOnSceneLoaded. Si la vidéo n'est pas prête à ce moment-là,m_frameTexest null et le premier réglage de texture est ignoré ; il est rempli à l'arrivée de la première image dansUpdate().
Étape 2 : Attacher le script et configurer les propriétés
- Attachez le script
VideoStreaming.csau GameObject LCCRenderer dans la fenêtre Hiérarchie. - Dans la fenêtre Inspecteur, faites glisser le GameObject LCCManager sur la propriété Manager du script.
- Faites glisser le GameObject Projector sur la propriété Projector du script.
- Renseignez le chemin du fichier de scène LCC dans la propriété File Path.
- Spécifiez la source vidéo, choisissez l'une des deux :
- Faites glisser un actif VideoClip du projet sur la propriété Video Clip ; ou
- Renseignez le chemin du fichier vidéo dans la propriété Video Url (par ex.
file:///D:/sample.mp4). - Lorsque les deux sont renseignés, Video Clip est prioritaire.
- Les autres propriétés peuvent être ajustées selon les besoins ; leur signification est indiquée dans le tableau ci-dessous.

Paramètres des propriétés de l'Inspecteur pour le script VideoStreaming
| Champ | Signification | Valeur par défaut |
|---|---|---|
| Manager | L'instance LCCManager de la scène | Requis |
| File Path | Un chemin de scène LCC valide | Requis |
| Video Clip | L'actif vidéo à projeter, priorité supérieure à Video Url | Vide |
| Video Url | Chemin / URL du fichier vidéo, utilisé lorsqu'aucun clip n'est spécifié | Vide |
| Video Loop | S'il faut lire en boucle | Coché |
| Projector | Pose du projecteur, +Z est la direction de projection ; vide utilise le Transform de cet objet | Vide |
| Fov | Champ de vision en perspective du projecteur | 60 |
| Aspect | Ratio d'aspect de la projection ; lorsqu'il est inférieur ou égal à 0, il est calculé automatiquement à partir du ratio d'aspect de la vidéo | 0 (auto) |
| Near / Far | Plans de découpe proche / lointain du frustum du projecteur | 0.1 / 100 |
| Opacity | Opacité globale du décalque | 1 |
| Edge Fade | Largeur du fondu de bord (ratio UV), 0 est un bord dur | 0.1 |
| Update Every Frame | Actualiser le projecteur / l'apparence à chaque image, pratique pour le réglage à l'exécution | Coché |
| Occlusion | S'il faut activer l'occlusion culling | Désactivé |
| Occlusion Cutoff / Bias / Footprint / Resolution | Paramètres de qualité de l'occlusion culling ; voir Guide de développement du décalque projeté | 0 / 0.05 / 2 / 1024 |
Étape 3 : Exécuter et observer l'effet
Cliquez sur Play dans Unity. La vidéo démarre après la fin de Prepare ; après la fin du chargement de la scène, le décalque s'active automatiquement, et les images vidéo sont projetées sur la surface 3DGS image par image. Lorsque Update Every Frame est coché, vous pouvez directement faire glisser le projecteur dans la vue Scène et voir l'image vidéo se déplacer sur la surface du nuage de points en temps réel.

Images vidéo projetées sur la surface 3DGS après l'exécution
Alignement spatial de la vidéo et de la scène
« Alignement spatial » signifie faire en sorte que l'image vidéo se projette précisément sur le nuage de points à la position, l'orientation et la taille que vous souhaitez. La position de l'image vidéo est entièrement déterminée par le projecteur (SetDecalProjection), indépendamment du contenu vidéo. Les paramètres affectant l'alignement sont les suivants :
| Paramètre | Source | Effet sur l'alignement |
|---|---|---|
| Position du projecteur (Projector.position) | Transform du projecteur | Détermine la coordonnée mondiale du sommet du frustum, c'est-à-dire d'où la vidéo est projetée. La translation déplace l'endroit où l'image atterrit sur le nuage de points. |
| Orientation du projecteur (Projector.rotation) | Transform du projecteur | +Z est la direction de projection. La rotation du projecteur change la direction de projection, et l'image se déplace / s'incline sur la surface en conséquence. |
| Fov (champ de vision vertical) | Champ de script m_fov | Détermine l'angle d'ouverture du frustum, affectant directement la taille de l'image sur le nuage de points : plus le FOV est grand, plus l'image est grande ; plus il est petit, plus elle est concentrée. Plage [1, 179]. |
| Aspect (ratio d'aspect) | Champ de script m_aspect | Détermine le rapport horizontal/vertical de l'image. Lorsqu'il est défini sur 0, le ratio d'aspect de la résolution de la vidéo est utilisé automatiquement (m_videoRT.width / height), ce qui est la façon recommandée pour éviter que la vidéo ne soit étirée. Une valeur manuelle erronée provoque un étirement de l'image. |
| Near / Far (plans de découpe proche / lointain) | Champs de script m_near / m_far | Détermine l'intervalle de profondeur effectif du frustum le long de la direction de projection. Seuls les points dans [near, far] sont colorés. Un Far trop petit coupe les nuages de points lointains, et un Near trop grand rate les proches. Il doit couvrir la plage de profondeur du nuage de points cible. |
Étapes d'alignement générales :
- Gardez d'abord Aspect à 0 (auto) pour éviter la distorsion de l'image, et concentrez-vous sur le réglage de la position et de la taille.
- Faites glisser la position et l'orientation du Projector pour centrer l'image sur la zone cible (cochez Update Every Frame pour un aperçu en temps réel).
- Ajustez Fov pour contrôler la taille de couverture de l'image.
- Confirmez que l'intervalle Near / Far couvre entièrement la profondeur du nuage de points cible, pour éviter que l'image ne soit coupée par les plans de découpe.
- Si vous devez forcer un ratio spécifique (par ex. projeter une vidéo 16:9 sur une zone carrée), définissez Aspect manuellement.
Conseil :
Le ratio d'image de la vidéo est contrôlé par Aspect, tandis que la résolution de la vidéo n'affecte que la clarté et le coût de lecture, pas la taille physique projetée sur le nuage de points — la taille est déterminée par Fov et la distance de projection ensemble.

Effet de la pose du projecteur / Fov / Aspect / Near-Far sur l'endroit et la taille de l'image vidéo sur le nuage de points
Aligner la fréquence d'images de la vidéo et la fréquence d'images de rendu
C'est la partie la plus critique du décalque vidéo. La fréquence d'images de la vidéo (par ex. 25/30 FPS) est généralement bien inférieure à la fréquence d'images de rendu de la scène (une scène 3DGS peut tourner à des centaines de FPS). Si vous effectuez une « lecture + SetDecalTexture » à chaque image de rendu, deux problèmes se posent :
- Travail redondant : la même image vidéo est copiée et poussée de manière répétée, ce qui est un pur gaspillage.
- Frein aux performances :
ReadPixelsest une lecture GPU→CPU au coût non négligeable ; le faire à chaque image à une fréquence d'images de rendu élevée provoque des chutes d'images perceptibles.
Stratégie de découplage : utiliser VideoPlayer.frame comme battement
VideoPlayer.frame est l'index d'image décodée actuel, qui ne s'incrémente qu'à la vitesse de lecture propre de la vidéo. Quelle que soit la hauteur de la fréquence d'images de rendu, cet index ne change pas entre deux images vidéo. Sur cette base, ne mettez à jour la texture que lorsque « l'index change » :
if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
{
m_lastVideoFrame = m_videoPlayer.frame;
// Ne lire qu'une fois et pousser le décalque lorsqu'une nouvelle image décodée arrive
RenderTexture prev = RenderTexture.active;
RenderTexture.active = m_videoRT;
m_frameTex.ReadPixels(new Rect(0, 0, m_videoRT.width, m_videoRT.height), 0, 0, false);
m_frameTex.Apply(false);
RenderTexture.active = prev;
m_manager.SetDecalTexture(m_frameTex);
}
De cette façon :
- Lorsque la scène rend à 300 FPS et que la vidéo est à 30 FPS, seules environ 30 des 300 images de rendu effectuent réellement la lecture et la poussée ; les images restantes sont directement ignorées par le
if. - Le battement est contrôlé par le lecteur lui-même ; aucun minuteur écrit à la main n'est nécessaire pour correspondre à la fréquence d'images de la vidéo, et aucune image n'est perdue ni dupliquée en raison des fluctuations de la fréquence d'images de rendu.
- La vitesse, la boucle et la pause de la vidéo sont toutes gérées en interne par
VideoPlayer, et l'indexframeles reflète fidèlement ; le script n'a besoin d'aucune synchronisation supplémentaire.
Pourquoi ne pas utiliser un minuteur par image ?
Accumuler manuellement
Time.deltaTimepour approximer l'intervalle d'images de la vidéo nécessite de gérer à la fois la gigue de la fréquence d'images de rendu et les changements de vitesse / sauts d'images de la vidéo, et peut facilement dériver de la progression de décodage interne réelle du lecteur. Lire directementVideoPlayer.framefait du lecteur la source unique de vérité — le plus simple et le plus robuste.
À propos de Update Every Frame :
Le
m_updateEveryFramedu script contrôle si la pose du projecteur / l'opacité / le fondu sont actualisés à chaque image, ce qui est une question distincte de la mise à jour de l'image vidéo. La mise à jour de l'image vidéo suit toujours le battement deVideoPlayer.frameet n'est pas affectée par ce commutateur.
Remarques sur les performances et les formats
- Le coût de lecture est proportionnel à la résolution.
ReadPixels+Applyest un aller-retour GPU→CPU→GPU ; plus la résolution est élevée, plus c'est lent. Cet exemple l'utilise pour sa simplicité et une démonstration suffisante. Si la résolution de la vidéo est très élevée ou que les performances sont sensibles, vous pouvez passer à une copie purement GPUGraphics.CopyTexture(m_videoRT, m_frameTex), mais les formats source et cible doivent correspondre (RenderTextureFormat.ARGB32correspond àTextureFormat.RGBA32), et certaines plateformes limitent CopyTexture. SetDecalTexturen'accepte queTexture2D. C'est exactement pourquoi leRenderTexturedoit être relu dans unTexture2D; vous ne pouvez pas passer directement leRenderTextureduVideoPlayerà l'interface du décalque.- Réutilisez le tampon d'image, ne faites pas
newà chaque image. Dans l'exemple,m_frameTexn'est créé qu'une seule fois lorsque la vidéo est prête, et le mêmeTexture2Dest réutilisé à chaque image ensuite, évitant des allocations / GC fréquentes. - Libérez les ressources à temps. Dans
OnDestroy,Release()etDestroyleRenderTextureet leTexture2D, et désenregistrezprepareCompleted, pour éviter les fuites. - L'audio n'est pas câblé. L'exemple définit
audioOutputModesurNoneet ne démontre que les visuels. Si le son est nécessaire, passez au modeAudioSourceet attachez unAudioSource.
Résumé des différences avec le décalque normal
| Aspect | Décalque normal (Decal.cs) | Décalque vidéo (VideoStreaming.cs) |
|---|---|---|
| Contenu du décalque | Texture2D statique | Texture2D issu du décodage vidéo par image |
| Source de la texture | Faire glisser une image dans l'Inspecteur | Décodage VideoPlayer → RenderTexture → lecture |
| Moment de mise à jour de la texture | Définie une fois | Mise à jour image par image au battement de VideoPlayer.frame |
| Composant supplémentaire | Aucun | AddComponent<VideoPlayer>() à l'exécution |
| Valeur auto d'Aspect | Ratio d'aspect de l'image | Ratio d'aspect de la vidéo |
| Le reste (projecteur / occlusion / API) | Exactement identique | Exactement identique |
API clés impliquées
Côté SDK, le décalque vidéo n'utilise en plus que la capacité de mise à jour dynamique de SetDecalTexture ; les autres API de décalque sont exactement les mêmes que le décalque normal. Voir Guide de développement du décalque projeté · API liées au décalque.
SetDecalTexture (mise à jour dynamique)
// Valeur de retour : void
public void SetDecalTexture(Texture2D tex)
Définit la texture du décalque. RGB est la couleur et A est le masque propre du décalque. Cette méthode prend en charge les appels répétés à l'exécution : à chaque fois qu'un nouveau Texture2D est transmis, le contenu actuel du décalque est remplacé immédiatement — c'est exactement la base de la projection vidéo par image : appelez-le une fois à chaque apparition d'une nouvelle image décodée dans Update().
| Paramètre | Type | Description |
|---|---|---|
tex | Texture2D | Texture du décalque, peut être remplacée de manière répétée à l'exécution |
Les autres types intégrés de Unity associés (VideoPlayer / RenderTexture / Texture2D) sont des API Unity standard ; voir la documentation officielle de Unity pour leur utilisation.