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:
- 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.
- 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.

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:
- Die Szene verfügt bereits über Hauptkamera, LCCManager, LCCRenderer und Projektor (siehe Entwicklungsanleitung für projizierte Decals · Schritt 1).
- Die URP-Pipeline ist derzeit aktiv; wenn Verdeckungs-Culling benötigt wird, ist das Decal Depth Feature im URP-Renderer-Asset registriert.
- 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).
- 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 zuerstSetupVideo()aufgerufen, umVideoPlayer.Prepare()zu starten, und anschließend dasLoadder Punktwolken-Szene angestoßen. Die beiden blockieren sich nicht gegenseitig.- Der Frame-Puffer wird erst nach Bereitschaft des Videos zugewiesen: Die Größen von
RenderTextureundTexture2Dwerden aus der realen Auflösung des Videos (vp.width/vp.height) übernommen, daher werden sie imprepareCompleted-CallbackOnVideoPreparederstellt.- Das Decal wird erst nach Abschluss des Szenenladens aktiviert: Konsistent mit dem normalen Decal werden
SetDecalTexture/SetDecalEnabledimOnSceneLoaded-Callback platziert. Ist das Video zu diesem Zeitpunkt noch nicht bereit, istm_frameTexnull und die erste Texturzuweisung wird übersprungen; sie wird ausgefüllt, wenn der erste Frame inUpdate()eintrifft.
Schritt 2: Das Skript anhängen und die Eigenschaften konfigurieren
- Hängen Sie das Skript
VideoStreaming.csim Fenster Hierarchie an das GameObject LCCRenderer. - Ziehen Sie im Fenster Inspektor das GameObject LCCManager auf die Eigenschaft Manager des Skripts.
- Ziehen Sie das GameObject Projektor auf die Eigenschaft Projector des Skripts.
- Tragen Sie den LCC-Szenen-Dateipfad in die Eigenschaft File Path ein.
- 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.
- Weitere Eigenschaften können nach Bedarf angepasst werden; ihre Bedeutung ist in der folgenden Tabelle dargestellt.

Eigenschaftseinstellungen im Inspektor für das VideoStreaming-Skript
| Feld | Bedeutung | Standardwert |
|---|---|---|
| Manager | Die LCCManager-Instanz in der Szene | Erforderlich |
| File Path | Ein gültiger LCC-Szenenpfad | Erforderlich |
| Video Clip | Das zu projizierende Video-Asset, höhere Priorität als Video Url | Leer |
| Video Url | Videodateipfad / -URL, verwendet, wenn kein Clip angegeben ist | Leer |
| Video Loop | Ob die Wiedergabe in Schleife erfolgen soll | Aktiviert |
| Projector | Projektor-Pose, +Z ist die Projektionsrichtung; leer verwendet die Transform dieses Objekts | Leer |
| Fov | Perspektivisches Sichtfeld des Projektors | 60 |
| Aspect | Seitenverhältnis der Projektion; wenn kleiner oder gleich 0, wird es automatisch aus dem Seitenverhältnis des Videos berechnet | 0 (automatisch) |
| Near / Far | Nahe / ferne Clipping-Ebenen des Projektor-Frustums | 0.1 / 100 |
| Opacity | Gesamt-Deckkraft des Decals | 1 |
| Edge Fade | Breite der Kantenüberblendung (UV-Verhältnis), 0 ist eine harte Kante | 0.1 |
| Update Every Frame | Projektor / Erscheinungsbild in jedem Frame aktualisieren, praktisch für das Anpassen zur Laufzeit | Aktiviert |
| Occlusion | Ob das Verdeckungs-Culling aktiviert werden soll | Aus |
| Occlusion Cutoff / Bias / Footprint / Resolution | Qualitätsparameter des Verdeckungs-Cullings; siehe Entwicklungsanleitung für projizierte Decals | 0 / 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
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:
| Parameter | Quelle | Auswirkung auf die Ausrichtung |
|---|---|---|
| Projektor-Position (Projector.position) | Transform des Projektors | Bestimmt 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_fov | Bestimmt 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_aspect | Bestimmt 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_far | Bestimmen 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:
- 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.
- 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).
- Passen Sie Fov an, um die Abdeckungsgröße des Frames zu steuern.
- 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.
- 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 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:
ReadPixelsist 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
ifdirekt ü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
VideoPlayerverwaltet, und derframe-Index spiegelt sie wahrheitsgemäß wider; das Skript benötigt keine zusätzliche Synchronisierung.
Warum keinen Timer pro Frame verwenden?
Das manuelle Aufsummieren von
Time.deltaTimezur 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 vonVideoPlayer.framemacht den Player zur einzigen Quelle der Wahrheit — am einfachsten und robustesten.
Über Update Every Frame:
m_updateEveryFrameim 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 demVideoPlayer.frame-Takt und wird von diesem Schalter nicht beeinflusst.
Hinweise zu Leistung und Format
- Rücklesekosten sind proportional zur Auflösung.
ReadPixels+Applyist 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-KopieGraphics.CopyTexture(m_videoRT, m_frameTex)umsteigen, aber Quell- und Zielformat müssen übereinstimmen (RenderTextureFormat.ARGB32entsprichtTextureFormat.RGBA32), und einige Plattformen beschränken CopyTexture. SetDecalTextureakzeptiert nurTexture2D. Genau deshalb muss dieRenderTexturein eineTexture2Dzurückgelesen werden; Sie können dieRenderTexturedesVideoPlayernicht direkt an die Decal-Schnittstelle übergeben.- Den Frame-Puffer wiederverwenden, nicht in jedem Frame
newerzeugen. Im Beispiel wirdm_frameTexnur einmal bei Bereitschaft des Videos erstellt, und dieselbeTexture2Dwird anschließend in jedem Frame wiederverwendet, wodurch häufige Zuweisungen / GC vermieden werden. - Ressourcen rechtzeitig freigeben. In
OnDestroyRelease()undDestroyfürRenderTextureundTexture2Daufrufen undprepareCompletedabmelden, um Lecks zu vermeiden. - Audio ist nicht angeschlossen. Das Beispiel setzt
audioOutputModeaufNoneund demonstriert nur die Optik. Wenn Ton benötigt wird, wechseln Sie in denAudioSource-Modus und hängen Sie eineAudioSourcean.
Zusammenfassung der Unterschiede zum normalen Decal
| Aspekt | Normales Decal (Decal.cs) | Video-Decal (VideoStreaming.cs) |
|---|---|---|
| Decal-Inhalt | Statische Texture2D | Texture2D aus der Frame-weisen Videodekodierung |
| Texturquelle | Bild im Inspektor ziehen | VideoPlayer-Dekodierung → RenderTexture → Rücklesen |
| Zeitpunkt der Texturaktualisierung | Einmal setzen | Frame für Frame im VideoPlayer.frame-Takt aktualisiert |
| Zusätzliche Komponente | Keine | AddComponent<VideoPlayer>() zur Laufzeit |
| Automatischer Aspect-Wert | Seitenverhältnis des Bildes | Seitenverhältnis des Videos |
| Der Rest (Projektor / Verdeckung / API) | Genau gleich | Genau 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.
| Parameter | Typ | Beschreibung |
|---|---|---|
tex | Texture2D | Decal-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.