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

    • PortalCam P1

      • Descripción del producto
      • Operaciones básicas
      • Uso de la LCC Scan App
      • Mantenimiento y cuidado
      • FAQ
  • Serie Lixel K

    • Lixel K1

      • Descripción del producto
      • Operaciones básicas
      • Activación y conexión del dispositivo
      • Flujo de escaneo
      • Obtener nube de puntos con coordenadas absolutas
      • Fusión de mapas
      • Recomendaciones de planificación de rutas para escenas típicas
      • Precauciones
      • FAQ
    • Lixel K2

      • Descripción del producto
      • Operaciones básicas
      • Activación y conexión
      • Flujo de escaneo
      • Obtener nube de puntos con coordenadas absolutas
      • Fusión de mapas
      • Recomendaciones de planificación de rutas para escenas típicas
      • Precauciones
      • FAQ
  • Serie Lixel L

    • Lixel L2 Pro

      • Descripción del producto
      • Operaciones básicas
      • Activación y conexión del dispositivo
      • Flujo de escaneo
      • Obtener nube de puntos con coordenadas absolutas
      • Punto de medición
      • Apéndice
      • FAQ
    • Lixel L3

      • Descripción del producto
      • Operaciones básicas
      • Activación y conexión del dispositivo
      • Flujo de escaneo
      • Obtener nube de puntos con coordenadas absolutas
      • Apéndice
  • Accesorios

    • Stick Light

      • Guía de instalación
      • Operaciones básicas
      • Preguntas frecuentes
  • Lixel Studio

    • Versión y derechos de autor
    • Instalación y activación
    • Interfaz del software
    • Operaciones de archivo
    • Procesamiento de proyectos
    • Herramientas
    • Dibujo 2D
    • Aplicaciones
    • Ajustes
    • Conexión de dispositivo
    • Preguntas Frecuentes
  • Lixel CyberColor

    • LCC Studio

      • Primeros pasos
      • Versión y actualizaciones
      • Descarga e instalación
      • Descripción general de la interfaz y navegación
      • Antes de la reconstrucción
      • Reconstrucción de modelos
      • Reconstrucción de modelo único
      • Fusión de mapas
      • Fusión aire-tierra
      • Reconstrucción aérea
      • Mis modelos
      • Otras funciones
      • Ajustes y cuenta
      • Converter
      • Reconstrucción por vídeo
      • Preguntas frecuentes
    • LCC Scene Editor

      • Versión y actualizaciones
      • Cuenta e inicio de sesión
      • Descripción general del producto y página de inicio
      • Interfaz del editor
      • Modos de navegación
      • Archivo
      • Configuración
      • Editar
      • Ventana
      • Barra de herramientas global
      • Activos y propiedades
      • Barra de herramientas izquierda
      • Puntos de vista
      • Portal
      • Skybox
      • Anotaciones
      • Medición
      • Flythrough
      • Informe de escena
      • 3D Layout
      • Minimapa
      • Modo Vista previa (Viewer)
      • Ayuda
      • Preguntas frecuentes (FAQ)
      • Punto de inicio
    • LCC Model Editor

      • Versión y actualizaciones
      • Guía del usuario
      • Visión general e interfaz
      • Operaciones de archivo
      • Selectores
      • Edición de modelos
      • Medición
      • Corrección de color
      • Gestión de activos
      • Configuración y ayuda
      • Preguntas frecuentes
    • Capture Guide

      • Descripción general
      • Descripción general de los dispositivos de captura
      • Principios generales de captura
      • Captura de escenas interiores
      • Captura de escenas exteriores
      • Captura a gran escala (Map Fusion)
      • Captura con aerial-ground map fusion
      • Captura de objetos
      • Captura de personas
      • Captura para reconstrucción a partir de vídeo
      • HD Enhancement
      • Puntos de control (Lixel P1)
      • Preguntas frecuentes y resolución de problemas
    • Buenas Prácticas

      • Fusión de varios modelos 3DGS de gran tamaño
    • Historial de versiones
  • Plugins y SDK

    • Unreal

      • Introducción
      • Inicio rápido - Windows
      • Inicio rápido - Linux
      • Inicio rápido - Quest3
      • Ediciones y licencias
      • Renderizado
      • Rasterización Tiled (experimental)
      • Ajustes visuales
      • Normales e iluminación
      • Edición de escenas
      • Parámetros de rendimiento
      • Guía de rendimiento
      • Integración con plugins de terceros y del motor
      • Proxy Mesh
      • Animación de carga
      • Colisión
      • Compatibilidad con Navigation System
      • Compatibilidad con Single Layer Water
      • Localización
      • Preguntas frecuentes
      • Resolución de problemas
      • Logs y diagnóstico
      • Contacto
      • Buenas prácticas

        • Reiluminar 3DGS con una malla de LixelStudio
      • Referencia de API

        • ALCCActorBase
        • ULCCComponentBase
        • ULCCComponent
        • ULCC2Component
        • Actors SOG / SPZ / PLY
        • ALCC2ProxyMesh
        • ALCCClippingVolume
        • ALCCSectionPlane
        • ALCCLoadVolume
        • ULCCUtilLibrary
        • Enumeraciones
        • Structs
      • Registro de cambios

        • 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

      • Introducción al SDK de Unity
      • Funciones técnicas
      • Requisitos del entorno de desarrollo
      • Guía de inicio rápido para PC
      • Guía de optimización de ajustes del proyecto
      • Guía de desarrollo para Pico 4 Ultra
      • Guía de streaming desde PC para Pico 4 Ultra
      • Guía de desarrollo para Quest 3
      • Guía de streaming desde PC para Quest 3
      • Guía de desarrollo con OpenXR
      • Guía de Business Streaming para Pico 4 Ultra
      • Glosario
      • Preguntas frecuentes
      • Contáctanos
      • Guía de desarrollo de funciones

        • Guía de desarrollo de renderizado mixto de varias escenas
        • Guía de desarrollo de recepción de sombras
        • Guía de desarrollo de iluminación en tiempo real
        • Guía de desarrollo de renderizado multicámara
        • Guía de desarrollo de renderizado de imágenes de alta resolución
        • Guía de desarrollo de compatibilidad de formatos de datos LCC y LCC2
        • Guía de desarrollo del efecto de carga
        • Guía de desarrollo de cambio de modo de renderizado Mip
        • Guía de desarrollo de ajuste a bordes y esquinas
        • Guía de desarrollo de decal proyectado
        • Guía de desarrollo de decal proyectado de vídeo
        • Guía de desarrollo de resaltado
        • Guía de desarrollo de recorte en tiempo real
        • Guía de desarrollo de ajuste de color
        • Guía de desarrollo para obtener los datos de malla de la escena
        • Guía de desarrollo del modo Editor
        • Guía de configuración de parámetros de rendimiento de renderizado
        • Guía de desarrollo de co-empaquetado de aplicación y datos
        • Guía de desarrollo del sistema de colisiones
        • Guía de problemas de permisos de desarrollo en macOS
        • Guía de desarrollo de compilación y empaquetado para dispositivos iOS
      • Referencia de API

        • LCCManager
        • Renderer
        • Tipos de datos
      • Registro de cambios

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

      • Introducción
      • Guía de optimización del rendimiento de renderizado
      • Configuración de gráficos
      • Preguntas frecuentes
      • Referencia de API

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

        • 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

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:

  1. 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.
  2. 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.frame para 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.
