Руководство по разработке видеопроецируемой декали
Обзор возможностей
Видеопроецируемая декаль — это расширение Проецируемой декали: проецируемое содержимое больше не является статичным изображением, а видео. Подход заключается в декодировании видео встроенным в Unity VideoPlayer и динамическом обновлении каждого кадра на текстуре декали, чтобы видео проецировалось на поверхность облака точек 3DGS, подобно слайд-проектору.
Поскольку сама декаль — это перспективная проекция, зафиксированная в мировом пространстве, видеокадр «наклеивается» на поверхность облака точек в соответствии с позой проектора. Точки внутри фрустума проектора окрашиваются текущим видеокадром, а точки вне фрустума не затрагиваются.
Примечание:
Как и обычная декаль, видеодекаль действует только на PC / Mac (конвейер URP). Вызовы связанных интерфейсов на платформах mobile или VR игнорируются SDK и выводят предупреждение.
Это руководство объясняет только инкрементальные части, связанные с «видео». По основам — настройке сцены (Main Camera / LCCManager / LCCRenderer / Projector), добавлению Decal Depth Feature в URP Renderer, настройке отсечения окклюзией и подробным API декалей — сначала прочтите Руководство по разработке проецируемой декали.
Принцип реализации
Интерфейс декали SDK SetDecalTexture(Texture2D tex) принимает только Texture2D, но поддерживает динамическое обновление этой текстуры во время выполнения. Результатом декодирования VideoPlayer Unity является RenderTexture (на GPU). Поэтому весь конвейер данных таков:
VideoPlayer декодирует → RenderTexture (GPU) → копирование в Texture2D → SetDecalTexture(кадр-текстура) → проекция на облако точек
Есть два ключевых момента, которые также являются двумя «выравниваниями», подчёркиваемыми в этом руководстве:
- Пространственное выравнивание: позиция, размер и ориентация видеокадра, проецируемого на облако точек, определяются параметрами проектора (поза, FOV, Aspect, Near/Far); см. Пространственное выравнивание видео и сцены.
- Временное выравнивание: частота кадров видео часто значительно ниже частоты кадров рендеринга сцены (сцена может работать на сотнях FPS), поэтому нельзя копировать и отправлять текстуру один раз за кадр рендеринга. Используйте
VideoPlayer.frameдля обнаружения появления нового декодированного кадра и обновляйте только при появлении нового кадра; см. Выравнивание частоты кадров видео и частоты кадров рендеринга.

