Przewodnik wideo dekala projekcyjnego
Przegląd funkcji
Wideo dekal projekcyjny jest rozszerzeniem dekala projekcyjnego: rzutowana treść nie jest już statycznym obrazem, lecz wideo. Podejście polega na zdekodowaniu wideo za pomocą wbudowanego w Unity VideoPlayer i dynamicznym aktualizowaniu każdej klatki na teksturze dekala, dzięki czemu wideo jest rzutowane na powierzchnię chmury punktów 3DGS jak rzutnik slajdów.
Ponieważ sam dekal to rzut perspektywiczny stały w przestrzeni świata, klatka wideo jest „naklejana" na powierzchnię chmury punktów zgodnie z pozą projektora. Punkty wewnątrz frustum projektora są kolorowane bieżącą klatką wideo, a punkty poza frustum pozostają nienaruszone.
Uwaga:
Podobnie jak zwykły dekal, dekal wideo działa tylko na PC / Mac (potok URP). Wywoływanie powiązanych interfejsów na platformach mobilnych lub VR jest ignorowane przez SDK i wyświetla ostrzeżenie.
Ten przewodnik wyjaśnia tylko przyrostowe części związane z „wideo". Podstawy — konfiguracja sceny (Kamera główna / LCCManager / LCCRenderer / Projektor), dodanie Decal Depth Feature do renderera URP, dostrajanie odcinania okluzji i szczegółowe API dekala — znajdziesz najpierw w Przewodnik dekala projekcyjnego.
Zasada implementacji
Interfejs dekala SDK SetDecalTexture(Texture2D tex) akceptuje tylko Texture2D, ale obsługuje dynamiczne aktualizowanie tej tekstury w czasie działania. Wyjście dekodowania Unity VideoPlayer to RenderTexture (na GPU). Dlatego cały potok danych to:
VideoPlayer dekoduje → RenderTexture (GPU) → kopiuj do Texture2D → SetDecalTexture(tekstura klatki) → rzutuj na chmurę punktów
Istnieją dwa kluczowe punkty, które są jednocześnie dwoma „wyrównaniami" podkreślanymi w tym przewodniku:
- Wyrównanie przestrzenne: pozycja, rozmiar i orientacja klatki wideo rzutowanej na chmurę punktów są określane przez parametry projektora (poza, FOV, Aspect, Near/Far); zobacz Wyrównanie przestrzenne wideo i sceny.
- Wyrównanie czasowe: liczba klatek wideo jest często znacznie niższa niż liczba klatek renderowania sceny (scena może działać z setkami FPS), więc nie możesz kopiować i przesyłać tekstury raz na klatkę renderowania. Użyj
VideoPlayer.frame, aby wykryć, czy pojawiła się nowa zdekodowana klatka, i aktualizuj tylko wtedy, gdy nadejdzie nowa klatka; zobacz Wyrównywanie liczby klatek wideo i liczby klatek renderowania.