Diagrama de la cadena de datos del decal proyectado de vídeo

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:

  1. La escena ya tiene Main Camera, LCCManager, LCCRenderer y Projector configurados (consulta Guía de desarrollo de decal proyectado · Paso 1).
  2. 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.
  3. 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).
  4. Prepara un activo de vídeo: puede ser un activo VideoClip dentro 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 a SetupVideo() para iniciar VideoPlayer.Prepare(), y luego se inicia la Load de 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 RenderTexture y Texture2D se toman de la resolución real del vídeo (vp.width / vp.height), por lo que se crean en la devolución de llamada prepareCompleted OnVideoPrepared.
  • El decal solo se activa después de que la escena termine de cargarse: coherente con el decal normal, SetDecalTexture / SetDecalEnabled se colocan en la devolución de llamada OnSceneLoaded. Si el vídeo no está listo en ese momento, m_frameTex es null y se omite la primera configuración de textura; se rellena cuando llega el primer fotograma en Update().

Paso 2: Adjuntar el script y configurar las propiedades

  1. Adjunta el script VideoStreaming.cs al GameObject LCCRenderer en la ventana Jerarquía.
  2. En la ventana Inspector, arrastra el GameObject LCCManager a la propiedad Manager del script.
  3. Arrastra el GameObject Projector a la propiedad Projector del script.
  4. Rellena la ruta del archivo de la escena LCC en la propiedad File Path.
  5. 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.
  6. 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

