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

    • PortalCam P1

      • Produktübersicht
      • Grundlegende Bedienung
      • LCC Scan App verwenden
      • Wartung und Pflege
      • FAQ
  • Lixel K-Serie

    • Lixel K1

      • Produktübersicht
      • Grundlegende Bedienung
      • Geräteaktivierung und -verbindung
      • Scan-Workflow
      • Punktwolke mit absoluten Koordinaten erfassen
      • Kartenfusion
      • Empfehlungen zur Routenplanung für typische Szenen
      • Hinweise
      • FAQ
    • Lixel K2

      • Produktübersicht
      • Grundlegende Bedienung
      • Aktivierung und Verbindung
      • Scan-Workflow
      • Punktwolke mit absoluten Koordinaten erfassen
      • Kartenfusion
      • Empfehlungen zur Routenplanung für typische Szenen
      • Hinweise
      • FAQ
  • Lixel L-Serie

    • Lixel L2 Pro

      • Produktübersicht
      • Grundlegende Bedienung
      • Geräteaktivierung und -verbindung
      • Scan-Workflow
      • Punktwolke mit absoluten Koordinaten erfassen
      • Messpunkt
      • Anhang
      • FAQ
    • Lixel L3

      • Produktübersicht
      • Grundlegende Bedienung
      • Aktivierung und Verbindung
      • Scan-Workflow
      • Punktwolke mit absoluten Koordinaten erfassen
      • Anhang
  • Zubehör

    • Stick Light

      • Installationsanleitung
      • Grundlegende Bedienung
      • Häufige Fragen
  • Lixel Studio

    • Version und Copyright
    • Installation und Aktivierung
    • Softwareoberfläche
    • Dateioperationen
    • Projektverarbeitung
    • Werkzeuge
    • 2D-Zeichnung
    • Anwendungen
    • Einstellungen
    • Geräteverbindung
    • Häufige Fragen
  • Lixel CyberColor

    • LCC Studio

      • Erste Schritte
      • Version und Updates
      • Download und Installation
      • Oberfläche und Navigation
      • Vor der Rekonstruktion
      • Modellrekonstruktion
      • Einzelmodell-Rekonstruktion
      • Kartenfusion
      • Boden-Luft-Fusion
      • Luftbild-Rekonstruktion
      • Meine Modelle
      • Weitere Funktionen
      • Einstellungen und Konto
      • Converter
      • Videorekonstruktion
      • Häufige Fragen / FAQ
    • LCC Scene Editor

      • Version und Updates
      • Konto und Anmeldung
      • Produktübersicht und Startseite
      • Editor-Oberfläche
      • Navigationsmodi
      • Datei
      • Einstellungen
      • Bearbeiten
      • Fenster
      • Globale Symbolleiste
      • Assets und Eigenschaften
      • Linke Symbolleiste
      • Ansichtspunkte
      • Portal
      • Skybox
      • Markierungen
      • Messung
      • Flythrough
      • Szenenbericht
      • 3D-Layout
      • Minikarte
      • Vorschaumodus (Viewer)
      • Hilfe
      • Häufige Fragen (FAQ)
      • Spawn-Point
    • LCC Model Editor

      • Version und Updates
      • Benutzerhandbuch
      • Übersicht und Oberfläche
      • Dateioperationen
      • Selektoren
      • Modelle bearbeiten
      • Messung
      • Farbkorrektur
      • Asset-Verwaltung
      • Einstellungen und Hilfe
      • Häufige Fragen
    • Capture Guide

      • Überblick
      • Überblick über die Aufnahmegeräte
      • Allgemeine Aufnahmeprinzipien
      • Aufnahme von Innenszenen
      • Aufnahme von Außenszenen
      • Großflächige Aufnahme (Map Fusion)
      • Aerial-Ground Map Fusion – Aufnahme
      • Objektaufnahme
      • Personenaufnahme
      • Video-Rekonstruktionsaufnahme
      • HD Enhancement
      • Kontrollpunkte (Lixel P1)
      • FAQ und Fehlerbehebung
    • Best Practices

      • Zusammenführen mehrerer großer 3DGS-Modelle
    • Versionsgeschichte
  • Plugin & SDK

    • Unreal

      • Einführung
      • Schnellstart - Windows
      • Schnellstart - Linux
      • Schnellstart - Quest3
      • Editionen und Lizenzierung
      • Rendering
      • Tiled-Rasterisierung (experimentell)
      • Bildeinstellungen
      • Normalen und Beleuchtung
      • Szenenbearbeitung
      • Leistungsparameter
      • Leitfaden zur Leistung
      • Integration von Drittanbieter- und Engine-Plugins
      • Proxy Mesh
      • Ladeanimation
      • Kollision
      • Unterstützung des Navigation System
      • Unterstützung von Single Layer Water
      • Lokalisierung
      • FAQ
      • Fehlerbehebung
      • Logs und Diagnose
      • Kontakt
      • Bewährte Vorgehensweisen

        • 3DGS mit einem LixelStudio-Mesh neu beleuchten
      • API-Referenz

        • ALCCActorBase
        • ULCCComponentBase
        • ULCCComponent
        • ULCC2Component
        • SOG- / SPZ- / PLY-Actors
        • ALCC2ProxyMesh
        • ALCCClippingVolume
        • ALCCSectionPlane
        • ALCCLoadVolume
        • ULCCUtilLibrary
        • Enums
        • Structs
      • Änderungsprotokoll

        • 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

      • Einführung in das Unity SDK
      • Technische Funktionen
      • Anforderungen an die Entwicklungsumgebung
      • PC-Schnellstartanleitung
      • Leitfaden zur Optimierung der Projekteinstellungen
      • Pico 4 Ultra-Entwicklungsanleitung
      • Pico 4 Ultra-PC-Streaming-Anleitung
      • Quest 3-Entwicklungsanleitung
      • Quest 3-PC-Streaming-Anleitung
      • OpenXR-Entwicklungsanleitung
      • Pico 4 Ultra-Business-Streaming-Anleitung
      • Glossar
      • FAQ
      • Kontakt
      • Funktionsentwicklungsleitfaden

        • Entwicklungsanleitung für gemischtes Multi-Szenen-Rendering
        • Entwicklungsanleitung für Schattenempfang
        • Entwicklungsanleitung für Echtzeitbeleuchtung
        • Entwicklungsanleitung für Multi-Kamera-Rendering
        • Entwicklungsanleitung für hochauflösendes Bild-Rendering
        • Entwicklungsanleitung zur Kompatibilität der Datenformate LCC und LCC2
        • Entwicklungsanleitung für Ladeeffekte
        • Entwicklungsanleitung für das Umschalten des Mip-Rendering-Modus
        • Entwicklungsanleitung für Kanten- und Eckensnapping
        • Entwicklungsanleitung für projizierte Decals
        • Entwicklungsanleitung für projizierte Video-Decals
        • Entwicklungsanleitung für Hervorhebung
        • Entwicklungsanleitung für Echtzeit-Clipping
        • Entwicklungsanleitung für Farbanpassung
        • Entwicklungsanleitung für das Abrufen von Szenen-Mesh-Daten
        • Entwicklungsanleitung für den Editor-Modus
        • Konfigurationsanleitung für Render-Leistungsparameter
        • Entwicklungsanleitung zum gemeinsamen Paketieren von Anwendung und Daten
        • Entwicklungsanleitung für das Kollisionssystem
        • Anleitung zu macOS-Berechtigungsproblemen in der Entwicklung
        • Entwicklungsanleitung für Kompilierung und Paketierung auf iOS-Geräten
      • API-Referenz

        • LCCManager
        • Renderer
        • Datentypen
      • Changelog

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

      • Einführung
      • Leitfaden zur Rendering-Leistungsoptimierung
      • Grafikkonfiguration
      • Häufige Fragen
      • API-Referenz

        • 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
      • Änderungsprotokoll

        • 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