Конвейер данных видеопроецируемой декали: VideoPlayer → RenderTexture → Texture2D → SetDecalTexture
Предварительные требования
Видеодекаль использует тот же набор предварительных требований, что и обычная декаль. Перед началом подтвердите следующее:
- В сцене уже настроены Main Camera, LCCManager, LCCRenderer и Projector (см. Руководство по разработке проецируемой декали · Шаг 1).
- В настоящее время используется конвейер URP; если требуется отсечение окклюзией, Decal Depth Feature зарегистрирован в URP Renderer Asset.
- Если Console сообщает об ошибках и эффект не виден, отключите режим совместимости Render Graph (см. раздел FAQ Руководство по разработке проецируемой декали).
- Подготовьте видеоассет: это может быть ассет
VideoClipвнутри проекта или URL видеофайла на диске / в сети (например,file:///D:/sample.mp4).
Шаг 1: Напишите скрипт
Создайте новый скрипт C# с именем VideoStreaming.cs. На основе каркаса обычного Decal.cs он заменяет «статичную текстуру декали» на «создание VideoPlayer во время выполнения для декодирования видео и обновление текстуры декали в Update() в соответствии с декодированными кадрами». Его можно скопировать непосредственно в ваш проект.
using LCCCore;
using UnityEngine;
using UnityEngine.Video;
public class VideoStreaming : MonoBehaviour
{
[Header("Обязательные")]
[Tooltip("Экземпляр LCCManager в сцене")]
public LCCManager m_manager;
[Tooltip("Действительный путь к LCC-сцене")]
public string m_filePath;
[Header("Источник видео (декодированные кадры проецируются как декаль)")]
[Tooltip("Видеоклип для проецирования. Имеет приоритет над URL ниже")]
public VideoClip m_videoClip;
[Tooltip("Путь к видеофайлу / URL, используется, когда клип не назначен (например, file:///D:/xxx.mp4)")]
public string m_videoUrl;
[Tooltip("Зацикленное воспроизведение")]
public bool m_videoLoop = true;
[Header("Проектор (проецирует вдоль +Z)")]
[Tooltip("Поза проектора. Оставьте пустым, чтобы использовать Transform этого объекта")]
public Transform m_projector;
[Range(1f, 179f)] public float m_fov = 60f;
[Tooltip("Соотношение сторон проекции; при <=0 автоматически используется соотношение сторон видео")]
public float m_aspect = 0f;
public float m_near = 0.1f;
public float m_far = 100f;
[Header("Внешний вид декали")]
[Range(0f, 1f)] public float m_opacity = 1.0f;
[Range(0f, 0.5f)][Tooltip("Ширина затухания края (отношение UV). 0 = жёсткий край")]
public float m_edgeFade = 0.1f;
[Tooltip("Обновлять проекцию каждый кадр, чтобы можно было перетаскивать проектор / менять параметры во время выполнения")]
public bool m_updateEveryFrame = true;
[Header("Отсечение окклюзией (только PC/Mac)")]
[Tooltip("При включении задние грани/окклюдированные точки за передней геометрией больше не окрашиваются декалью (эффект слайд-проектора); при выключении — эффект рентгена")]
public bool m_occlusion = false;
[Range(0f, 1f)][Tooltip("Порог непрозрачности: только сплаты с непрозрачностью >= этого значения участвуют в карте глубины, отфильтровывая плавающие/туманные точки. 0 = участвуют все")]
public float m_occlusionCutoff = 0f;
[Range(0f, 0.5f)][Tooltip("Относительный допуск глубины. Слишком мало -> самоокклюзия у поверхности; слишком много -> утечка рентгеновского окрашивания. По умолчанию 0.05")]
public float m_occlusionBias = 0.05f;
[Range(0, 16)][Tooltip("Ограничение квадратного радиуса следа (px), главная ручка для устранения рентгеновского просвечивания. 0 = один тексель, по умолчанию 2")]
public int m_occlusionFootprint = 2;
[Tooltip("Разрешение карты глубины (px, степень двойки 256~4096). Уменьшите, чтобы заполнить дыры на скользящих углах. По умолчанию 1024")]
public int m_occlusionResolution = 1024;
// -- Состояние во время выполнения --
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;
// -- Состояние видео --
private VideoPlayer m_videoPlayer;
private RenderTexture m_videoRT; // VideoPlayer декодирует видео сюда (GPU)
private Texture2D m_frameTex; // текущий кадр копируется сюда и подаётся в декаль
private bool m_videoReady;
private long m_lastVideoFrame = -1; // индекс последнего кадра, отправленного в декаль
void Start()
{
if (m_manager == null || string.IsNullOrEmpty(m_filePath))
{
Debug.LogError("[VideoStreaming] m_manager или m_filePath не назначены.");
enabled = false;
return;
}
// 1) Инициализируем рендер-конвейер в режим редактора PC для одной сцены
m_manager.SetPlatformType(PlatformType.PC);
m_manager.SwitchRenderPass(ActiveRenderMode.SingleRender);
m_manager.SetEditorMode(true);
// 2) Начинаем декодирование видео параллельно с загрузкой сцены
SetupVideo();
// 3) Загружаем сцену облака точек, затем включаем декаль (декаль — глобальное состояние; безопаснее всего задавать её после готовности сцены)
var renderer = m_manager.GetRender(this.transform);
Debug.Log($"[VideoStreaming] Начинаем загрузку: {m_filePath}");
renderer.Load(m_filePath,
_ => { /* обратный вызов прогресса, в примере не используется */ },
OnSceneLoaded);
}
/// <summary>Создаёт VideoPlayer во время выполнения и начинает подготовку клипа/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; // демо: без звука
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;
}
// Выделяем буфер кадра, как только разрешение известно, затем начинаем воспроизведение.
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] Видео подготовлено: {w}x{h}, воспроизведение.");
}
private void OnSceneLoaded()
{
Debug.Log("[VideoStreaming] Загрузка завершена, включение проекции декали.");
// -- Базовая проекция: 5 API --
// Содержимое текстуры определяется декодированными кадрами в Update(); здесь сначала отправляем текущий доступный кадр.
if (m_frameTex != null) m_manager.SetDecalTexture(m_frameTex);
m_manager.SetDecalOpacity(m_opacity); // общая непрозрачность
m_manager.SetDecalEdgeFade(m_edgeFade); // затухание края
ApplyProjection(); // поза проектора (внутри вызывает SetDecalProjection)
m_manager.SetDecalEnabled(true); // включаем декаль (ключевое слово _DECAL)
// -- Отсечение окклюзией: 2 API -- должны вызываться после ApplyProjection (карте глубины нужна валидная матрица VP проектора)
m_manager.SetDecalOcclusionSettings(BuildOcclusionSettings());
m_manager.SetDecalOcclusion(m_occlusion);
CacheOcclusionState();
m_loaded = true;
}
void Update()
{
if (!m_loaded) return;
// --- Обновление кадра видео, отделено от FPS рендеринга ---
// VideoPlayer.frame увеличивается только с собственной скоростью воспроизведения видео. Даже если сцена рендерится на сотнях FPS,
// индекс остаётся тем же между двумя соседними видеокадрами, поэтому только когда фактически появляется новый декодированный кадр,
// мы копируем этот кадр и снова отправляем текстуру декали.
if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
{
m_lastVideoFrame = m_videoPlayer.frame;
// Копируем RenderTexture на GPU в Texture2D, принимаемый API декали.
// Это выполняется только с частотой кадров видео, поэтому стоимость чтения приемлема.
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);
}
// переключаем окклюзию во время выполнения
if (m_occlusion != m_lastOcclusion)
{
m_manager.SetDecalOcclusion(m_occlusion);
m_lastOcclusion = m_occlusion;
}
// если какой-либо параметр качества окклюзии изменён -> отправляем структуру один раз
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();
}
// обновляем проектор / внешний вид каждый кадр для удобной настройки во время выполнения
if (!m_updateEveryFrame) return;
m_manager.SetDecalOpacity(m_opacity);
m_manager.SetDecalEdgeFade(m_edgeFade);
ApplyProjection();
}
/// <summary>Строит матрицу проекции из текущей позы проектора и параметров перспективы.</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>Собирает 4 параметра окклюзии в структуру.</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);
}
// -- Удобные точки входа для отладки (правая кнопка в Inspector) --
[ContextMenu("Включить декаль")] private void DecalOn() { m_manager?.SetDecalEnabled(true); }
[ContextMenu("Выключить декаль")] private void DecalOff() { m_manager?.SetDecalEnabled(false); }
[ContextMenu("Включить окклюзию")] private void OcclusionOn() { m_occlusion = true; m_manager?.SetDecalOcclusion(true); m_lastOcclusion = true; }
[ContextMenu("Выключить окклюзию")] private void OcclusionOff() { m_occlusion = false; m_manager?.SetDecalOcclusion(false); m_lastOcclusion = false; }
}
Примечание:
- Видео и загрузка сцены выполняются параллельно: в
Start()сначала вызываетсяSetupVideo()для запускаVideoPlayer.Prepare(), а затем инициируетсяLoadсцены облака точек. Они не блокируют друг друга.- Буфер кадра выделяется только после готовности видео: размеры
RenderTextureиTexture2Dберутся из реального разрешения видео (vp.width/vp.height), поэтому они создаются в обратном вызовеprepareCompleted—OnVideoPrepared.- Декаль включается только после завершения загрузки сцены: как и в случае обычной декали,
SetDecalTexture/SetDecalEnabledпомещены в обратный вызовOnSceneLoaded. Если в этот момент видео ещё не готово,m_frameTexравен null, и первая установка текстуры пропускается; она заполняется при появлении первого кадра вUpdate().
Шаг 2: Прикрепите скрипт и настройте свойства
- Прикрепите скрипт
VideoStreaming.csк GameObject LCCRenderer в окне Hierarchy. - В окне Inspector перетащите GameObject LCCManager на свойство Manager скрипта.
- Перетащите GameObject Projector на свойство Projector скрипта.
- Заполните путь к файлу LCC-сцены в свойстве File Path.
- Укажите источник видео, выбрав одно из двух:
- Перетащите ассет VideoClip из проекта на свойство Video Clip; или
- Заполните путь к видеофайлу в свойстве Video Url (например,
file:///D:/sample.mp4). - Когда заполнены оба, Video Clip имеет приоритет.
- Остальные свойства можно настроить по мере необходимости; их значения показаны в таблице ниже.

Настройки свойств Inspector для скрипта VideoStreaming
| Поле | Значение | Значение по умолчанию |
|---|---|---|
| Manager | Экземпляр LCCManager в сцене | Обязательное |
| File Path | Действительный путь к LCC-сцене | Обязательное |
| Video Clip | Видеоассет для проецирования, приоритет выше, чем Video Url | Пусто |
| Video Url | Путь к видеофайлу / URL, используется, когда клип не указан | Пусто |
| Video Loop | Зацикливать ли воспроизведение | Отмечено |
| Projector | Поза проектора, +Z — направление проекции; пусто — используется Transform этого объекта | Пусто |
| Fov | Перспективное поле зрения проектора | 60 |
| Aspect | Соотношение сторон проекции; при значении меньше или равном 0 вычисляется автоматически из соотношения сторон видео | 0 (авто) |
| Near / Far | Ближняя / дальняя плоскости отсечения фрустума проектора | 0.1 / 100 |
| Opacity | Общая непрозрачность декали | 1 |
| Edge Fade | Ширина затухания края (отношение UV), 0 — жёсткий край | 0.1 |
| Update Every Frame | Обновлять проектор / внешний вид каждый кадр, удобно для настройки во время выполнения | Отмечено |
| Occlusion | Включать ли отсечение окклюзией | Выкл |
| Occlusion Cutoff / Bias / Footprint / Resolution | Параметры качества отсечения окклюзией; см. Руководство по разработке проецируемой декали | 0 / 0.05 / 2 / 1024 |
Шаг 3: Запустите и наблюдайте эффект
Нажмите Play в Unity. Видео начнёт воспроизводиться после завершения Prepare; после завершения загрузки сцены декаль включится автоматически, и видеокадры будут проецироваться на поверхность 3DGS кадр за кадром. При отмеченном Update Every Frame вы можете напрямую перетаскивать проектор в окне Scene и видеть движение видеокадра по поверхности облака точек в реальном времени.

Видеокадры, спроецированные на поверхность 3DGS после запуска
Пространственное выравнивание видео и сцены
«Пространственное выравнивание» означает точную проекцию видеокадра на облако точек в нужной позиции, ориентации и размере. Позиция видеокадра полностью определяется проектором (SetDecalProjection), независимо от содержимого видео. Параметры, влияющие на выравнивание, следующие:
| Параметр | Источник | Влияние на выравнивание |
|---|---|---|
| Позиция проектора (Projector.position) | Transform проектора | Определяет мировую координату вершины фрустума, то есть откуда проецируется видео. Перемещение сдвигает место посадки кадра на облаке точек. |
| Ориентация проектора (Projector.rotation) | Transform проектора | +Z — направление проекции. Поворот проектора меняет направление проекции, и кадр соответственно сдвигается / наклоняется на поверхности. |
| Fov (вертикальное поле зрения) | Поле скрипта m_fov | Определяет угол раскрытия фрустума, напрямую влияя на размер кадра на облаке точек: чем больше FOV, тем больше кадр; чем меньше, тем более сконцентрирован. Диапазон [1, 179]. |
| Aspect (соотношение сторон) | Поле скрипта m_aspect | Определяет горизонтально-вертикальное соотношение кадра. При значении 0 автоматически используется соотношение сторон разрешения видео (m_videoRT.width / height), что рекомендуется, чтобы видео не растягивалось. Неправильное ручное значение вызывает растяжение кадра. |
| Near / Far (ближняя / дальняя плоскости отсечения) | Поля скрипта m_near / m_far | Определяют эффективный интервал глубины фрустума вдоль направления проекции. Только точки в пределах [near, far] окрашиваются. Слишком малый Far отсекает дальние облака точек, а слишком большой Near пропускает ближние. Интервал должен покрывать диапазон глубины целевого облака точек. |
Общие шаги выравнивания:
- Сначала оставьте Aspect равным 0 (авто), чтобы избежать искажения кадра, и сосредоточьтесь на настройке позиции и размера.
- Перетаскивайте позицию и ориентацию Projector, чтобы отцентрировать кадр на целевой области (отметьте Update Every Frame для предпросмотра в реальном времени).
- Настройте Fov для управления размером покрытия кадра.
- Подтвердите, что интервал Near / Far полностью покрывает глубину целевого облака точек, чтобы избежать отсечения кадра плоскостями отсечения.
- Если нужно принудительно задать конкретное соотношение (например, проецировать видео 16:9 на квадратную область), задайте Aspect вручную.
Совет:
Соотношение кадра видео управляется Aspect, а разрешение видео влияет только на чёткость и стоимость чтения, но не на физический размер, проецируемый на облако точек — размер определяется Fov и дистанцией проекции вместе.

Влияние позы проектора / Fov / Aspect / Near-Far на место и размер посадки видеокадра на облаке точек
Выравнивание частоты кадров видео и частоты кадров рендеринга
Это самая критичная часть видеодекали. Частота кадров видео (например, 25/30 FPS) обычно значительно ниже частоты кадров рендеринга сцены (сцена 3DGS может работать на сотнях FPS). Если выполнять «чтение + SetDecalTexture» на каждом кадре рендеринга, возникают две проблемы:
- Избыточная работа: один и тот же видеокадр многократно копируется и многократно отправляется, что является чистой тратой.
- Снижение производительности:
ReadPixels— это чтение GPU→CPU с нетривиальной стоимостью; выполнение его на каждом кадре при высокой частоте рендеринга вызывает заметные падения кадров.
Стратегия развязки: используйте VideoPlayer.frame как такт
VideoPlayer.frame — это текущий индекс декодированного кадра, который увеличивается только с собственной скоростью воспроизведения видео. Какой бы высокой ни была частота рендеринга, этот индекс не меняется между двумя видеокадрами. Исходя из этого, обновляйте текстуру только тогда, когда «индекс меняется»:
if (m_videoReady && m_videoPlayer.frame != m_lastVideoFrame)
{
m_lastVideoFrame = m_videoPlayer.frame;
// Читаем обратно только один раз и отправляем декаль при появлении нового декодированного кадра
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);
}
Таким образом:
- Когда сцена рендерится на 300 FPS, а видео — 30 FPS, только около 30 из 300 кадров рендеринга фактически выполняют чтение и отправку; остальные кадры пропускаются напрямую через
if. - Такт управляется самим плеером; не нужен рукописный таймер для соответствия частоте кадров видео, и кадры не пропадают и не дублируются из-за колебаний частоты кадров рендеринга.
- Скорость, зацикливание и пауза видео управляются внутри
VideoPlayer, и индексframeправдиво отражает их; скрипту не нужна дополнительная синхронизация.
Почему бы не использовать покадровый таймер?
Ручное накопление
Time.deltaTimeдля аппроксимации интервала видеокадров требует обработки как джиттера частоты рендеринга, так и изменений скорости видео / пропуска кадров, и может легко расходиться с фактическим внутренним прогрессом декодирования плеера. Прямое чтениеVideoPlayer.frameделает плеер единственным источником истины — проще и надёжнее.
Об Update Every Frame:
m_updateEveryFrameв скрипте управляет тем, обновляются ли поза проектора / непрозрачность / затухание каждый кадр, что является отдельным вопросом от обновления видеокадра. Обновление видеокадра всегда следует тактуVideoPlayer.frameи не зависит от этого переключателя.
Примечания о производительности и форматах
- Стоимость чтения пропорциональна разрешению.
ReadPixels+Apply— это круговой путь GPU→CPU→GPU; чем выше разрешение, тем медленнее. Этот пример использует его для простоты и достаточной демонстрации. Если разрешение видео очень высокое или производительность критична, можно переключиться на чистое копирование на GPUGraphics.CopyTexture(m_videoRT, m_frameTex), но исходный и целевой форматы должны совпадать (RenderTextureFormat.ARGB32соответствуетTextureFormat.RGBA32), и некоторые платформы ограничивают CopyTexture. SetDecalTextureпринимает толькоTexture2D. Именно поэтомуRenderTextureдолжен быть прочитан обратно вTexture2D; нельзя напрямую передатьRenderTextureVideoPlayerв интерфейс декали.- Переиспользуйте буфер кадра, не создавайте
newкаждый кадр. В примереm_frameTexсоздаётся только один раз при готовности видео, и тот жеTexture2Dпереиспользуется каждый кадр, избегая частого выделения / GC. - Своевременно освобождайте ресурсы. В
OnDestroyвызовитеRelease()иDestroyдляRenderTextureиTexture2D, а также отпишитесь отprepareCompleted, чтобы избежать утечек. - Звук не подключён. В примере
audioOutputModeзадан какNoneи демонстрируется только визуальная часть. Если нужен звук, измените на режимAudioSourceи прикрепитеAudioSource.
Сводка отличий от обычной декали
| Аспект | Обычная декаль (Decal.cs) | Видеодекаль (VideoStreaming.cs) |
|---|---|---|
| Содержимое декали | Статичный Texture2D | Texture2D из покадрового декодирования видео |
| Источник текстуры | Перетаскивание изображения в Inspector | VideoPlayer декодирует → RenderTexture → чтение обратно |
| Момент обновления текстуры | Задаётся один раз | Обновляется кадр за кадром по такту VideoPlayer.frame |
| Дополнительный компонент | Нет | AddComponent<VideoPlayer>() во время выполнения |
| Значение Aspect авто | Соотношение сторон изображения | Соотношение сторон видео |
| Остальное (проектор / окклюзия / API) | Точно так же | Точно так же |
Задействованные ключевые API
На стороне SDK видеодекаль дополнительно использует только возможность динамического обновления SetDecalTexture; остальные API декалей точно такие же, как у обычной декали. См. Руководство по разработке проецируемой декали · API, связанные с декалью.
SetDecalTexture (динамическое обновление)
// Возвращаемое значение: void
public void SetDecalTexture(Texture2D tex)
Задаёт текстуру декали. RGB — цвет, а A — собственная маска декали. Этот метод поддерживает повторный вызов во время выполнения: каждый раз при передаче нового Texture2D текущее содержимое декали немедленно заменяется — это и есть основа покадровой видеопроекции: вызывайте его один раз при появлении нового декодированного кадра в Update().
| Параметр | Тип | Описание |
|---|---|---|
tex | Texture2D | Текстура декали, может многократно заменяться во время выполнения |
Другие связанные встроенные типы Unity (VideoPlayer / RenderTexture / Texture2D) — это стандартные API Unity; по их использованию см. официальную документацию Unity.