XGRIDSDocumentazione
  • 简体中文
  • English
  • 繁體中文
  • 日本語
  • Deutsch
  • Español
  • Italiano
  • Français
  • Русский
  • Polski
  • Nederlands
  • 简体中文
  • English
  • 繁體中文
  • 日本語
  • Deutsch
  • Español
  • Italiano
  • Français
  • Русский
  • Polski
  • Nederlands
  • Serie Lixel PortalCam

    • PortalCam P1

      • Panoramica del prodotto
      • Operazioni di base
      • Uso della LCC Scan App
      • Manutenzione e cura
      • FAQ
  • Serie Lixel K

    • Lixel K1

      • Panoramica del prodotto
      • Operazioni di base
      • Attivazione e connessione del dispositivo
      • Flusso di scansione
      • Ottenere una nuvola di punti con coordinate assolute
      • Fusione delle mappe
      • Consigli di pianificazione dei percorsi per scene tipiche
      • Precauzioni
      • FAQ
    • Lixel K2

      • Panoramica del prodotto
      • Operazioni di base
      • Attivazione e connessione
      • Flusso di scansione
      • Acquisire una nuvola di punti con coordinate assolute
      • Fusione mappe
      • Consigli di pianificazione dei percorsi per scene tipiche
      • Precauzioni
      • FAQ
  • Serie Lixel L

    • Lixel L2 Pro

      • Panoramica del prodotto
      • Operazioni di base
      • Attivazione e connessione del dispositivo
      • Flusso di scansione
      • Ottenere una nuvola di punti con coordinate assolute
      • Punto di misura
      • Appendice
      • FAQ
    • Lixel L3

      • Panoramica del prodotto
      • Operazioni di base
      • Attivazione e connessione del dispositivo
      • Acquisizione dei dati
      • Acquisizione di coordinate assolute
      • Appendice
  • Accessori

    • Stick Light

      • Guida all'installazione
      • Operazioni di base
      • Domande frequenti
  • Lixel Studio

    • Versione e copyright
    • Installazione e attivazione
    • Interfaccia del software
    • Operazioni sui file
    • Elaborazione del progetto
    • Strumenti
    • Disegno 2D
    • Applicazioni di settore
    • Impostazioni
    • Connessione del dispositivo
    • Domande Frequenti
  • Lixel CyberColor

    • LCC Studio

      • Introduzione
      • Versione e aggiornamenti
      • Download e installazione
      • Panoramica dell'interfaccia e navigazione
      • Operazioni preliminari alla ricostruzione
      • Prima della ricostruzione
      • Ricostruzione del modello
      • Ricostruzione modello singolo
      • Fusione delle mappe
      • Fusione aria-terra
      • Ricostruzione aerea
      • I miei modelli
      • Altre funzioni
      • Impostazioni e account
      • Convertitore
      • Ricostruzione video
      • Domande Frequenti / FAQ
    • LCC Scene Editor

      • Versione e aggiornamenti
      • Account e accesso
      • Panoramica del prodotto e home page
      • Interfaccia dell'editor
      • Modalità di navigazione
      • File
      • Impostazioni
      • Modifica
      • Finestra
      • Barra degli strumenti globale
      • Asset e proprietà
      • Barra degli strumenti sinistra
      • Punti di vista
      • Portale
      • Skybox
      • Annotazioni
      • Misurazione
      • Flythrough
      • Rapporto scena
      • 3D Layout
      • Mini-mappa
      • Modalità Anteprima (Viewer)
      • Aiuto
      • Domande frequenti (FAQ)
      • Punto di spawn
    • LCC Model Editor

      • Versione e aggiornamenti
      • Guida utente
      • Panoramica e interfaccia
      • Operazioni sui file
      • Selettori
      • Modifica dei modelli
      • Misurazione
      • Correzione colore
      • Gestione degli asset
      • Impostazioni e Aiuto
      • Domande frequenti
    • Capture Guide

      • Panoramica
      • Panoramica dei dispositivi di acquisizione
      • Principi generali di acquisizione
      • Acquisizione di scene interne
      • Acquisizione di scene esterne
      • Acquisizione su larga scala (map fusion)
      • Acquisizione per aerial-ground map fusion
      • Acquisizione di oggetti
      • Acquisizione di persone
      • Acquisizione per ricostruzione da video
      • HD Enhancement
      • Control points (Lixel P1)
      • FAQ e risoluzione dei problemi
    • Buone Pratiche

      • Unione di più modelli 3DGS di grandi dimensioni
    • Cronologia versioni
  • Plugin e SDK

    • Unreal

      • Introduzione
      • Avvio rapido - Windows
      • Avvio rapido - Linux
      • Avvio rapido - Quest3
      • Edizioni e licenze
      • Rendering
      • Rasterizzazione Tiled (sperimentale)
      • Impostazioni visive
      • Normali e illuminazione
      • Modifica della scena
      • Parametri di prestazione
      • Guida alle prestazioni
      • Integrazione con plugin di terze parti e del motore
      • Proxy Mesh
      • Animazione di caricamento
      • Collisioni
      • Supporto del Navigation System
      • Supporto di Single Layer Water
      • Localizzazione
      • FAQ
      • Risoluzione dei problemi
      • Log e diagnostica
      • Contattaci
      • Buone pratiche

        • Rilluminare il 3DGS con una mesh di LixelStudio
      • Riferimento API

        • ALCCActorBase
        • ULCCComponentBase
        • ULCCComponent
        • ULCC2Component
        • Actor SOG / SPZ / PLY
        • ALCC2ProxyMesh
        • ALCCClippingVolume
        • ALCCSectionPlane
        • ALCCLoadVolume
        • ULCCUtilLibrary
        • Enum
        • Struct
      • Registro delle modifiche

        • v3.4.0
        • v3.3.1
        • v3.0.0
        • v2.2.1
        • v1.0.0
        • v0.9.0
        • v0.8.0
        • v0.7.1
        • v0.6.1
        • v0.5.2
        • v0.4.1
        • v0.4.0
        • v0.3.0
        • v0.0.5
        • v0.0.4
        • v0.0.3
        • v0.0.2
        • v0.0.1
    • Unity

      • Introduzione all'Unity SDK
      • Caratteristiche tecniche
      • Requisiti dell'ambiente di sviluppo
      • Guida rapida per PC
      • Guida all'ottimizzazione delle impostazioni di progetto
      • Guida allo sviluppo per Pico 4 Ultra
      • Guida allo streaming PC per Pico 4 Ultra
      • Guida allo sviluppo per Quest 3
      • Guida allo streaming PC per Quest 3
      • Guida allo sviluppo OpenXR
      • Guida allo streaming Business per Pico 4 Ultra
      • Glossario
      • FAQ
      • Contattaci
      • Guida allo sviluppo delle funzionalità

        • Guida allo sviluppo del rendering misto multi-scena
        • Guida allo sviluppo della ricezione delle ombre
        • Guida allo sviluppo dell'illuminazione in tempo reale
        • Guida allo sviluppo del rendering multi-camera
        • Guida allo sviluppo del rendering di immagini ad alta risoluzione
        • Guida allo sviluppo della compatibilità dei formati dati LCC e LCC2
        • Guida allo sviluppo dell'effetto di caricamento
        • Guida allo sviluppo del cambio di modalità di rendering Mip
        • Guida allo sviluppo dello snap a bordi e angoli
        • Guida allo sviluppo della decal proiettata
        • Guida allo sviluppo della decal proiettata video
        • Guida allo sviluppo dell'evidenziazione
        • Guida allo sviluppo del ritaglio in tempo reale
        • Guida allo sviluppo della regolazione del tono
        • Guida allo sviluppo dell'ottenimento dei dati Mesh della scena
        • Guida allo sviluppo della modalità Editor
        • Guida alla configurazione dei parametri di prestazioni di rendering
        • Guida allo sviluppo del co-impacchettamento di applicazione e dati
        • Guida allo sviluppo del sistema di collisione
        • Guida ai problemi di autorizzazione nello sviluppo su macOS
        • Guida allo sviluppo di compilazione e packaging per dispositivi iOS
      • Riferimento API

        • LCCManager
        • Renderer
        • Tipi di dati
      • Registro delle modifiche

        • v2.3.0
        • v2.2.10
        • v2.2.0
        • v2.1.19
        • v2.0.14
    • Web

      • Introduzione
      • Guida all'ottimizzazione delle prestazioni di rendering
      • Configurazione grafica
      • Domande frequenti
      • Riferimento API

        • LCCRender::clearIndexDB
        • LCCRender::dispose
        • LCCRender::load
        • LCCRender::setCamera
        • LCCRender::unload
        • LCCRender::update
        • LCCObject::checkRenderNextFrame
        • LCCObject::clearRenderNextFrame
        • LCCObject::ecef2Prj
        • LCCObject::getBounds
        • LCCObject::getEnvInstancedMesh
        • LCCObject::getInstancedMesh
        • LCCObject::getLodInfos
        • LCCObject::getOriginPosition
        • LCCObject::getProjectionCoordinateSystemInfos
        • LCCObject::hasCollision
        • LCCObject::hasEnvironment
        • LCCObject::hasShcoef
        • LCCObject::intersectsCapsule
        • LCCObject::intersectsRayExt
        • LCCObject::intersectsRayFromOriginExt
        • LCCObject::intersectsSphere
        • LCCObject::lowerToBottom
        • LCCObject::prj2Ecef
        • LCCObject::raiseToTop
        • LCCObject::raycast
        • LCCObject::raycastFromOrigin
        • LCCObject::setAlpha
        • LCCObject::setClipBox
        • LCCObject::setClipPlane
        • LCCObject::setEndLod
        • LCCObject::setLodAutoLevelUp
        • LCCObject::setMaxDistance
        • LCCObject::setMaxNodeSplats
        • LCCObject::setMaxSplats
        • LCCObject::setOriginPosition
        • LCCObject::setPointsColor
        • LCCObject::setRenderState
        • LCCObject::setRotation
        • LCCObject::setScale
        • LCCObject::setSemantic
        • LCCObject::setSemanticColor
        • LCCObject::setSmooth
        • LCCObject::setStartLod
        • LCCObject::setTranslation
        • LCCObject::setVisible
        • LCCObject::togglePointsDisplayMode
        • LCCObject::useEnvironment
        • LCCObject::useShcoef
      • Registro delle modifiche

        • v0.6.3
        • v0.6.2
        • v0.6.1
        • v0.6.0
        • v0.5.5
        • v0.5.4
        • v0.5.3
        • v0.5.2
        • v0.5.1
        • v0.5.0
        • v0.4.1
        • v0.4.0
        • v0.3.1
        • v0.3.0
        • v0.2.0