Entwicklungsanleitung für projizierte Video-Decals

Funktionsübersicht

Das projizierte Video-Decal ist eine Erweiterung des projizierten Decals: Der projizierte Inhalt ist kein statisches Bild mehr, sondern ein Video. Der Ansatz besteht darin, das Video mit dem in Unity eingebauten VideoPlayer zu dekodieren und jeden Frame dynamisch auf die Decal-Textur zu übertragen, sodass das Video wie von einem Diaprojektor auf die 3DGS-Punktwolkenoberfläche projiziert wird.

Da das Decal selbst eine im Weltraum fixierte perspektivische Projektion ist, wird der Video-Frame entsprechend der Pose des Projektors auf die Punktwolkenoberfläche „geklebt“. Punkte innerhalb des Projektor-Frustums werden vom aktuellen Video-Frame eingefärbt, und Punkte außerhalb des Frustums bleiben unbeeinflusst.

Hinweis:

Wie das normale Decal wirkt das Video-Decal nur auf PC / Mac (URP-Pipeline). Der Aufruf der zugehörigen Schnittstellen auf Mobil- oder VR-Plattformen wird vom SDK ignoriert und eine Warnung ausgegeben.

Diese Anleitung erläutert nur die zusätzlichen Teile rund um „Video“. Für die Grundlagen — Szenenaufbau (Hauptkamera / LCCManager / LCCRenderer / Projektor), das Hinzufügen des Decal Depth Feature zum URP-Renderer, die Abstimmung des Verdeckungs-Cullings und die detaillierten Decal-APIs — lesen Sie bitte zuerst die Entwicklungsanleitung für projizierte Decals.

