Guía de desarrollo de decal proyectado de vídeo
Descripción general de la función
El decal proyectado de vídeo es una extensión del Decal proyectado: el contenido proyectado ya no es una imagen estática, sino un vídeo. El enfoque es decodificar el vídeo con el VideoPlayer integrado de Unity y actualizar dinámicamente cada fotograma sobre la textura del decal, de modo que el vídeo se proyecta sobre la superficie de la nube de puntos 3DGS como un proyector de diapositivas.
Dado que el decal en sí es una proyección en perspectiva fija en el espacio de mundo, el fotograma de vídeo se «pega» sobre la superficie de la nube de puntos según la pose del proyector. Los puntos dentro del frustum del proyector se colorean con el fotograma de vídeo actual, y los puntos fuera del frustum no se ven afectados.
Nota:
Al igual que el decal normal, el decal de vídeo solo surte efecto en PC / Mac (pipeline URP). Llamar a las interfaces relacionadas en plataformas móviles o VR es ignorado por el SDK e imprime una advertencia.
Esta guía solo explica las partes incrementales relacionadas con el «vídeo». Para lo básico — configuración de la escena (Main Camera / LCCManager / LCCRenderer / Projector), adición del Decal Depth Feature al URP Renderer, ajuste del culling por oclusión y las API de decal detalladas — lee primero la Guía de desarrollo de decal proyectado.
Principio de implementación
La interfaz de decal del SDK SetDecalTexture(Texture2D tex) solo acepta Texture2D, pero admite actualizar dinámicamente esta textura en tiempo de ejecución. La salida de la decodificación del VideoPlayer de Unity es una RenderTexture (en la GPU). Por lo tanto, toda la cadena de datos es:
VideoPlayer decodifica → RenderTexture (GPU) → copiar a Texture2D → SetDecalTexture(textura del fotograma) → proyectar sobre la nube de puntos
Hay dos puntos clave, que son también las dos «alineaciones» que enfatiza esta guía:
- Alineación espacial: la posición, el tamaño y la orientación del fotograma de vídeo proyectado sobre la nube de puntos están determinados por los parámetros del proyector (pose, FOV, Aspect, Near/Far); consulta Alineación espacial del vídeo y la escena.
- Alineación temporal: la tasa de fotogramas del vídeo suele ser mucho menor que la tasa de fotogramas de renderizado de la escena (la escena puede ejecutarse a cientos de FPS), por lo que no puedes copiar y enviar la textura una vez por fotograma de renderizado. Usa
VideoPlayer.framepara detectar si ha aparecido un nuevo fotograma decodificado y actualiza solo cuando llega un nuevo fotograma; consulta Alinear la tasa de fotogramas del vídeo y la de renderizado.