Guida allo sviluppo della decal proiettata video

Panoramica della funzionalità

La decal proiettata video è un'estensione della Decal proiettata: il contenuto proiettato non è più un'immagine statica, ma un video. L'approccio consiste nel decodificare il video con il VideoPlayer integrato di Unity e aggiornare dinamicamente ogni fotogramma sulla texture della decal, così il video viene proiettato sulla superficie della nuvola di punti 3DGS come un proiettore di diapositive.

Poiché la decal stessa è una proiezione prospettica fissa nello spazio mondo, il fotogramma video viene "incollato" sulla superficie della nuvola di punti in base alla posa del proiettore. I punti all'interno del frustum del proiettore vengono colorati dal fotogramma video corrente, mentre i punti all'esterno del frustum non vengono influenzati.

Nota:

Come la decal normale, la decal video ha effetto solo su PC / Mac (pipeline URP). La chiamata delle interfacce correlate sulle piattaforme mobile o VR viene ignorata dall'SDK e stampa un avviso.

Questa guida spiega solo le parti incrementali relative al "video". Per le nozioni di base — configurazione della scena (Camera principale / LCCManager / LCCRenderer / Projector), aggiunta della Decal Depth Feature al Renderer URP, regolazione dell'occlusion culling e le API dettagliate della decal — leggi prima la Guida allo sviluppo della decal proiettata.