Implementierungsprinzip

Die Decal-Schnittstelle SetDecalTexture(Texture2D tex) des SDK akzeptiert nur Texture2D, unterstützt jedoch das dynamische Aktualisieren dieser Textur zur Laufzeit. Die Ausgabe der Dekodierung des Unity VideoPlayer ist eine RenderTexture (auf der GPU). Daher lautet die gesamte Datenpipeline:

VideoPlayer dekodiert → RenderTexture (GPU) → nach Texture2D kopieren → SetDecalTexture(Frame-Textur) → auf die Punktwolke projizieren

Es gibt zwei Schlüsselpunkte, die zugleich die beiden „Ausrichtungen“ sind, die diese Anleitung hervorhebt:

  1. Räumliche Ausrichtung: Position, Größe und Ausrichtung des auf die Punktwolke projizierten Video-Frames werden durch die Projektorparameter (Pose, FOV, Aspect, Near/Far) bestimmt; siehe Räumliche Ausrichtung von Video und Szene.
  2. Zeitliche Ausrichtung: Die Video-Bildrate ist oft deutlich niedriger als die Rendering-Bildrate der Szene (die Szene kann mit Hunderten von FPS laufen), daher können Sie die Textur nicht einmal pro Render-Frame kopieren und übergeben. Verwenden Sie VideoPlayer.frame, um zu erkennen, ob ein neuer dekodierter Frame erschienen ist, und aktualisieren Sie nur, wenn ein neuer Frame eintrifft; siehe Video-Bildrate und Rendering-Bildrate ausrichten.
Diagramm der Datenpipeline des projizierten Video-Decals

Datenpipeline des projizierten Video-Decals: VideoPlayer → RenderTexture → Texture2D → SetDecalTexture

Voraussetzungen