Potok danych wideo dekala projekcyjnego: VideoPlayer → RenderTexture → Texture2D → SetDecalTexture
Wymagania wstępne
Dekal wideo dzieli ten sam zestaw wymagań wstępnych co zwykły dekal. Przed rozpoczęciem potwierdź:
- Scena ma już skonfigurowane Kamerę główną, LCCManager, LCCRenderer i Projektor (zobacz Przewodnik dekala projekcyjnego · Krok 1).
- Obecnie używany jest potok URP; jeśli potrzebne jest odcinanie okluzji, Decal Depth Feature została zarejestrowana w zasobie renderera URP.
- Jeśli Konsola zgłasza błędy i nie widać efektu, wyłącz tryb zgodności Render Graph (zobacz sekcję FAQ w Przewodnik dekala projekcyjnego).
- Przygotuj zasób wideo: może to być zasób
VideoClipw projekcie lub URL pliku wideo na dysku / w sieci (np.file:///D:/sample.mp4).
Krok 1: Napisz skrypt
Utwórz nowy skrypt C# o nazwie VideoStreaming.cs. Opierając się na szkielecie zwykłego Decal.cs, zastępuje „statyczną teksturę dekala" przez „tworzenie VideoPlayer w czasie działania do dekodowania wideo i aktualizowanie tekstury dekala w Update() zgodnie z zdekodowanymi klatkami". Można go skopiować bezpośrednio do projektu.
using LCCCore;
using UnityEngine;
using UnityEngine.Video;
public class VideoStreaming : MonoBehaviour
{
[Header("Wymagane")]
[Tooltip("Instancja LCCManager w scenie")]
public LCCManager m_manager;
[Tooltip("Prawidłowa ścieżka sceny LCC")]
public string m_filePath;
[Header("Źródło wideo (zdekodowane klatki są rzutowane jako dekal)")]
[Tooltip("Klip wideo do rzutowania. Ma priorytet nad poniższym URL")]
public VideoClip m_videoClip;
[Tooltip("Ścieżka pliku wideo / URL, używana, gdy nie przypisano klipu (np. file:///D:/xxx.mp4)")]
public string m_videoUrl;
[Tooltip("Odtwarzanie w pętli")]
public bool m_videoLoop = true;
[Header("Projektor (rzutuje wzdłuż +Z)")]
[Tooltip("Poza projektora. Pozostaw puste, aby użyć transformu tego obiektu")]
public Transform m_projector;
[Range(1f, 179f)] public float m_fov = 60f;
[Tooltip("Współczynnik proporcji rzutu; <=0 używa automatycznie proporcji wideo")]
public float m_aspect = 0f;
public float m_near = 0.1f;
public float m_far = 100f;
[Header("Wygląd dekala")]
[Range(0f, 1f)] public float m_opacity = 1.0f;
[Range(0f, 0.5f)][Tooltip("Szerokość zanikania krawędzi (współczynnik UV). 0 = twarda krawędź")]
public float m_edgeFade = 0.1f;
[Tooltip("Odświeżaj rzut co klatkę, aby móc przeciągać projektor / dostrajać parametry w czasie działania")]
public bool m_updateEveryFrame = true;
[Header("Odcinanie okluzji (tylko PC/Mac)")]
[Tooltip("Gdy włączone, punkty odwrócone tyłem/zasłonięte za geometrią przednią nie są już kolorowane przez dekal (efekt rzutnika slajdów); gdy wyłączone, jest to prześwietlenie rentgenowskie")]
public bool m_occlusion = false;
[Range(0f, 1f)][Tooltip("Próg nieprzezroczystości: tylko splaty o nieprzezroczystości >= tej wartości wnoszą wkład do mapy głębi, filtrując punkty unoszące się/zamglone. 0 = wszystkie uczestniczą")]
public float m_occlusionCutoff = 0f;
[Range(0f, 0.5f)][Tooltip("Względna tolerancja głębi. Zbyt mała -> samo-okluzja przy powierzchni; zbyt duża -> przeciekające kolorowanie rentgenowskie. Domyślnie 0.05")]
public float m_occlusionBias = 0.05f;
[Range(0, 16)][Tooltip("Górny limit promienia kwadratowego śladu (px), główne pokrętło do usuwania przebijania rentgenowskiego. 0 = pojedynczy teksel, domyślnie 2")]
public int m_occlusionFootprint = 2;
[Tooltip("Rozdzielczość mapy głębi (px, potęga dwójki 256~4096). Zmniejsz ją, aby wypełnić dziury przy kątach stycznych. Domyślnie 1024")]
public int m_occlusionResolution = 1024;
// -- Stan w czasie działania --
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;
// -- Stan wideo --
private VideoPlayer m_videoPlayer;
private RenderTexture m_videoRT; // VideoPlayer dekoduje tutaj wideo (GPU)
private Texture2D m_frameTex; // bieżąca klatka jest tutaj kopiowana i przekazywana do dekala
private bool m_videoReady;
private long m_lastVideoFrame = -1; // indeks ostatniej klatki przesłanej do dekala
void Start()
{
if (m_manager == null || string.IsNullOrEmpty(m_filePath))
{
Debug.LogError("[VideoStreaming] m_manager lub m_filePath nie jest przypisany.");
enabled = false;
return;
}
// 1) Zainicjalizuj potok renderowania do trybu edytora pojedynczej sceny PC
m_manager.SetPlatformType(PlatformType.PC);
m_manager.SwitchRenderPass(ActiveRenderMode.SingleRender);
m_manager.SetEditorMode(true);
// 2) Rozpocznij dekodowanie wideo równolegle z ładowaniem sceny
SetupVideo();
// 3) Załaduj scenę chmury punktów, a następnie włącz dekal (dekal jest stanem globalnym; najbezpieczniej ustawić go, gdy scena jest gotowa)
var renderer = m_manager.GetRender(this.transform);
Debug.Log($"[VideoStreaming] Rozpoczynanie ładowania: {m_filePath}");
renderer.Load(m_filePath,
_ => { /* wywołanie zwrotne postępu, nieużywane w tym przykładzie */ },
OnSceneLoaded);
}
/// <summary>Utwórz VideoPlayer w czasie działania i rozpocznij przygotowywanie klipu/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: bez dźwięku
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;
}
// Przydziel bufor klatki, gdy znana jest rozdzielczość, a następnie rozpocznij odtwarzanie.
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] Wideo przygotowane: {w}x{h}, odtwarzanie.");
}
private void OnSceneLoaded()
{
Debug.Log("[VideoStreaming] Ładowanie zakończone, włączanie rzutowania dekala.");
// -- Rzut podstawowy: 5 API --
// Zawartość tekstury jest sterowana przez zdekodowane klatki w Update(); tutaj najpierw przesyłamy bieżącą dostępną klatkę.
if (m_frameTex != null) m_manager.SetDecalTexture(m_frameTex);
m_manager.SetDecalOpacity(m_opacity); // ogólna nieprzezroczystość
m_manager.SetDecalEdgeFade(m_edgeFade); // zanikanie krawędzi
ApplyProjection(); // poza projektora (wewnętrznie wywołuje SetDecalProjection)
m_manager.SetDecalEnabled(true); // włącz dekal (słowo kluczowe _DECAL)
// -- Odcinanie okluzji: 2 API -- muszą być po ApplyProjection (mapa głębi potrzebuje prawidłowej macierzy VP projektora)
m_manager.SetDecalOcclusionSettings(BuildOcclusionSettings());
m_manager.SetDecalOcclusion(m_occlusion);
CacheOcclusionState();
m_loaded = true;
}
void Update()
{
if (!m_loaded) return;
// --- Aktualizacja klatki wideo, oddzielona od FPS renderowania ---
// VideoPlayer.frame zwiększa się tylko z częstotliwością odtwarzania wideo. Nawet jeśli scena renderuje z setkami FPS,
// indeks pozostaje taki sam między dwiema sąsiednimi klatkami wideo, więc tylko wtedy, gdy rzeczywiście pojawi się nowa zdekodowana klatka,
// kopiujemy tę klatkę i ponownie przesyłamy teksturę dekala.
if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
{
m_lastVideoFrame = m_videoPlayer.frame;
// Skopiuj RenderTexture na GPU do Texture2D akceptowanej przez API dekala.
// Działa to tylko z częstotliwością klatek wideo, więc koszt odczytu zwrotnego jest akceptowalny.
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);
}
// przełącz okluzję w czasie działania
if (m_occlusion != m_lastOcclusion)
{
m_manager.SetDecalOcclusion(m_occlusion);
m_lastOcclusion = m_occlusion;
}
// jeśli zmienił się dowolny parametr jakości okluzji -> wyślij strukturę raz
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();
}
// odświeżaj projektor / wygląd co klatkę dla wygodnego dostrajania w czasie działania
if (!m_updateEveryFrame) return;
m_manager.SetDecalOpacity(m_opacity);
m_manager.SetDecalEdgeFade(m_edgeFade);
ApplyProjection();
}
/// <summary>Zbuduj macierz rzutowania z bieżącej pozy projektora i parametrów perspektywy.</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>Zbierz 4 parametry okluzji do struktury.</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);
}
// -- Wygodne punkty wejścia debugowania (kliknięcie prawym przyciskiem w Inspektorze) --
[ContextMenu("Dekal włączony")] private void DecalOn() { m_manager?.SetDecalEnabled(true); }
[ContextMenu("Dekal wyłączony")] private void DecalOff() { m_manager?.SetDecalEnabled(false); }
[ContextMenu("Okluzja włączona")] private void OcclusionOn() { m_occlusion = true; m_manager?.SetDecalOcclusion(true); m_lastOcclusion = true; }
[ContextMenu("Okluzja wyłączona")] private void OcclusionOff() { m_occlusion = false; m_manager?.SetDecalOcclusion(false); m_lastOcclusion = false; }
}
Uwaga:
- Wideo i ładowanie sceny działają równolegle: w
Start()najpierw wywoływane jestSetupVideo(), aby rozpocząćVideoPlayer.Prepare(), a następnie inicjowane jestLoadsceny chmury punktów. Oba się nie blokują.- Bufor klatki jest przydzielany dopiero po przygotowaniu wideo: rozmiary
RenderTextureiTexture2Dsą pobierane z rzeczywistej rozdzielczości wideo (vp.width/vp.height), więc są tworzone w wywołaniu zwrotnymprepareCompletedOnVideoPrepared.- Dekal jest włączany dopiero po zakończeniu ładowania sceny: zgodnie ze zwykłym dekalem,
SetDecalTexture/SetDecalEnabledsą umieszczane w wywołaniu zwrotnymOnSceneLoaded. Jeśli wideo nie jest wtedy gotowe,m_frameTexjest null i pierwsze ustawienie tekstury jest pomijane; jest wypełniane, gdy nadejdzie pierwsza klatka wUpdate().
Krok 2: Dołącz skrypt i skonfiguruj właściwości
- Dołącz skrypt
VideoStreaming.csdo GameObject LCCRenderer w oknie Hierarchia. - W oknie Inspektor przeciągnij GameObject LCCManager na właściwość Manager skryptu.
- Przeciągnij GameObject Projector na właściwość Projector skryptu.
- Wpisz ścieżkę pliku sceny LCC we właściwości File Path.
- Określ źródło wideo, wybierz jedno z dwóch:
- Przeciągnij zasób VideoClip z projektu na właściwość Video Clip; lub
- Wpisz ścieżkę pliku wideo we właściwości Video Url (np.
file:///D:/sample.mp4). - Gdy oba są wypełnione, Video Clip ma priorytet.
- Pozostałe właściwości można dostosować w razie potrzeby; ich znaczenia pokazano w tabeli poniżej.

Ustawienia właściwości w Inspektorze dla skryptu VideoStreaming
| Pole | Znaczenie | Wartość domyślna |
|---|---|---|
| Manager | Instancja LCCManager w scenie | Wymagane |
| File Path | Prawidłowa ścieżka sceny LCC | Wymagane |
| Video Clip | Zasób wideo do rzutowania, wyższy priorytet niż Video Url | Puste |
| Video Url | Ścieżka pliku wideo / URL, używana, gdy nie określono klipu | Puste |
| Video Loop | Czy odtwarzać w pętli | Zaznaczone |
| Projector | Poza projektora, +Z to kierunek rzutowania; puste używa Transformu tego obiektu | Puste |
| Fov | Perspektywiczne pole widzenia projektora | 60 |
| Aspect | Współczynnik proporcji rzutu; gdy mniejszy lub równy 0, jest obliczany automatycznie z proporcji wideo | 0 (auto) |
| Near / Far | Bliska / daleka płaszczyzna przycinania frustum projektora | 0.1 / 100 |
| Opacity | Ogólna nieprzezroczystość dekala | 1 |
| Edge Fade | Szerokość zanikania krawędzi (współczynnik UV), 0 to twarda krawędź | 0.1 |
| Update Every Frame | Odświeżaj projektor / wygląd co klatkę, wygodne przy dostrajaniu w czasie działania | Zaznaczone |
| Occlusion | Czy włączyć odcinanie okluzji | Wyłączone |
| Occlusion Cutoff / Bias / Footprint / Resolution | Parametry jakości odcinania okluzji; zobacz Przewodnik dekala projekcyjnego | 0 / 0.05 / 2 / 1024 |
Krok 3: Uruchom i obserwuj efekt
Kliknij Odtwórz w Unity. Wideo zaczyna odtwarzać się po zakończeniu Prepare; po zakończeniu ładowania sceny dekal włącza się automatycznie, a klatki wideo są rzutowane na powierzchnię 3DGS klatka po klatce. Gdy Update Every Frame jest zaznaczone, możesz bezpośrednio przeciągać projektor w widoku Scena i widzieć, jak klatka wideo przesuwa się po powierzchni chmury punktów w czasie rzeczywistym.

Klatki wideo rzutowane na powierzchnię 3DGS po uruchomieniu
Wyrównanie przestrzenne wideo i sceny
„Wyrównanie przestrzenne" oznacza sprawienie, aby klatka wideo rzutowała dokładnie na chmurę punktów w pozycji, orientacji i rozmiarze, jakich chcesz. Pozycja klatki wideo jest całkowicie określana przez projektor (SetDecalProjection), niezależnie od zawartości wideo. Parametry wpływające na wyrównanie są następujące:
| Parametr | Źródło | Wpływ na wyrównanie |
|---|---|---|
| Pozycja projektora (Projector.position) | Transform projektora | Określa współrzędną świata wierzchołka frustum, czyli skąd wideo jest rzutowane „od". Przesuwanie jej przesuwa miejsce, w którym klatka ląduje na chmurze punktów. |
| Orientacja projektora (Projector.rotation) | Transform projektora | +Z to kierunek rzutowania. Obracanie projektora zmienia kierunek rzutowania, a klatka odpowiednio przesuwa się / przechyla na powierzchni. |
| Fov (pionowe pole widzenia) | Pole skryptu m_fov | Określa kąt otwarcia frustum, bezpośrednio wpływając na rozmiar klatki na chmurze punktów: im większy FOV, tym większa klatka; im mniejszy, tym bardziej skoncentrowana. Zakres [1, 179]. |
| Aspect (współczynnik proporcji) | Pole skryptu m_aspect | Określa poziomy/pionowy stosunek klatki. Gdy ustawione na 0, automatycznie używany jest współczynnik proporcji rozdzielczości wideo (m_videoRT.width / height), co jest zalecanym sposobem uniknięcia rozciągania wideo. Błędna ręczna wartość powoduje rozciągnięcie klatki. |
| Near / Far (bliska / daleka płaszczyzna przycinania) | Pola skryptu m_near / m_far | Określają efektywny przedział głębokości frustum wzdłuż kierunku rzutowania. Kolorowane są tylko punkty w [near, far]. Zbyt małe Far przycina odległe chmury punktów, a zbyt duże Near pomija bliskie. Powinien obejmować zakres głębokości docelowej chmury punktów. |
Ogólne kroki wyrównania:
- Najpierw pozostaw Aspect na 0 (auto), aby uniknąć zniekształcenia klatki, i skup się na dostosowaniu pozycji i rozmiaru.
- Przeciągaj pozycję i orientację Projektora, aby wyśrodkować klatkę na obszarze docelowym (zaznacz Update Every Frame dla podglądu w czasie rzeczywistym).
- Dostosuj Fov, aby kontrolować rozmiar pokrycia klatki.
- Potwierdź, że przedział Near / Far w pełni obejmuje głębokość docelowej chmury punktów, aby uniknąć przycinania klatki przez płaszczyzny przycinania.
- Jeśli musisz wymusić określony stosunek (np. rzutowanie wideo 16:9 na kwadratowy obszar), ustaw Aspect ręcznie.
Wskazówka:
Proporcje klatki wideo są kontrolowane przez Aspect, a rozdzielczość wideo wpływa tylko na klarowność i koszt odczytu zwrotnego, nie na fizyczny rozmiar rzutowany na chmurę punktów — rozmiar jest określany wspólnie przez Fov i odległość rzutowania.

Wpływ pozy projektora / Fov / Aspect / Near-Far na to, gdzie i jak duża klatka wideo ląduje na chmurze punktów
Wyrównywanie liczby klatek wideo i liczby klatek renderowania
To najistotniejsza część dekala wideo. Liczba klatek wideo (np. 25/30 FPS) jest zwykle znacznie niższa niż liczba klatek renderowania sceny (scena 3DGS może działać z setkami FPS). Jeśli wykonasz „odczyt zwrotny + SetDecalTexture" co klatkę renderowania, pojawiają się dwa problemy:
- Zbędna praca: ta sama klatka wideo jest wielokrotnie kopiowana i wielokrotnie przesyłana, co jest czystym marnotrawstwem.
- Spadek wydajności:
ReadPixelsto odczyt zwrotny GPU→CPU o nietrywialnym koszcie; wykonywanie go co klatkę przy wysokiej liczbie klatek renderowania powoduje zauważalne spadki klatek.
Strategia rozprzężenia: użyj VideoPlayer.frame jako rytmu
VideoPlayer.frame to bieżący indeks zdekodowanej klatki, który zwiększa się tylko z częstotliwością odtwarzania wideo. Niezależnie od tego, jak wysoka jest liczba klatek renderowania, indeks ten nie zmienia się między dwiema klatkami wideo. Na tej podstawie aktualizuj teksturę tylko wtedy, gdy „indeks się zmienia":
if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
{
m_lastVideoFrame = m_videoPlayer.frame;
// Odczytaj zwrotnie tylko raz i prześlij dekal, gdy pojawi się nowa zdekodowana klatka
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);
}
Dzięki temu:
- Gdy scena renderuje z 300 FPS, a wideo ma 30 FPS, tylko około 30 z 300 klatek renderowania faktycznie wykonuje odczyt zwrotny i przesłanie; pozostałe klatki są pomijane bezpośrednio przez
if. - Rytm jest kontrolowany przez sam odtwarzacz; nie potrzeba ręcznego timera do dopasowania liczby klatek wideo, a żadne klatki nie są gubione ani duplikowane z powodu wahań liczby klatek renderowania.
- Szybkość, zapętlanie i wstrzymywanie wideo są zarządzane wewnętrznie przez
VideoPlayer, a indeksframeodzwierciedla je wiernie; skrypt nie potrzebuje dodatkowej synchronizacji.
Dlaczego nie użyć timera per-klatkowego?
Ręczne sumowanie
Time.deltaTimew celu przybliżenia interwału klatek wideo wymaga obsługi zarówno drgań liczby klatek renderowania, jak i zmian szybkości wideo / pomijania klatek, i może łatwo rozjechać się z rzeczywistym wewnętrznym postępem dekodowania odtwarzacza. Bezpośredni odczytVideoPlayer.frameczyni odtwarzacz jedynym źródłem prawdy — najprostsze i najbardziej niezawodne.
O Update Every Frame:
m_updateEveryFramew skrypcie kontroluje, czy poza projektora / nieprzezroczystość / zanikanie są odświeżane co klatkę, co jest odrębną sprawą od aktualizacji klatki wideo. Aktualizacja klatki wideo zawsze podąża za rytmemVideoPlayer.framei nie zależy od tego przełącznika.
Uwagi dotyczące wydajności i formatów
- Koszt odczytu zwrotnego jest proporcjonalny do rozdzielczości.
ReadPixels+Applyto podróż w obie strony GPU→CPU→GPU; im wyższa rozdzielczość, tym wolniej. Ten przykład używa tego dla prostoty i wystarczającej demonstracji. Jeśli rozdzielczość wideo jest bardzo wysoka lub wydajność jest wrażliwa, możesz przełączyć się na czyste kopiowanie GPUGraphics.CopyTexture(m_videoRT, m_frameTex), ale formaty źródła i celu muszą pasować (RenderTextureFormat.ARGB32odpowiadaTextureFormat.RGBA32), a niektóre platformy ograniczają CopyTexture. SetDecalTextureakceptuje tylkoTexture2D. Właśnie dlategoRenderTexturemusi być odczytana z powrotem doTexture2D; nie możesz bezpośrednio przekazaćRenderTexturezVideoPlayerdo interfejsu dekala.- Używaj ponownie bufora klatki, nie
newco klatkę. W przykładziem_frameTexjest tworzony tylko raz, gdy wideo jest gotowe, a ta samaTexture2Djest ponownie używana co klatkę, unikając częstej alokacji / GC. - Zwalniaj zasoby w odpowiednim czasie. W
OnDestroywywołajRelease()iDestroydlaRenderTextureiTexture2Doraz wyrejestrujprepareCompleted, aby uniknąć wycieków. - Dźwięk nie jest podłączony. Przykład ustawia
audioOutputModenaNonei demonstruje tylko wizualizację. Jeśli potrzebny jest dźwięk, zmień na trybAudioSourcei dołączAudioSource.
Podsumowanie różnic w stosunku do zwykłego dekala
| Aspekt | Zwykły dekal (Decal.cs) | Dekal wideo (VideoStreaming.cs) |
|---|---|---|
| Zawartość dekala | Statyczna Texture2D | Texture2D z per-klatkowego dekodowania wideo |
| Źródło tekstury | Przeciągnij obraz w Inspektorze | VideoPlayer dekoduje → RenderTexture → odczyt zwrotny |
| Moment aktualizacji tekstury | Ustawiana raz | Aktualizowana klatka po klatce w rytmie VideoPlayer.frame |
| Dodatkowy komponent | Brak | AddComponent<VideoPlayer>() w czasie działania |
| Wartość auto Aspect | Proporcje obrazu | Proporcje wideo |
| Reszta (projektor / okluzja / API) | Dokładnie taka sama | Dokładnie taka sama |
Kluczowe API
Po stronie SDK dekal wideo dodatkowo wykorzystuje tylko zdolność dynamicznej aktualizacji SetDecalTexture; pozostałe API dekala są dokładnie takie same jak w zwykłym dekalu. Zobacz Przewodnik dekala projekcyjnego · API powiązane z dekalem.
SetDecalTexture (aktualizacja dynamiczna)
// Wartość zwracana: void
public void SetDecalTexture(Texture2D tex)
Ustawia teksturę dekala. RGB to kolor, a A to własna maska dekala. Ta metoda obsługuje wielokrotne wywoływanie w czasie działania: za każdym razem, gdy przekazywana jest nowa Texture2D, bieżąca zawartość dekala jest natychmiast zastępowana — to dokładnie podstawa rzutowania wideo klatka po klatce: wywołaj ją raz, gdy w Update() pojawi się nowa zdekodowana klatka.
| Parametr | Typ | Opis |
|---|---|---|
tex | Texture2D | Tekstura dekala, może być wielokrotnie zastępowana w czasie działania |
Pozostałe powiązane wbudowane typy Unity (VideoPlayer / RenderTexture / Texture2D) to standardowe API Unity; sposób użycia znajdziesz w oficjalnej dokumentacji Unity.