Principio di implementazione

L'interfaccia decal dell'SDK SetDecalTexture(Texture2D tex) accetta solo Texture2D, ma supporta l'aggiornamento dinamico di questa texture in fase di esecuzione. L'output della decodifica del VideoPlayer di Unity è una RenderTexture (sulla GPU). Pertanto l'intera pipeline di dati è:

Il VideoPlayer decodifica → RenderTexture (GPU) → copia su Texture2D → SetDecalTexture(texture del fotogramma) → proietta sulla nuvola di punti

Ci sono due punti chiave, che sono anche i due "allineamenti" che questa guida sottolinea:

  1. Allineamento spaziale: la posizione, la dimensione e l'orientamento del fotogramma video proiettato sulla nuvola di punti sono determinati dai parametri del proiettore (posa, FOV, Aspect, Near/Far); vedi Allineamento spaziale di video e scena.
  2. Allineamento temporale: il frame rate del video è spesso molto più basso del frame rate di rendering della scena (la scena può girare a centinaia di FPS), quindi non puoi copiare e inviare la texture una volta per fotogramma di rendering. Usa VideoPlayer.frame per rilevare se è apparso un nuovo fotogramma decodificato e aggiorna solo quando arriva un nuovo fotogramma; vedi Allineamento del frame rate del video e del frame rate di rendering.
Diagramma della pipeline di dati della decal proiettata video

Pipeline di dati della decal proiettata video: VideoPlayer → RenderTexture → Texture2D → SetDecalTexture

Prerequisiti