Das Video-Decal teilt dieselben Voraussetzungen wie das normale Decal. Bestätigen Sie vor dem Start Folgendes:

  1. Die Szene verfügt bereits über Hauptkamera, LCCManager, LCCRenderer und Projektor (siehe Entwicklungsanleitung für projizierte Decals · Schritt 1).
  2. Die URP-Pipeline ist derzeit aktiv; wenn Verdeckungs-Culling benötigt wird, ist das Decal Depth Feature im URP-Renderer-Asset registriert.
  3. Wenn die Konsole Fehler meldet und kein Effekt sichtbar ist, deaktivieren Sie den Render-Graph-Kompatibilitätsmodus (siehe den FAQ-Abschnitt der Entwicklungsanleitung für projizierte Decals).
  4. Bereiten Sie ein Video-Asset vor: Es kann ein VideoClip-Asset innerhalb des Projekts sein oder eine Videodatei-URL auf Datenträger / Netzwerk (z. B. file:///D:/sample.mp4).

Schritt 1: Das Skript schreiben

Erstellen Sie ein neues C#-Skript namens VideoStreaming.cs. Basierend auf dem Gerüst des normalen Decal.cs ersetzt es die „statische Decal-Textur“ durch „das Erstellen eines VideoPlayer zur Laufzeit zum Dekodieren des Videos und das Aktualisieren der Decal-Textur in Update() entsprechend den dekodierten Frames“. Es kann direkt in Ihr Projekt kopiert werden.

using LCCCore;
using UnityEngine;
using UnityEngine.Video;

public class VideoStreaming : MonoBehaviour
{
    [Header("Erforderlich")]
    [Tooltip("Die LCCManager-Instanz in der Szene")]
    public LCCManager m_manager;
    [Tooltip("Ein gültiger LCC-Szenenpfad")]
    public string m_filePath;

    [Header("Videoquelle (dekodierte Frames werden als Decal projiziert)")]
    [Tooltip("Der zu projizierende Video-Clip. Hat Vorrang vor der unten stehenden URL")]
    public VideoClip m_videoClip;
    [Tooltip("Videodateipfad / -URL, verwendet, wenn kein Clip zugewiesen ist (z. B. file:///D:/xxx.mp4)")]
    public string m_videoUrl;
    [Tooltip("Wiedergabe in Schleife")]
    public bool m_videoLoop = true;

    [Header("Projektor (projiziert entlang +Z)")]
    [Tooltip("Projektor-Pose. Leer lassen, um die Transform dieses Objekts zu verwenden")]
    public Transform m_projector;
    [Range(1f, 179f)] public float m_fov = 60f;
    [Tooltip("Seitenverhältnis der Projektion; <=0 verwendet automatisch das Seitenverhältnis des Videos")]
    public float m_aspect = 0f;
    public float m_near = 0.1f;
    public float m_far = 100f;

    [Header("Decal-Erscheinungsbild")]
    [Range(0f, 1f)] public float m_opacity = 1.0f;
    [Range(0f, 0.5f)][Tooltip("Breite der Kantenüberblendung (UV-Verhältnis). 0 = harte Kante")]
    public float m_edgeFade = 0.1f;
    [Tooltip("Projektion in jedem Frame aktualisieren, damit Sie den Projektor ziehen / Parameter zur Laufzeit anpassen können")]
    public bool m_updateEveryFrame = true;

    [Header("Verdeckungs-Culling (nur PC/Mac)")]
    [Tooltip("Wenn aktiviert, werden rückwärtige/verdeckte Punkte hinter der vorderen Geometrie nicht mehr vom Decal eingefärbt (Diaprojektor-Effekt); wenn deaktiviert, ist es röntgenartig")]
    public bool m_occlusion = false;
    [Range(0f, 1f)][Tooltip("Deckkraft-Schwellenwert: Nur Splats mit einer Deckkraft >= diesem Wert tragen zur Tiefenkarte bei, wodurch schwebende/nebelartige Punkte herausgefiltert werden. 0 = alle nehmen teil")]
    public float m_occlusionCutoff = 0f;
    [Range(0f, 0.5f)][Tooltip("Relative Tiefentoleranz. Zu klein -> Selbstverdeckung an der nahen Oberfläche; zu groß -> durchgesickerte röntgenartige Einfärbung. Standard 0.05")]
    public float m_occlusionBias = 0.05f;
    [Range(0, 16)][Tooltip("Maximaler quadratischer Footprint-Radius (px), der Hauptregler zum Entfernen von röntgenartigem Durchscheinen. 0 = einzelnes Texel, Standard 2")]
    public int m_occlusionFootprint = 2;
    [Tooltip("Auflösung der Tiefenkarte (px, Zweierpotenz 256~4096). Verringern, um Löcher bei flachen Winkeln zu füllen. Standard 1024")]
    public int m_occlusionResolution = 1024;

    // -- Laufzeit-Zustand --
    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;

    // -- Video-Zustand --
    private VideoPlayer m_videoPlayer;
    private RenderTexture m_videoRT;   // Der VideoPlayer dekodiert das Video hier (GPU)
    private Texture2D m_frameTex;      // Der aktuelle Frame wird hierher kopiert und dem Decal zugeführt
    private bool m_videoReady;
    private long m_lastVideoFrame = -1; // Der zuletzt an das Decal übergebene Frame-Index

    void Start()
    {
        if (m_manager == null || string.IsNullOrEmpty(m_filePath))
        {
            Debug.LogError("[VideoStreaming] m_manager oder m_filePath ist nicht zugewiesen.");
            enabled = false;
            return;
        }

        // 1) Die Render-Pipeline auf den PC-Einzel-Szenen-Editor-Modus initialisieren
        m_manager.SetPlatformType(PlatformType.PC);
        m_manager.SwitchRenderPass(ActiveRenderMode.SingleRender);
        m_manager.SetEditorMode(true);

        // 2) Parallel zum Szenenladen mit der Dekodierung des Videos beginnen
        SetupVideo();

        // 3) Die Punktwolken-Szene laden und das Decal aktivieren (das Decal ist ein globaler Zustand; es nach der Bereitschaft der Szene zu setzen ist am sichersten)
        var renderer = m_manager.GetRender(this.transform);
        Debug.Log($"[VideoStreaming] Ladevorgang wird gestartet: {m_filePath}");
        renderer.Load(m_filePath,
            _ => { /* Fortschritts-Callback, im Beispiel ungenutzt */ },
            OnSceneLoaded);
    }

    /// <summary>Den VideoPlayer zur Laufzeit erstellen und mit dem Vorbereiten des Clips/der URL beginnen.</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: ohne 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;
        }

        // Den Frame-Puffer zuweisen, sobald die Auflösung bekannt ist, dann die Wiedergabe starten.
        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 vorbereitet: {w}x{h}, Wiedergabe.");
    }

    private void OnSceneLoaded()
    {
        Debug.Log("[VideoStreaming] Laden abgeschlossen, Decal-Projektion wird aktiviert.");

        // -- Basisprojektion: 5 APIs --
        // Der Texturinhalt wird in Update() von den dekodierten Frames getrieben; hier übergeben wir zunächst den aktuell verfügbaren Frame.
        if (m_frameTex != null) m_manager.SetDecalTexture(m_frameTex);
        m_manager.SetDecalOpacity(m_opacity);        // Gesamt-Deckkraft
        m_manager.SetDecalEdgeFade(m_edgeFade);      // Kantenüberblendung
        ApplyProjection();                           // Projektor-Pose (ruft intern SetDecalProjection auf)
        m_manager.SetDecalEnabled(true);             // Decal aktivieren (_DECAL-Keyword)

        // -- Verdeckungs-Culling: 2 APIs -- muss nach ApplyProjection erfolgen (die Tiefenkarte benötigt eine gültige Projektor-VP-Matrix)
        m_manager.SetDecalOcclusionSettings(BuildOcclusionSettings());
        m_manager.SetDecalOcclusion(m_occlusion);
        CacheOcclusionState();

        m_loaded = true;
    }

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

        // --- Video-Frame-Aktualisierung, vom Render-FPS entkoppelt ---
        // VideoPlayer.frame erhöht sich nur mit der eigenen Wiedergaberate des Videos. Selbst wenn die Szene mit Hunderten von FPS rendert,
        // bleibt der Index zwischen zwei benachbarten Video-Frames gleich, daher kopieren und übergeben wir die Decal-Textur nur dann erneut,
        // wenn tatsächlich ein neuer dekodierter Frame erscheint.
        if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
        {
            m_lastVideoFrame = m_videoPlayer.frame;

            // Die RenderTexture auf der GPU in die von der Decal-API akzeptierte Texture2D kopieren.
            // Dies läuft nur mit der Video-Framerate, daher sind die Rücklesekosten überschaubar.
            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);
        }

        // Verdeckung zur Laufzeit umschalten
        if (m_occlusion != m_lastOcclusion)
        {
            m_manager.SetDecalOcclusion(m_occlusion);
            m_lastOcclusion = m_occlusion;
        }

        // Wenn sich ein Qualitätsparameter der Verdeckung geändert hat -> die Struktur einmal übergeben
        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();
        }

        // Projektor / Erscheinungsbild in jedem Frame aktualisieren, für bequemes Anpassen zur Laufzeit
        if (!m_updateEveryFrame) return;
        m_manager.SetDecalOpacity(m_opacity);
        m_manager.SetDecalEdgeFade(m_edgeFade);
        ApplyProjection();
    }

    /// <summary>Die Projektionsmatrix aus der aktuellen Projektor-Pose und den Perspektivparametern aufbauen.</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>Die 4 Verdeckungsparameter in einer Struktur zusammenführen.</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);
    }

    // -- Bequeme Debug-Einstiegspunkte (Rechtsklick im Inspektor) --
    [ContextMenu("Decal an")]  private void DecalOn()  { m_manager?.SetDecalEnabled(true); }
    [ContextMenu("Decal aus")] private void DecalOff() { m_manager?.SetDecalEnabled(false); }
    [ContextMenu("Verdeckung an")]  private void OcclusionOn()  { m_occlusion = true;  m_manager?.SetDecalOcclusion(true);  m_lastOcclusion = true; }
    [ContextMenu("Verdeckung aus")] private void OcclusionOff() { m_occlusion = false; m_manager?.SetDecalOcclusion(false); m_lastOcclusion = false; }
}