Ajustes de propiedad del Inspector para el script VideoStreaming

CampoSignificadoValor por defecto
ManagerLa instancia LCCManager en la escenaRequerido
File PathUna ruta de escena LCC válidaRequerido
Video ClipEl activo de vídeo a proyectar, mayor prioridad que Video UrlVacío
Video UrlRuta / URL del archivo de vídeo, usada cuando no se especifica ningún clipVacío
Video LoopSi se reproduce en bucleMarcado
ProjectorPose del proyector, +Z es la dirección de proyección; vacío usa el Transform de este objetoVacío
FovCampo de visión en perspectiva del proyector60
AspectRelació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ídeo0 (auto)
Near / FarPlanos de recorte cercano / lejano del frustum del proyector0.1 / 100
OpacityOpacidad general del decal1
Edge FadeAncho de desvanecimiento del borde (relación UV), 0 es un borde duro0.1
Update Every FrameActualiza proyector / apariencia cada fotograma, cómodo para el ajuste en tiempo de ejecuciónMarcado
OcclusionSi se habilita el culling por oclusiónOff
Occlusion Cutoff / Bias / Footprint / ResolutionParámetros de calidad del culling por oclusión; consulta la Guía de desarrollo de decal proyectado0 / 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

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ámetroOrigenEfecto sobre la alineación
Posición del proyector (Projector.position)Transform del proyectorDetermina 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_fovDetermina 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_aspectDetermina 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_farDetermina 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:

  1. 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.
  2. 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).
  3. Ajusta Fov para controlar el tamaño de cobertura del fotograma.
  4. 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.
  5. 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 los parámetros del proyector sobre dónde y a qué tamaño aterriza el fotograma de vídeo en la nube de puntos

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: ReadPixels es 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 índice frame los refleja fielmente; el script no necesita sincronización adicional.

¿Por qué no usar un temporizador por fotograma?

Acumular manualmente Time.deltaTime para 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. Leer VideoPlayer.frame directamente convierte al reproductor en la única fuente de verdad — lo más simple y robusto.

Acerca de Update Every Frame:

El m_updateEveryFrame del 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 de VideoPlayer.frame y 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 + Apply es 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 GPU Graphics.CopyTexture(m_videoRT, m_frameTex), pero los formatos de origen y destino deben coincidir (RenderTextureFormat.ARGB32 corresponde a TextureFormat.RGBA32), y algunas plataformas restringen CopyTexture.
  • SetDecalTexture solo acepta Texture2D. Esta es exactamente la razón por la que la RenderTexture debe leerse de vuelta a una Texture2D; no puedes pasar directamente la RenderTexture del VideoPlayer a la interfaz del decal.
  • Reutiliza el búfer de fotogramas, no hagas new en cada fotograma. En el ejemplo, m_frameTex se crea solo una vez cuando el vídeo está listo, y la misma Texture2D se reutiliza en cada fotograma posterior, evitando asignaciones / GC frecuentes.
  • Libera los recursos a tiempo. En OnDestroy, haz Release() y Destroy de la RenderTexture y la Texture2D, y anula el registro de prepareCompleted, para evitar fugas.
  • El audio no está conectado. El ejemplo establece audioOutputMode como None y solo demuestra las imágenes. Si se necesita sonido, cambia al modo AudioSource y adjunta un AudioSource.

Resumen de diferencias con el decal normal

AspectoDecal normal (Decal.cs)Decal de vídeo (VideoStreaming.cs)
Contenido del decalTexture2D estáticaTexture2D de la decodificación de vídeo por fotograma
Fuente de la texturaArrastra una imagen en el InspectorVideoPlayer decodifica → RenderTexture → lectura de vuelta
Momento de actualización de la texturaSe establece una vezSe actualiza fotograma a fotograma al pulso de VideoPlayer.frame
Componente adicionalNingunoAddComponent<VideoPlayer>() en tiempo de ejecución
Valor automático de AspectRelación de aspecto de la imagenRelación de aspecto del vídeo
El resto (proyector / oclusión / API)Exactamente igualExactamente 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ámetroTipoDescripción
texTexture2DTextura 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.