La decal video condivide lo stesso insieme di prerequisiti della decal normale. Prima di iniziare, conferma quanto segue:

  1. La scena ha già Camera principale, LCCManager, LCCRenderer e Projector configurati (vedi Guida allo sviluppo della decal proiettata · Passaggio 1).
  2. È attualmente in uso la pipeline URP; se è necessario l'occlusion culling, la Decal Depth Feature è stata registrata nell'URP Renderer Asset.
  3. Se la Console segnala errori e non è visibile alcun effetto, disabilita la modalità di compatibilità Render Graph (vedi la sezione FAQ della Guida allo sviluppo della decal proiettata).
  4. Prepara una risorsa video: può essere una risorsa VideoClip all'interno del progetto, oppure un URL di un file video su disco / rete (ad es. file:///D:/sample.mp4).

Passaggio 1: scrivi lo script

Crea un nuovo script C# denominato VideoStreaming.cs. Basato sullo scheletro del normale Decal.cs, sostituisce la "texture statica della decal" con "la creazione di un VideoPlayer in fase di esecuzione per decodificare il video e l'aggiornamento della texture della decal in Update() in base ai fotogrammi decodificati". Può essere copiato direttamente nel tuo progetto.

using LCCCore;
using UnityEngine;
using UnityEngine.Video;

public class VideoStreaming : MonoBehaviour
{
    [Header("Obbligatorio")]
    [Tooltip("L'istanza LCCManager nella scena")]
    public LCCManager m_manager;
    [Tooltip("Un percorso scena LCC valido")]
    public string m_filePath;

    [Header("Origine video (i fotogrammi decodificati vengono proiettati come decal)")]
    [Tooltip("Clip video da proiettare. Ha priorità sull'URL sottostante")]
    public VideoClip m_videoClip;
    [Tooltip("Percorso / URL del file video, usato quando non è assegnato alcun clip (ad es. file:///D:/xxx.mp4)")]
    public string m_videoUrl;
    [Tooltip("Riproduzione in loop")]
    public bool m_videoLoop = true;

    [Header("Proiettore (proietta lungo +Z)")]
    [Tooltip("Posa del proiettore. Lascia vuoto per usare il transform di questo oggetto")]
    public Transform m_projector;
    [Range(1f, 179f)] public float m_fov = 60f;
    [Tooltip("Rapporto d'aspetto della proiezione; <=0 usa automaticamente il rapporto d'aspetto del video")]
    public float m_aspect = 0f;
    public float m_near = 0.1f;
    public float m_far = 100f;

    [Header("Aspetto della decal")]
    [Range(0f, 1f)] public float m_opacity = 1.0f;
    [Range(0f, 0.5f)][Tooltip("Larghezza della sfumatura del bordo (rapporto UV). 0 = bordo netto")]
    public float m_edgeFade = 0.1f;
    [Tooltip("Aggiorna la proiezione a ogni fotogramma, così puoi trascinare il proiettore / regolare i parametri in fase di esecuzione")]
    public bool m_updateEveryFrame = true;

    [Header("Occlusion culling (solo PC/Mac)")]
    [Tooltip("Quando è attivo, i punti retrostanti/occlusi dietro la geometria frontale non vengono più colorati dalla decal (effetto proiettore di diapositive); quando è disattivo è a raggi X")]
    public bool m_occlusion = false;
    [Range(0f, 1f)][Tooltip("Soglia di opacità: solo gli splat con opacità >= a questo valore contribuiscono alla depth map, filtrando i punti fluttuanti/nebbiosi. 0 = partecipano tutti")]
    public float m_occlusionCutoff = 0f;
    [Range(0f, 0.5f)][Tooltip("Tolleranza di profondità relativa. Troppo piccola -> auto-occlusione vicino alla superficie; troppo grande -> colorazione a raggi X diffusa. Predefinito 0.05")]
    public float m_occlusionBias = 0.05f;
    [Range(0, 16)][Tooltip("Limite del raggio dell'impronta quadrata (px), la principale manopola per eliminare la trasparenza a raggi X. 0 = singolo texel, predefinito 2")]
    public int m_occlusionFootprint = 2;
    [Tooltip("Risoluzione della depth map (px, potenza di due 256~4096). Abbassala per riempire i buchi ad angoli radenti. Predefinito 1024")]
    public int m_occlusionResolution = 1024;

    // -- Stato di runtime --
    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;

    // -- Stato del video --
    private VideoPlayer m_videoPlayer;
    private RenderTexture m_videoRT;   // Il VideoPlayer decodifica qui il video (GPU)
    private Texture2D m_frameTex;      // il fotogramma corrente viene copiato qui e fornito alla decal
    private bool m_videoReady;
    private long m_lastVideoFrame = -1; // l'ultimo indice di fotogramma inviato alla decal