Hinweis:

  • Video- und Szenenladen laufen parallel: In Start() wird zuerst SetupVideo() aufgerufen, um VideoPlayer.Prepare() zu starten, und anschließend das Load der Punktwolken-Szene angestoßen. Die beiden blockieren sich nicht gegenseitig.
  • Der Frame-Puffer wird erst nach Bereitschaft des Videos zugewiesen: Die Größen von RenderTexture und Texture2D werden aus der realen Auflösung des Videos (vp.width / vp.height) übernommen, daher werden sie im prepareCompleted-Callback OnVideoPrepared erstellt.
  • Das Decal wird erst nach Abschluss des Szenenladens aktiviert: Konsistent mit dem normalen Decal werden SetDecalTexture / SetDecalEnabled im OnSceneLoaded-Callback platziert. Ist das Video zu diesem Zeitpunkt noch nicht bereit, ist m_frameTex null und die erste Texturzuweisung wird übersprungen; sie wird ausgefüllt, wenn der erste Frame in Update() eintrifft.

Schritt 2: Das Skript anhängen und die Eigenschaften konfigurieren

  1. Hängen Sie das Skript VideoStreaming.cs im Fenster Hierarchie an das GameObject LCCRenderer.
  2. Ziehen Sie im Fenster Inspektor das GameObject LCCManager auf die Eigenschaft Manager des Skripts.
  3. Ziehen Sie das GameObject Projektor auf die Eigenschaft Projector des Skripts.
  4. Tragen Sie den LCC-Szenen-Dateipfad in die Eigenschaft File Path ein.
  5. Geben Sie die Videoquelle an; wählen Sie eine von zwei Optionen:
    • Ziehen Sie ein VideoClip-Asset aus dem Projekt auf die Eigenschaft Video Clip; oder
    • Tragen Sie den Videodateipfad in die Eigenschaft Video Url ein (z. B. file:///D:/sample.mp4).
    • Wenn beide ausgefüllt sind, hat Video Clip Vorrang.
  6. Weitere Eigenschaften können nach Bedarf angepasst werden; ihre Bedeutung ist in der folgenden Tabelle dargestellt.
Eigenschaftseinstellungen im Inspektor für das VideoStreaming-Skript

Eigenschaftseinstellungen im Inspektor für das VideoStreaming-Skript

FeldBedeutungStandardwert
ManagerDie LCCManager-Instanz in der SzeneErforderlich
File PathEin gültiger LCC-SzenenpfadErforderlich
Video ClipDas zu projizierende Video-Asset, höhere Priorität als Video UrlLeer
Video UrlVideodateipfad / -URL, verwendet, wenn kein Clip angegeben istLeer
Video LoopOb die Wiedergabe in Schleife erfolgen sollAktiviert
ProjectorProjektor-Pose, +Z ist die Projektionsrichtung; leer verwendet die Transform dieses ObjektsLeer
FovPerspektivisches Sichtfeld des Projektors60
AspectSeitenverhältnis der Projektion; wenn kleiner oder gleich 0, wird es automatisch aus dem Seitenverhältnis des Videos berechnet0 (automatisch)
Near / FarNahe / ferne Clipping-Ebenen des Projektor-Frustums0.1 / 100
OpacityGesamt-Deckkraft des Decals1
Edge FadeBreite der Kantenüberblendung (UV-Verhältnis), 0 ist eine harte Kante0.1
Update Every FrameProjektor / Erscheinungsbild in jedem Frame aktualisieren, praktisch für das Anpassen zur LaufzeitAktiviert
OcclusionOb das Verdeckungs-Culling aktiviert werden sollAus
Occlusion Cutoff / Bias / Footprint / ResolutionQualitätsparameter des Verdeckungs-Cullings; siehe Entwicklungsanleitung für projizierte Decals0 / 0.05 / 2 / 1024

Schritt 3: Ausführen und den Effekt beobachten

Klicken Sie in Unity auf Play. Das Video beginnt nach Abschluss von Prepare mit der Wiedergabe; nach Abschluss des Szenenladens schaltet sich das Decal automatisch ein, und die Video-Frames werden Frame für Frame auf die 3DGS-Oberfläche projiziert. Wenn Update Every Frame aktiviert ist, können Sie den Projektor direkt in der Ansicht Szene ziehen und sehen, wie sich der Video-Frame in Echtzeit auf der Punktwolkenoberfläche bewegt.

Nach dem Ausführen auf die 3DGS-Oberfläche projizierte Video-Frames

Nach dem Ausführen auf die 3DGS-Oberfläche projizierte Video-Frames

Räumliche Ausrichtung von Video und Szene

„Räumliche Ausrichtung“ bedeutet, den Video-Frame an der gewünschten Position, Ausrichtung und Größe präzise auf die Punktwolke zu projizieren. Die Position des Video-Frames wird vollständig durch den Projektor (SetDecalProjection) bestimmt, unabhängig vom Videoinhalt. Die ausrichtungsrelevanten Parameter sind wie folgt:

ParameterQuelleAuswirkung auf die Ausrichtung
Projektor-Position (Projector.position)Transform des ProjektorsBestimmt die Weltkoordinate der Frustum-Spitze, also von wo das Video projiziert „wird“. Ein Verschieben verschiebt, wo der Frame auf der Punktwolke landet.
Projektor-Ausrichtung (Projector.rotation)Transform des Projektors+Z ist die Projektionsrichtung. Das Drehen des Projektors ändert die Projektionsrichtung, und der Frame verschiebt sich / neigt sich entsprechend auf der Oberfläche.
Fov (vertikales Sichtfeld)Skriptfeld m_fovBestimmt den Öffnungswinkel des Frustums und wirkt direkt auf die Größe des Frames auf der Punktwolke: Je größer das FOV, desto größer der Frame; je kleiner, desto konzentrierter. Bereich [1, 179].
Aspect (Seitenverhältnis)Skriptfeld m_aspectBestimmt das horizontale/vertikale Verhältnis des Frames. Bei 0 wird automatisch das Seitenverhältnis der Videoauflösung verwendet (m_videoRT.width / height), die empfohlene Methode, um eine Verzerrung des Videos zu vermeiden. Ein falscher manueller Wert führt zu einer Verzerrung des Frames.
Near / Far (nahe / ferne Clipping-Ebenen)Skriptfelder m_near / m_farBestimmen das effektive Tiefenintervall des Frustums entlang der Projektionsrichtung. Nur Punkte innerhalb [near, far] werden eingefärbt. Ein zu kleines Far beschneidet entfernte Punktwolken, und ein zu großes Near verpasst nahegelegene. Es sollte den Tiefenbereich der Ziel-Punktwolke abdecken.

Allgemeine Schritte zur Ausrichtung:

  1. Halten Sie Aspect zunächst bei 0 (automatisch), um Frame-Verzerrung zu vermeiden, und konzentrieren Sie sich auf die Anpassung von Position und Größe.
  2. Ziehen Sie Position und Ausrichtung des Projektors, um den Frame auf dem Zielbereich zu zentrieren (aktivieren Sie Update Every Frame für die Echtzeit-Vorschau).
  3. Passen Sie Fov an, um die Abdeckungsgröße des Frames zu steuern.
  4. Bestätigen Sie, dass das Intervall Near / Far die Tiefe der Ziel-Punktwolke vollständig abdeckt, um ein Abschneiden des Frames durch die Clipping-Ebenen zu vermeiden.
  5. Wenn Sie ein bestimmtes Verhältnis erzwingen müssen (z. B. ein 16:9-Video auf eine quadratische Fläche projizieren), setzen Sie Aspect manuell.

Tipp:

Das Frame-Verhältnis des Videos wird durch Aspect gesteuert, während die Auflösung des Videos nur die Klarheit und die Rücklesekosten beeinflusst, nicht die auf die Punktwolke projizierte physische Größe — die Größe wird gemeinsam durch Fov und die Projektionsdistanz bestimmt.

Auswirkung der Projektorparameter darauf, wo und wie groß der Video-Frame auf der Punktwolke landet

Auswirkung von Projektor-Pose / Fov / Aspect / Near-Far darauf, wo und wie groß der Video-Frame auf der Punktwolke landet

Video-Bildrate und Rendering-Bildrate ausrichten

Dies ist der kritischste Teil des Video-Decals. Die Video-Bildrate (z. B. 25/30 FPS) ist in der Regel weit niedriger als die Rendering-Bildrate der Szene (eine 3DGS-Szene kann mit Hunderten von FPS laufen). Wenn Sie in jedem Render-Frame „Rücklesen + SetDecalTexture“ ausführen, gibt es zwei Probleme:

  • Redundante Arbeit: Derselbe Video-Frame wird wiederholt kopiert und wiederholt übergeben, was reine Verschwendung ist.
  • Leistungsbelastung: ReadPixels ist ein GPU→CPU-Rücklesen mit nicht unerheblichen Kosten; dies bei hoher Rendering-Bildrate in jedem Frame auszuführen, verursacht spürbare Frame-Einbrüche.

Entkopplungsstrategie: VideoPlayer.frame als Taktgeber verwenden

VideoPlayer.frame ist der aktuelle dekodierte Frame-Index, der sich nur mit der eigenen Wiedergaberate des Videos erhöht. Unabhängig davon, wie hoch die Rendering-Bildrate ist, ändert sich dieser Index zwischen zwei Video-Frames nicht. Aktualisieren Sie die Textur daher nur, wenn sich „der Index ändert“:

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

    // Nur einmal zurücklesen und das Decal übergeben, wenn ein neuer dekodierter Frame eintrifft
    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);
}