Cadena de datos del decal proyectado de vídeo: VideoPlayer → RenderTexture → Texture2D → SetDecalTexture
Requisitos previos
El decal de vídeo comparte el mismo conjunto de requisitos previos que el decal normal. Antes de empezar, confirma lo siguiente:
- La escena ya tiene Main Camera, LCCManager, LCCRenderer y Projector configurados (consulta Guía de desarrollo de decal proyectado · Paso 1).
- El pipeline URP está en uso actualmente; si se necesita culling por oclusión, el Decal Depth Feature se ha registrado en el URP Renderer Asset.
- Si la Console informa de errores y no se ve ningún efecto, desactiva el modo de compatibilidad de Render Graph (consulta la sección de preguntas frecuentes de la Guía de desarrollo de decal proyectado).
- Prepara un activo de vídeo: puede ser un activo
VideoClipdentro del proyecto o una URL de archivo de vídeo en disco / red (p. ej.file:///D:/sample.mp4).
Paso 1: Escribir el script
Crea un nuevo script C# llamado VideoStreaming.cs. Basado en el esqueleto del Decal.cs normal, reemplaza la «textura de decal estática» por «crear un VideoPlayer en tiempo de ejecución para decodificar el vídeo y actualizar la textura del decal en Update() según los fotogramas decodificados». Puede copiarse directamente en tu proyecto.
using LCCCore;
using UnityEngine;
using UnityEngine.Video;
public class VideoStreaming : MonoBehaviour
{
[Header("Requerido")]
[Tooltip("La instancia LCCManager en la escena")]
public LCCManager m_manager;
[Tooltip("Una ruta de escena LCC válida")]
public string m_filePath;
[Header("Fuente de vídeo (los fotogramas decodificados se proyectan como decal)")]
[Tooltip("Clip de vídeo a proyectar. Tiene prioridad sobre la URL de abajo")]
public VideoClip m_videoClip;
[Tooltip("Ruta / URL del archivo de vídeo, usada cuando no se asigna ningún clip (p. ej. file:///D:/xxx.mp4)")]
public string m_videoUrl;
[Tooltip("Reproducción en bucle")]
public bool m_videoLoop = true;
[Header("Proyector (proyecta a lo largo de +Z)")]
[Tooltip("Pose del proyector. Déjalo vacío para usar el transform de este objeto")]
public Transform m_projector;
[Range(1f, 179f)] public float m_fov = 60f;
[Tooltip("Relación de aspecto de la proyección; <=0 usa automáticamente la relación de aspecto del vídeo")]
public float m_aspect = 0f;
public float m_near = 0.1f;
public float m_far = 100f;
[Header("Apariencia del decal")]
[Range(0f, 1f)] public float m_opacity = 1.0f;
[Range(0f, 0.5f)][Tooltip("Ancho de desvanecimiento del borde (relación UV). 0 = borde duro")]
public float m_edgeFade = 0.1f;
[Tooltip("Actualiza la proyección cada fotograma, para que puedas arrastrar el proyector / ajustar parámetros en tiempo de ejecución")]
public bool m_updateEveryFrame = true;
[Header("Oclusión por culling (solo PC/Mac)")]
[Tooltip("Cuando está activada, los puntos orientados hacia atrás/ocluidos detrás de la geometría frontal ya no son coloreados por el decal (efecto de proyector de diapositivas); cuando está desactivada es tipo radiografía")]
public bool m_occlusion = false;
[Range(0f, 1f)][Tooltip("Umbral de opacidad: solo los splats con opacidad >= este valor contribuyen al mapa de profundidad, filtrando puntos flotantes/nebulosos. 0 = todos participan")]
public float m_occlusionCutoff = 0f;
[Range(0f, 0.5f)][Tooltip("Tolerancia de profundidad relativa. Demasiado pequeña -> autooclusión cerca de la superficie; demasiado grande -> coloración de radiografía filtrada. Por defecto 0.05")]
public float m_occlusionBias = 0.05f;
[Range(0, 16)][Tooltip("Tope del radio de huella cuadrada (px), el control principal para eliminar la filtración de radiografía. 0 = un solo texel, por defecto 2")]
public int m_occlusionFootprint = 2;
[Tooltip("Resolución del mapa de profundidad (px, potencia de dos 256~4096). Bájala para rellenar huecos en ángulos rasantes. Por defecto 1024")]
public int m_occlusionResolution = 1024;
// -- Estado en tiempo de ejecución --
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;
// -- Estado del vídeo --
private VideoPlayer m_videoPlayer;
private RenderTexture m_videoRT; // VideoPlayer decodifica el vídeo aquí (GPU)
private Texture2D m_frameTex; // el fotograma actual se copia aquí y se alimenta al decal
private bool m_videoReady;
private long m_lastVideoFrame = -1; // el último índice de fotograma enviado al decal
void Start()
{
if (m_manager == null || string.IsNullOrEmpty(m_filePath))
{
Debug.LogError("[VideoStreaming] m_manager o m_filePath no están asignados.");
enabled = false;
return;
}
// 1) Inicializa el pipeline de renderizado al modo editor de una sola escena de PC
m_manager.SetPlatformType(PlatformType.PC);
m_manager.SwitchRenderPass(ActiveRenderMode.SingleRender);
m_manager.SetEditorMode(true);
// 2) Comienza a decodificar el vídeo en paralelo con la carga de la escena
SetupVideo();
// 3) Carga la escena de nube de puntos y luego activa el decal (el decal es un estado global; lo más seguro es configurarlo después de que la escena esté lista)
var renderer = m_manager.GetRender(this.transform);
Debug.Log($"[VideoStreaming] Iniciando carga: {m_filePath}");
renderer.Load(m_filePath,
_ => { /* devolución de llamada de progreso, sin usar en este ejemplo */ },
OnSceneLoaded);
}
/// <summary>Crea el VideoPlayer en tiempo de ejecución y comienza a preparar el 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: sin 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;
}
// Asigna el búfer de fotogramas una vez que se conoce la resolución, luego inicia la reproducción.
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] Vídeo preparado: {w}x{h}, reproduciendo.");
}
private void OnSceneLoaded()
{
Debug.Log("[VideoStreaming] Carga completa, activando la proyección del decal.");
// -- Proyección base: 5 API --
// El contenido de la textura lo impulsan los fotogramas decodificados en Update(); aquí enviamos primero el fotograma disponible actual.
if (m_frameTex != null) m_manager.SetDecalTexture(m_frameTex);
m_manager.SetDecalOpacity(m_opacity); // opacidad general
m_manager.SetDecalEdgeFade(m_edgeFade); // desvanecimiento del borde
ApplyProjection(); // pose del proyector (internamente llama a SetDecalProjection)
m_manager.SetDecalEnabled(true); // activa el decal (keyword _DECAL)
// -- Oclusión por culling: 2 API -- debe ir después de ApplyProjection (el mapa de profundidad necesita una matriz VP válida del proyector)
m_manager.SetDecalOcclusionSettings(BuildOcclusionSettings());
m_manager.SetDecalOcclusion(m_occlusion);
CacheOcclusionState();
m_loaded = true;
}
void Update()
{
if (!m_loaded) return;
// --- Actualización del fotograma de vídeo, desacoplada del FPS de renderizado ---
// VideoPlayer.frame solo se incrementa a la propia tasa de reproducción del vídeo. Incluso si la escena se renderiza a cientos de FPS,
// el índice permanece igual entre dos fotogramas de vídeo adyacentes, por lo que solo cuando aparece realmente un nuevo fotograma decodificado
// copiamos ese fotograma y volvemos a enviar la textura del decal.
if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
{
m_lastVideoFrame = m_videoPlayer.frame;
// Copia la RenderTexture en la GPU a la Texture2D aceptada por la API del decal.
// Esto solo se ejecuta a la tasa de fotogramas del vídeo, por lo que el coste de lectura de vuelta es manejable.
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);
}
// alterna la oclusión en tiempo de ejecución
if (m_occlusion != m_lastOcclusion)
{
m_manager.SetDecalOcclusion(m_occlusion);
m_lastOcclusion = m_occlusion;
}
// si cambió algún parámetro de calidad de oclusión -> envía la estructura una vez
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();
}
// actualiza proyector / apariencia cada fotograma para un ajuste cómodo en tiempo de ejecución
if (!m_updateEveryFrame) return;
m_manager.SetDecalOpacity(m_opacity);
m_manager.SetDecalEdgeFade(m_edgeFade);
ApplyProjection();
}
/// <summary>Construye la matriz de proyección a partir de la pose actual del proyector y los parámetros de perspectiva.</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>Reúne los 4 parámetros de oclusión en una estructura.</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);
}
// -- Puntos de entrada de depuración cómodos (clic derecho en el Inspector) --
[ContextMenu("Activar decal")] private void DecalOn() { m_manager?.SetDecalEnabled(true); }
[ContextMenu("Desactivar decal")] private void DecalOff() { m_manager?.SetDecalEnabled(false); }
[ContextMenu("Activar oclusión")] private void OcclusionOn() { m_occlusion = true; m_manager?.SetDecalOcclusion(true); m_lastOcclusion = true; }
[ContextMenu("Desactivar oclusión")] private void OcclusionOff() { m_occlusion = false; m_manager?.SetDecalOcclusion(false); m_lastOcclusion = false; }
}
Nota:
- El vídeo y la carga de la escena se ejecutan en paralelo: en
Start(), primero se llama aSetupVideo()para iniciarVideoPlayer.Prepare(), y luego se inicia laLoadde la escena de nube de puntos. Los dos no se bloquean mutuamente.- El búfer de fotogramas solo se asigna después de que el vídeo esté listo: los tamaños de
RenderTextureyTexture2Dse toman de la resolución real del vídeo (vp.width/vp.height), por lo que se crean en la devolución de llamadaprepareCompletedOnVideoPrepared.- El decal solo se activa después de que la escena termine de cargarse: coherente con el decal normal,
SetDecalTexture/SetDecalEnabledse colocan en la devolución de llamadaOnSceneLoaded. Si el vídeo no está listo en ese momento,m_frameTexes null y se omite la primera configuración de textura; se rellena cuando llega el primer fotograma enUpdate().
Paso 2: Adjuntar el script y configurar las propiedades
- Adjunta el script
VideoStreaming.csal GameObject LCCRenderer en la ventana Jerarquía. - En la ventana Inspector, arrastra el GameObject LCCManager a la propiedad Manager del script.
- Arrastra el GameObject Projector a la propiedad Projector del script.
- Rellena la ruta del archivo de la escena LCC en la propiedad File Path.
- Especifica la fuente de vídeo, elige una de dos:
- Arrastra un activo VideoClip del proyecto a la propiedad Video Clip; o
- Rellena la ruta del archivo de vídeo en la propiedad Video Url (p. ej.
file:///D:/sample.mp4). - Cuando ambos están rellenos, Video Clip tiene prioridad.
- Otras propiedades pueden ajustarse según sea necesario; sus significados se muestran en la tabla siguiente.

Ajustes de propiedad del Inspector para el script VideoStreaming
| Campo | Significado | Valor por defecto |
|---|---|---|
| Manager | La instancia LCCManager en la escena | Requerido |
| File Path | Una ruta de escena LCC válida | Requerido |
| Video Clip | El activo de vídeo a proyectar, mayor prioridad que Video Url | Vacío |
| Video Url | Ruta / URL del archivo de vídeo, usada cuando no se especifica ningún clip | Vacío |
| Video Loop | Si se reproduce en bucle | Marcado |
| Projector | Pose del proyector, +Z es la dirección de proyección; vacío usa el Transform de este objeto | Vacío |
| Fov | Campo de visión en perspectiva del proyector | 60 |
| Aspect | Relación de aspecto de la proyección; cuando es menor o igual a 0, se calcula automáticamente a partir de la relación de aspecto del vídeo | 0 (auto) |
| Near / Far | Planos de recorte cercano / lejano del frustum del proyector | 0.1 / 100 |
| Opacity | Opacidad general del decal | 1 |
| Edge Fade | Ancho de desvanecimiento del borde (relación UV), 0 es un borde duro | 0.1 |
| Update Every Frame | Actualiza proyector / apariencia cada fotograma, cómodo para el ajuste en tiempo de ejecución | Marcado |
| Occlusion | Si se habilita el culling por oclusión | Off |
| Occlusion Cutoff / Bias / Footprint / Resolution | Parámetros de calidad del culling por oclusión; consulta la Guía de desarrollo de decal proyectado | 0 / 0.05 / 2 / 1024 |
Paso 3: Ejecutar y observar el efecto
Haz clic en Play en Unity. El vídeo comienza a reproducirse después de que Prepare finalice; después de que la escena termine de cargarse, el decal se activa automáticamente y los fotogramas de vídeo se proyectan sobre la superficie 3DGS fotograma a fotograma. Cuando Update Every Frame está marcado, puedes arrastrar directamente el proyector en la vista Scene y ver el fotograma de vídeo moverse sobre la superficie de la nube de puntos en tiempo real.

Fotogramas de vídeo proyectados sobre la superficie 3DGS tras la ejecución
Alineación espacial del vídeo y la escena
«Alineación espacial» significa hacer que el fotograma de vídeo se proyecte con precisión sobre la nube de puntos en la posición, orientación y tamaño deseados. La posición del fotograma de vídeo está determinada enteramente por el proyector (SetDecalProjection), independientemente del contenido del vídeo. Los parámetros que afectan a la alineación son los siguientes:
| Parámetro | Origen | Efecto sobre la alineación |
|---|---|---|
| Posición del proyector (Projector.position) | Transform del proyector | Determina la coordenada de mundo del vértice del frustum, es decir, desde dónde se proyecta el vídeo. Trasladarlo desplaza dónde aterriza el fotograma en la nube de puntos. |
| Orientación del proyector (Projector.rotation) | Transform del proyector | +Z es la dirección de proyección. Rotar el proyector cambia la dirección de proyección y el fotograma se desplaza / inclina sobre la superficie en consecuencia. |
| Fov (campo de visión vertical) | Campo del script m_fov | Determina el ángulo de apertura del frustum, afectando directamente al tamaño del fotograma en la nube de puntos: cuanto mayor es el FOV, mayor es el fotograma; cuanto menor, más concentrado. Rango [1, 179]. |
| Aspect (relación de aspecto) | Campo del script m_aspect | Determina la relación horizontal/vertical del fotograma. Cuando se establece en 0, se usa automáticamente la relación de aspecto de la resolución del vídeo (m_videoRT.width / height), que es la forma recomendada para evitar que el vídeo se estire. Un valor manual incorrecto provoca que el fotograma se estire. |
| Near / Far (planos de recorte cercano / lejano) | Campos del script m_near / m_far | Determina el intervalo de profundidad efectivo del frustum a lo largo de la dirección de proyección. Solo se colorean los puntos dentro de [near, far]. Un Far demasiado pequeño recorta nubes de puntos lejanas, y un Near demasiado grande omite las cercanas. Debe cubrir el rango de profundidad de la nube de puntos de destino. |
Pasos generales de alineación:
- Primero mantén Aspect en 0 (auto) para evitar la distorsión del fotograma y concéntrate en ajustar la posición y el tamaño.
- Arrastra la posición y orientación del Projector para centrar el fotograma en el área de destino (marca Update Every Frame para una previsualización en tiempo real).
- Ajusta Fov para controlar el tamaño de cobertura del fotograma.
- Confirma que el intervalo Near / Far cubre por completo la profundidad de la nube de puntos de destino, para evitar que el fotograma sea recortado por los planos de recorte.
- Si necesitas forzar una relación específica (p. ej. proyectar un vídeo 16:9 sobre un área cuadrada), establece Aspect manualmente.
Consejo:
La relación de fotograma del vídeo se controla mediante Aspect, mientras que la resolución del vídeo solo afecta a la claridad y al coste de lectura de vuelta, no al tamaño físico proyectado sobre la nube de puntos — el tamaño lo determinan juntos el Fov y la distancia de proyección.

Efecto de la pose del proyector / Fov / Aspect / Near-Far sobre dónde y a qué tamaño aterriza el fotograma de vídeo en la nube de puntos
Alinear la tasa de fotogramas del vídeo y la de renderizado
Esta es la parte más crítica del decal de vídeo. La tasa de fotogramas del vídeo (p. ej. 25/30 FPS) suele ser mucho menor que la tasa de fotogramas de renderizado de la escena (una escena 3DGS puede ejecutarse a cientos de FPS). Si haces una «lectura de vuelta + SetDecalTexture» en cada fotograma de renderizado, hay dos problemas:
- Trabajo redundante: el mismo fotograma de vídeo se copia y se envía repetidamente, lo que es puro desperdicio.
- Lastre de rendimiento:
ReadPixelses una lectura de vuelta GPU→CPU con un coste no trivial; hacerlo en cada fotograma a una tasa de renderizado alta provoca caídas de fotogramas notables.
Estrategia de desacoplamiento: usar VideoPlayer.frame como pulso
VideoPlayer.frame es el índice de fotograma decodificado actual, que se incrementa solo a la propia tasa de reproducción del vídeo. No importa cuán alta sea la tasa de fotogramas de renderizado, este índice no cambia entre dos fotogramas de vídeo. En base a esto, actualiza la textura solo cuando «el índice cambia»:
if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
{
m_lastVideoFrame = m_videoPlayer.frame;
// Solo lee de vuelta una vez y envía el decal cuando llega un nuevo fotograma decodificado
RenderTexture prev = RenderTexture.active;
RenderTexture.active = m_videoRT;
m_frameTex.ReadPixels(new Rect(0, 0, m_videoRT.width, m_videoRT.height), 0, 0, false);
m_frameTex.Apply(false);
RenderTexture.active = prev;
m_manager.SetDecalTexture(m_frameTex);
}
De esta manera:
- Cuando la escena se renderiza a 300 FPS y el vídeo está a 30 FPS, solo unos 30 de los 300 fotogramas de renderizado realizan realmente la lectura de vuelta y el envío; los fotogramas restantes se omiten directamente mediante el
if. - El pulso lo controla el propio reproductor; no se necesita un temporizador escrito a mano para igualar la tasa de fotogramas del vídeo, y no se pierden ni duplican fotogramas debido a fluctuaciones de la tasa de fotogramas de renderizado.
- La velocidad, el bucle y la pausa del vídeo se gestionan internamente mediante
VideoPlayer, y el índiceframelos refleja fielmente; el script no necesita sincronización adicional.
¿Por qué no usar un temporizador por fotograma?
Acumular manualmente
Time.deltaTimepara aproximar el intervalo de fotogramas del vídeo requiere manejar tanto el jitter de la tasa de fotogramas de renderizado como los cambios de velocidad / saltos de fotogramas del vídeo, y puede derivar fácilmente del progreso de decodificación interno real del reproductor. LeerVideoPlayer.framedirectamente convierte al reproductor en la única fuente de verdad — lo más simple y robusto.
Acerca de Update Every Frame:
El
m_updateEveryFramedel script controla si la pose del proyector / opacidad / desvanecimiento se actualizan cada fotograma, lo que es un asunto separado de la actualización del fotograma de vídeo. La actualización del fotograma de vídeo siempre sigue el pulso deVideoPlayer.framey no se ve afectada por este interruptor.
Notas de rendimiento y formato
- El coste de lectura de vuelta es proporcional a la resolución.
ReadPixels+Applyes un viaje de ida y vuelta GPU→CPU→GPU; cuanto mayor es la resolución, más lento es. Este ejemplo lo usa por simplicidad y para una demostración suficiente. Si la resolución del vídeo es muy alta o el rendimiento es sensible, puedes cambiar a una copia puramente en GPUGraphics.CopyTexture(m_videoRT, m_frameTex), pero los formatos de origen y destino deben coincidir (RenderTextureFormat.ARGB32corresponde aTextureFormat.RGBA32), y algunas plataformas restringen CopyTexture. SetDecalTexturesolo aceptaTexture2D. Esta es exactamente la razón por la que laRenderTexturedebe leerse de vuelta a unaTexture2D; no puedes pasar directamente laRenderTexturedelVideoPlayera la interfaz del decal.- Reutiliza el búfer de fotogramas, no hagas
newen cada fotograma. En el ejemplo,m_frameTexse crea solo una vez cuando el vídeo está listo, y la mismaTexture2Dse reutiliza en cada fotograma posterior, evitando asignaciones / GC frecuentes. - Libera los recursos a tiempo. En
OnDestroy, hazRelease()yDestroyde laRenderTexturey laTexture2D, y anula el registro deprepareCompleted, para evitar fugas. - El audio no está conectado. El ejemplo establece
audioOutputModecomoNoney solo demuestra las imágenes. Si se necesita sonido, cambia al modoAudioSourcey adjunta unAudioSource.
Resumen de diferencias con el decal normal
| Aspecto | Decal normal (Decal.cs) | Decal de vídeo (VideoStreaming.cs) |
|---|---|---|
| Contenido del decal | Texture2D estática | Texture2D de la decodificación de vídeo por fotograma |
| Fuente de la textura | Arrastra una imagen en el Inspector | VideoPlayer decodifica → RenderTexture → lectura de vuelta |
| Momento de actualización de la textura | Se establece una vez | Se actualiza fotograma a fotograma al pulso de VideoPlayer.frame |
| Componente adicional | Ninguno | AddComponent<VideoPlayer>() en tiempo de ejecución |
| Valor automático de Aspect | Relación de aspecto de la imagen | Relación de aspecto del vídeo |
| El resto (proyector / oclusión / API) | Exactamente igual | Exactamente igual |
API clave implicadas
En el lado del SDK, el decal de vídeo solo usa adicionalmente la capacidad de actualización dinámica de SetDecalTexture; las demás API de decal son exactamente las mismas que las del decal normal. Consulta Guía de desarrollo de decal proyectado · API relacionadas con el decal.
SetDecalTexture (actualización dinámica)
// Valor de retorno: void
public void SetDecalTexture(Texture2D tex)
Establece la textura del decal. RGB es el color y A es la máscara propia del decal. Este método admite ser llamado repetidamente en tiempo de ejecución: cada vez que se pasa una nueva Texture2D, el contenido del decal actual se reemplaza inmediatamente — esta es exactamente la base de la proyección de vídeo por fotograma: llámalo una vez cada vez que aparezca un nuevo fotograma decodificado en Update().
| Parámetro | Tipo | Descripción |
|---|---|---|
tex | Texture2D | Textura del decal, puede reemplazarse repetidamente en tiempo de ejecución |
Los otros tipos integrados relacionados de Unity (VideoPlayer / RenderTexture / Texture2D) son API estándar de Unity; consulta la documentación oficial de Unity para su uso.