    void Start()
    {
        if (m_manager == null || string.IsNullOrEmpty(m_filePath))
        {
            Debug.LogError("[VideoStreaming] m_manager o m_filePath non è assegnato.");
            enabled = false;
            return;
        }

        // 1) Inizializza la pipeline di rendering in modalità editor a scena singola per PC
        m_manager.SetPlatformType(PlatformType.PC);
        m_manager.SwitchRenderPass(ActiveRenderMode.SingleRender);
        m_manager.SetEditorMode(true);

        // 2) Avvia la decodifica del video in parallelo al caricamento della scena
        SetupVideo();

        // 3) Carica la scena della nuvola di punti, poi abilita la decal (la decal è uno stato globale; è più sicuro impostarla dopo che la scena è pronta)
        var renderer = m_manager.GetRender(this.transform);
        Debug.Log($"[VideoStreaming] Avvio caricamento: {m_filePath}");
        renderer.Load(m_filePath,
            _ => { /* callback di avanzamento, non usato in questo esempio */ },
            OnSceneLoaded);
    }

    /// <summary>Crea il VideoPlayer in fase di esecuzione e avvia la preparazione del clip/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; // demo: nessun 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;
        }

        // Alloca il buffer dei fotogrammi una volta nota la risoluzione, poi avvia la riproduzione.
        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] Video preparato: {w}x{h}, riproduzione in corso.");
    }

    private void OnSceneLoaded()
    {
        Debug.Log("[VideoStreaming] Caricamento completato, abilitazione della proiezione decal.");

        // -- Proiezione di base: 5 API --
        // Il contenuto della texture è guidato dai fotogrammi decodificati in Update(); qui inviamo prima il fotogramma attualmente disponibile.
        if (m_frameTex != null) m_manager.SetDecalTexture(m_frameTex);
        m_manager.SetDecalOpacity(m_opacity);        // opacità complessiva
        m_manager.SetDecalEdgeFade(m_edgeFade);      // sfumatura del bordo
        ApplyProjection();                           // posa del proiettore (chiama internamente SetDecalProjection)
        m_manager.SetDecalEnabled(true);             // abilita la decal (keyword _DECAL)

        // -- Occlusion culling: 2 API -- devono seguire ApplyProjection (la depth map necessita di una matrice VP del proiettore valida)
        m_manager.SetDecalOcclusionSettings(BuildOcclusionSettings());
        m_manager.SetDecalOcclusion(m_occlusion);
        CacheOcclusionState();

        m_loaded = true;
    }

    void Update()
    {
        if (!m_loaded) return;

        // --- Aggiornamento del fotogramma video, disaccoppiato dagli FPS di rendering ---
        // VideoPlayer.frame incrementa solo alla velocità di riproduzione del video stesso. Anche se la scena renderizza a centinaia di FPS,
        // l'indice rimane lo stesso tra due fotogrammi video adiacenti, quindi solo quando appare effettivamente un nuovo fotogramma decodificato
        // copiamo quel fotogramma e inviamo nuovamente la texture della decal.
        if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
        {
            m_lastVideoFrame = m_videoPlayer.frame;

            // Copia la RenderTexture sulla GPU nella Texture2D accettata dall'API della decal.
            // Questo avviene solo al frame rate del video, quindi il costo del readback è gestibile.
            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);
        }

        // attiva/disattiva l'occlusione in fase di esecuzione
        if (m_occlusion != m_lastOcclusion)
        {
            m_manager.SetDecalOcclusion(m_occlusion);
            m_lastOcclusion = m_occlusion;
        }

        // se un parametro di qualità dell'occlusione è cambiato -> invia la struct una volta
        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();
        }

        // aggiorna proiettore / aspetto a ogni fotogramma per una comoda regolazione in fase di esecuzione
        if (!m_updateEveryFrame) return;
        m_manager.SetDecalOpacity(m_opacity);
        m_manager.SetDecalEdgeFade(m_edgeFade);
        ApplyProjection();
    }

    /// <summary>Costruisce la matrice di proiezione dalla posa corrente del proiettore e dai parametri prospettici.</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>Raccoglie i 4 parametri di occlusione in una 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);
    }

    // -- Punti di ingresso di debug comodi (clic destro nell'Inspector) --
    [ContextMenu("Attiva Decal")]  private void DecalOn()  { m_manager?.SetDecalEnabled(true); }
    [ContextMenu("Disattiva Decal")] private void DecalOff() { m_manager?.SetDecalEnabled(false); }
    [ContextMenu("Attiva Occlusione")]  private void OcclusionOn()  { m_occlusion = true;  m_manager?.SetDecalOcclusion(true);  m_lastOcclusion = true; }
    [ContextMenu("Disattiva Occlusione")] private void OcclusionOff() { m_occlusion = false; m_manager?.SetDecalOcclusion(false); m_lastOcclusion = false; }
}