Auf diese Weise:

  • Wenn die Szene mit 300 FPS rendert und das Video 30 FPS hat, führen nur etwa 30 der 300 Render-Frames tatsächlich das Rücklesen und die Übergabe aus; die übrigen Frames werden durch das if direkt übersprungen.
  • Der Takt wird vom Player selbst gesteuert; es ist kein handgeschriebener Timer erforderlich, um die Video-Bildrate zu treffen, und es werden keine Frames aufgrund von Rendering-Bildraten-Schwankungen übersprungen oder dupliziert.
  • Geschwindigkeit, Schleifenwiedergabe und Pausieren des Videos werden vollständig intern vom VideoPlayer verwaltet, und der frame-Index spiegelt sie wahrheitsgemäß wider; das Skript benötigt keine zusätzliche Synchronisierung.

Warum keinen Timer pro Frame verwenden?

Das manuelle Aufsummieren von Time.deltaTime zur Annäherung an das Video-Frame-Intervall erfordert die Behandlung sowohl von Rendering-Bildraten-Jitter als auch von Video-Geschwindigkeitsänderungen / Frame-Überspringen und kann leicht vom tatsächlichen internen Dekodierungsfortschritt des Players abweichen. Das direkte Lesen von VideoPlayer.frame macht den Player zur einzigen Quelle der Wahrheit — am einfachsten und robustesten.

Über Update Every Frame:

m_updateEveryFrame im Skript steuert, ob Projektor-Pose / Deckkraft / Überblendung in jedem Frame aktualisiert werden, was eine von der Video-Frame-Aktualisierung getrennte Angelegenheit ist. Die Video-Frame-Aktualisierung folgt immer dem VideoPlayer.frame-Takt und wird von diesem Schalter nicht beeinflusst.

Hinweise zu Leistung und Format

  • Rücklesekosten sind proportional zur Auflösung. ReadPixels + Apply ist ein GPU→CPU→GPU-Umlauf; je höher die Auflösung, desto langsamer. Dieses Beispiel verwendet es der Einfachheit halber und zur ausreichenden Veranschaulichung. Wenn die Videoauflösung sehr hoch oder die Leistung empfindlich ist, können Sie auf eine reine GPU-Kopie Graphics.CopyTexture(m_videoRT, m_frameTex) umsteigen, aber Quell- und Zielformat müssen übereinstimmen (RenderTextureFormat.ARGB32 entspricht TextureFormat.RGBA32), und einige Plattformen beschränken CopyTexture.
  • SetDecalTexture akzeptiert nur Texture2D. Genau deshalb muss die RenderTexture in eine Texture2D zurückgelesen werden; Sie können die RenderTexture des VideoPlayer nicht direkt an die Decal-Schnittstelle übergeben.
  • Den Frame-Puffer wiederverwenden, nicht in jedem Frame new erzeugen. Im Beispiel wird m_frameTex nur einmal bei Bereitschaft des Videos erstellt, und dieselbe Texture2D wird anschließend in jedem Frame wiederverwendet, wodurch häufige Zuweisungen / GC vermieden werden.
  • Ressourcen rechtzeitig freigeben. In OnDestroy Release() und Destroy für RenderTexture und Texture2D aufrufen und prepareCompleted abmelden, um Lecks zu vermeiden.
  • Audio ist nicht angeschlossen. Das Beispiel setzt audioOutputMode auf None und demonstriert nur die Optik. Wenn Ton benötigt wird, wechseln Sie in den AudioSource-Modus und hängen Sie eine AudioSource an.