Nota:

  • Il caricamento di video e scena avviene in parallelo: in Start(), viene chiamato prima SetupVideo() per avviare VideoPlayer.Prepare(), e poi viene avviato il Load della scena della nuvola di punti. I due non si bloccano a vicenda.
  • Il buffer dei fotogrammi viene allocato solo dopo che il video è pronto: le dimensioni di RenderTexture e Texture2D sono ricavate dalla risoluzione reale del video (vp.width / vp.height), quindi vengono create nel callback prepareCompleted OnVideoPrepared.
  • La decal viene abilitata solo dopo che la scena ha terminato il caricamento: coerentemente con la decal normale, SetDecalTexture / SetDecalEnabled vengono inseriti nel callback OnSceneLoaded. Se il video non è pronto in quel momento, m_frameTex è null e la prima impostazione della texture viene saltata; viene riempita quando il primo fotogramma arriva in Update().

Passaggio 2: associa lo script e configura le proprietà

  1. Associa lo script VideoStreaming.cs al GameObject LCCRenderer nella finestra Gerarchia.
  2. Nella finestra Inspector, trascina il GameObject LCCManager sulla proprietà Manager dello script.
  3. Trascina il GameObject Projector sulla proprietà Projector dello script.
  4. Inserisci il percorso del file della scena LCC nella proprietà File Path.
  5. Specifica l'origine video, scegli una delle due opzioni:
    • Trascina una risorsa VideoClip dal progetto sulla proprietà Video Clip; oppure
    • Inserisci il percorso del file video nella proprietà Video Url (ad es. file:///D:/sample.mp4).
    • Quando sono compilate entrambe, Video Clip ha la priorità.
  6. Le altre proprietà possono essere regolate secondo necessità; il loro significato è mostrato nella tabella seguente.
Impostazioni delle proprietà nell'Inspector per lo script VideoStreaming

Impostazioni delle proprietà nell'Inspector per lo script VideoStreaming

CampoSignificatoValore predefinito
ManagerL'istanza LCCManager nella scenaObbligatorio
File PathUn percorso scena LCC validoObbligatorio
Video ClipLa risorsa video da proiettare, priorità maggiore rispetto a Video UrlVuoto
Video UrlPercorso / URL del file video, usato quando non è specificato alcun clipVuoto
Video LoopIndica se riprodurre in loopSelezionato
ProjectorPosa del proiettore, +Z è la direzione di proiezione; vuoto usa il Transform di questo oggettoVuoto
FovCampo visivo prospettico del proiettore60
AspectRapporto d'aspetto della proiezione; quando è minore o uguale a 0, viene calcolato automaticamente dal rapporto d'aspetto del video0 (auto)
Near / FarPiani di ritaglio vicino / lontano del frustum del proiettore0.1 / 100
OpacityOpacità complessiva della decal1
Edge FadeLarghezza della sfumatura del bordo (rapporto UV), 0 è un bordo netto0.1
Update Every FrameAggiorna proiettore / aspetto a ogni fotogramma, comodo per la regolazione in fase di esecuzioneSelezionato
OcclusionIndica se abilitare l'occlusion cullingDisattivo
Occlusion Cutoff / Bias / Footprint / ResolutionParametri di qualità dell'occlusion culling; vedi Guida allo sviluppo della decal proiettata0 / 0.05 / 2 / 1024

Passaggio 3: esegui e osserva l'effetto

Fai clic su Play in Unity. Il video inizia la riproduzione dopo il completamento di Prepare; dopo che la scena ha terminato il caricamento, la decal si attiva automaticamente e i fotogrammi video vengono proiettati sulla superficie 3DGS fotogramma per fotogramma. Quando Update Every Frame è selezionato, puoi trascinare direttamente il proiettore nella vista Scene e vedere il fotogramma video muoversi sulla superficie della nuvola di punti in tempo reale.

Fotogrammi video proiettati sulla superficie 3DGS dopo l'esecuzione

Fotogrammi video proiettati sulla superficie 3DGS dopo l'esecuzione

Allineamento spaziale di video e scena

"L'allineamento spaziale" significa far sì che il fotogramma video si proietti con precisione sulla nuvola di punti nella posizione, nell'orientamento e nella dimensione desiderati. La posizione del fotogramma video è interamente determinata dal proiettore (SetDecalProjection), indipendentemente dal contenuto del video. I parametri che influenzano l'allineamento sono i seguenti:

ParametroOrigineEffetto sull'allineamento
Posizione del proiettore (Projector.position)Transform del ProjectorDetermina la coordinata mondo dell'apice del frustum, ovvero da dove il video viene proiettato. Traslandola si sposta il punto in cui il fotogramma cade sulla nuvola di punti.
Orientamento del proiettore (Projector.rotation)Transform del Projector+Z è la direzione di proiezione. Ruotando il proiettore cambia la direzione di proiezione e il fotogramma si sposta / inclina sulla superficie di conseguenza.
Fov (campo visivo verticale)Campo dello script m_fovDetermina l'angolo di apertura del frustum, influenzando direttamente la dimensione del fotogramma sulla nuvola di punti: maggiore è il FOV, più grande è il fotogramma; minore è, più è concentrato. Intervallo [1, 179].
Aspect (rapporto d'aspetto)Campo dello script m_aspectDetermina il rapporto orizzontale/verticale del fotogramma. Quando è impostato a 0, viene usato automaticamente il rapporto d'aspetto della risoluzione del video (m_videoRT.width / height), che è il modo consigliato per evitare che il video venga stirato. Un valore manuale errato causa lo stiramento del fotogramma.
Near / Far (piani di ritaglio vicino / lontano)Campi dello script m_near / m_farDetermina l'intervallo di profondità effettivo del frustum lungo la direzione di proiezione. Solo i punti entro [near, far] vengono colorati. Un Far troppo piccolo ritaglia le nuvole di punti distanti, e un Near troppo grande manca quelle vicine. Dovrebbe coprire l'intervallo di profondità della nuvola di punti di destinazione.

Passaggi generali di allineamento:

  1. Mantieni prima Aspect a 0 (auto) per evitare la distorsione del fotogramma, e concentrati sulla regolazione di posizione e dimensione.
  2. Trascina la posizione e l'orientamento del Projector per centrare il fotogramma sull'area di destinazione (seleziona Update Every Frame per un'anteprima in tempo reale).
  3. Regola il Fov per controllare la dimensione di copertura del fotogramma.
  4. Conferma che l'intervallo Near / Far copra interamente la profondità della nuvola di punti di destinazione, per evitare che il fotogramma venga tagliato dai piani di ritaglio.
  5. Se devi forzare un rapporto specifico (ad es. proiettare un video 16:9 su un'area quadrata), imposta Aspect manualmente.

Suggerimento:

Il rapporto del fotogramma del video è controllato da Aspect, mentre la risoluzione del video influisce solo sulla nitidezza e sul costo del readback, non sulla dimensione fisica proiettata sulla nuvola di punti — la dimensione è determinata insieme dal Fov e dalla distanza di proiezione.

Effetto dei parametri del proiettore su dove e quanto grande cade il fotogramma video sulla nuvola di punti

Effetto di posa del proiettore / Fov / Aspect / Near-Far su dove e quanto grande cade il fotogramma video sulla nuvola di punti

Allineamento del frame rate del video e del frame rate di rendering

Questa è la parte più critica della decal video. Il frame rate del video (ad es. 25/30 FPS) è di solito molto più basso del frame rate di rendering della scena (una scena 3DGS può girare a centinaia di FPS). Se esegui un "readback + SetDecalTexture" a ogni fotogramma di rendering, ci sono due problemi:

  • Lavoro ridondante: lo stesso fotogramma video viene copiato e inviato ripetutamente, il che è un puro spreco.
  • Calo delle prestazioni: ReadPixels è un readback GPU→CPU con un costo non trascurabile; eseguirlo a ogni fotogramma a un frame rate di rendering elevato causa cali di frame evidenti.

Strategia di disaccoppiamento: usa VideoPlayer.frame come battito

VideoPlayer.frame è l'indice del fotogramma decodificato corrente, che incrementa solo alla velocità di riproduzione del video stesso. Per quanto alto sia il frame rate di rendering, questo indice non cambia tra due fotogrammi video. In base a ciò, aggiorna la texture solo quando "l'indice cambia":

if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
{
    m_lastVideoFrame = m_videoPlayer.frame;

    // Esegui il readback una sola volta e invia la decal quando arriva un nuovo fotogramma decodificato
    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);
}

In questo modo:

  • Quando la scena renderizza a 300 FPS e il video è a 30 FPS, solo circa 30 dei 300 fotogrammi di rendering eseguono effettivamente il readback e l'invio; i fotogrammi rimanenti vengono saltati direttamente dall'if.
  • Il battito è controllato dal player stesso; non serve alcun timer scritto a mano per adattarsi al frame rate del video, e nessun fotogramma viene perso o duplicato a causa delle fluttuazioni del frame rate di rendering.
  • La velocità, il loop e la pausa del video sono gestiti internamente da VideoPlayer, e l'indice frame li riflette fedelmente; lo script non necessita di sincronizzazione aggiuntiva.

Perché non usare un timer per fotogramma?

Accumulare manualmente Time.deltaTime per approssimare l'intervallo dei fotogrammi video richiede di gestire sia il jitter del frame rate di rendering sia i cambi di velocità / salti di fotogramma del video, e può facilmente discostarsi dall'effettivo avanzamento di decodifica interno del player. Leggere direttamente VideoPlayer.frame rende il player l'unica fonte di verità — il modo più semplice e robusto.

Riguardo a Update Every Frame:

Il campo m_updateEveryFrame nello script controlla se la posa del proiettore / opacità / sfumatura vengono aggiornati a ogni fotogramma, il che è una questione separata dall'aggiornamento del fotogramma video. L'aggiornamento del fotogramma video segue sempre il battito di VideoPlayer.frame e non è influenzato da questo interruttore.

Note su prestazioni e formati

  • Il costo del readback è proporzionale alla risoluzione. ReadPixels + Apply è un percorso GPU→CPU→GPU; maggiore è la risoluzione, più lento è. Questo esempio lo usa per semplicità e per una dimostrazione sufficiente. Se la risoluzione del video è molto alta o le prestazioni sono sensibili, puoi passare a una copia puramente GPU Graphics.CopyTexture(m_videoRT, m_frameTex), ma i formati di origine e destinazione devono corrispondere (RenderTextureFormat.ARGB32 corrisponde a TextureFormat.RGBA32), e alcune piattaforme limitano CopyTexture.
  • SetDecalTexture accetta solo Texture2D. È esattamente per questo che la RenderTexture deve essere riletta in una Texture2D; non puoi passare direttamente la RenderTexture del VideoPlayer all'interfaccia della decal.
  • Riutilizza il buffer dei fotogrammi, non fare new a ogni fotogramma. Nell'esempio, m_frameTex viene creato una sola volta quando il video è pronto, e la stessa Texture2D viene riutilizzata a ogni fotogramma successivo, evitando allocazioni / GC frequenti.
  • Libera le risorse in tempo utile. In OnDestroy, esegui Release() e Destroy sulla RenderTexture e sulla Texture2D, e annulla la registrazione di prepareCompleted, per evitare perdite.
  • L'audio non è collegato. L'esempio imposta audioOutputMode su None e dimostra solo la parte visiva. Se serve il suono, passa alla modalità AudioSource e associa un AudioSource.

Riepilogo delle differenze rispetto alla decal normale

AspettoDecal normale (Decal.cs)Decal video (VideoStreaming.cs)
Contenuto della decalTexture2D staticaTexture2D dalla decodifica video per fotogramma
Origine della textureTrascina un'immagine nell'InspectorVideoPlayer decodifica → RenderTexture → readback
Tempistica di aggiornamento della textureImpostata una voltaAggiornata fotogramma per fotogramma sul battito di VideoPlayer.frame
Componente aggiuntivoNessunoAddComponent<VideoPlayer>() in fase di esecuzione
Valore automatico AspectRapporto d'aspetto dell'immagineRapporto d'aspetto del video
Il resto (proiettore / occlusione / API)Esattamente ugualeEsattamente uguale

API chiave coinvolte

Sul lato SDK, la decal video usa in aggiunta solo la capacità di aggiornamento dinamico di SetDecalTexture; le altre API della decal sono esattamente uguali alla decal normale. Vedi Guida allo sviluppo della decal proiettata · API relative alla decal.

SetDecalTexture (aggiornamento dinamico)

// Valore restituito: void
public void SetDecalTexture(Texture2D tex)

Imposta la texture della decal. RGB è il colore e A è la maschera propria della decal. Questo metodo supporta la chiamata ripetuta in fase di esecuzione: ogni volta che viene passata una nuova Texture2D, il contenuto corrente della decal viene sostituito immediatamente — questa è esattamente la base della proiezione video per fotogramma: chiamalo una volta ogni volta che appare un nuovo fotogramma decodificato in Update().

ParametroTipoDescrizione
texTexture2DTexture della decal, può essere sostituita ripetutamente in fase di esecuzione

Gli altri tipi integrati di Unity correlati (VideoPlayer / RenderTexture / Texture2D) sono API Unity standard; consulta la documentazione ufficiale di Unity per l'utilizzo.