Zusammenfassung der Unterschiede zum normalen Decal

AspektNormales Decal (Decal.cs)Video-Decal (VideoStreaming.cs)
Decal-InhaltStatische Texture2DTexture2D aus der Frame-weisen Videodekodierung
TexturquelleBild im Inspektor ziehenVideoPlayer-Dekodierung → RenderTexture → Rücklesen
Zeitpunkt der TexturaktualisierungEinmal setzenFrame für Frame im VideoPlayer.frame-Takt aktualisiert
Zusätzliche KomponenteKeineAddComponent<VideoPlayer>() zur Laufzeit
Automatischer Aspect-WertSeitenverhältnis des BildesSeitenverhältnis des Videos
Der Rest (Projektor / Verdeckung / API)Genau gleichGenau gleich

Beteiligte Schlüssel-APIs

Auf SDK-Seite nutzt das Video-Decal nur zusätzlich die Fähigkeit der dynamischen Aktualisierung von SetDecalTexture; die übrigen Decal-APIs sind genau gleich wie beim normalen Decal. Siehe Entwicklungsanleitung für projizierte Decals · Decal-bezogene APIs.

SetDecalTexture (dynamische Aktualisierung)

// Rückgabewert: void
public void SetDecalTexture(Texture2D tex)

Legt die Decal-Textur fest. RGB ist Farbe und A ist die Maske des Decals. Diese Methode unterstützt das wiederholte Aufrufen zur Laufzeit: Jedes Mal, wenn eine neue Texture2D übergeben wird, wird der aktuelle Decal-Inhalt sofort ersetzt — genau das ist die Grundlage der Frame-weisen Videoprojektion: Rufen Sie die Methode einmal auf, wenn in Update() ein neuer dekodierter Frame erscheint.

ParameterTypBeschreibung
texTexture2DDecal-Textur, kann zur Laufzeit wiederholt ersetzt werden

Die übrigen verwandten eingebauten Unity-Typen (VideoPlayer / RenderTexture / Texture2D) sind Standard-Unity-APIs; ihre Verwendung finden Sie in der offiziellen Unity-Dokumentation.