video plugins
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
/*----------------------------------------------------------------
|
||||
// Copyright (C) 2025 Beijing All rights reserved.
|
||||
//
|
||||
// Author: huachangmiao
|
||||
// Create Date: 2025/04/11
|
||||
// Module Describe: 3D MeshRenderer 视频渲染组件
|
||||
//----------------------------------------------------------------*/
|
||||
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 3D MeshRenderer 视频渲染组件
|
||||
/// 继承自 LeviathanVideoDecoderBase,用于在 3D 物体(如 Cube、Plane)上显示视频
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(MeshRenderer))]
|
||||
public unsafe class LeviathanVideo3DRenderer : LeviathanVideoDecoderBase
|
||||
{
|
||||
private static readonly int MainTexIdx = Shader.PropertyToID("_MainTex");
|
||||
|
||||
[Header("3D Renderer Settings")]
|
||||
[SerializeField]
|
||||
[Tooltip("是否翻转Y轴UV,用于修正不同模型的UV方向")]
|
||||
private bool _reverseY = true;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("材质所在的索引(用于多材质渲染器)")]
|
||||
private int _materialIndex = 0;
|
||||
|
||||
private Material _material;
|
||||
|
||||
private new static void Log(string message)
|
||||
{
|
||||
LeviathanLogConfig.Log("Video3DRenderer", message);
|
||||
}
|
||||
|
||||
private new static void LogWarning(string message)
|
||||
{
|
||||
LeviathanLogConfig.LogWarning("Video3DRenderer", message);
|
||||
}
|
||||
|
||||
private new static void LogError(string message)
|
||||
{
|
||||
LeviathanLogConfig.LogError("Video3DRenderer", message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用渲染目标
|
||||
/// </summary>
|
||||
protected override void OnEnableRenderTarget()
|
||||
{
|
||||
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (meshRenderer)
|
||||
{
|
||||
meshRenderer.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用渲染目标
|
||||
/// </summary>
|
||||
protected override void OnDisableRenderTarget()
|
||||
{
|
||||
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (meshRenderer)
|
||||
{
|
||||
meshRenderer.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新渲染目标尺寸
|
||||
/// 3D渲染器不需要调整尺寸,由模型本身决定
|
||||
/// </summary>
|
||||
protected override void OnUpdateRenderTargetSize()
|
||||
{
|
||||
// 3D渲染器不需要像UI那样自动调整尺寸
|
||||
// 尺寸由模型本身决定
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化材质
|
||||
/// </summary>
|
||||
protected override void OnInitMaterial()
|
||||
{
|
||||
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (meshRenderer)
|
||||
{
|
||||
UpdateMeshMaterial(meshRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新材质
|
||||
/// </summary>
|
||||
protected override void OnUpdateMaterial()
|
||||
{
|
||||
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (meshRenderer)
|
||||
{
|
||||
UpdateMeshMaterial(meshRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdateMeshMaterial(MeshRenderer meshRenderer)
|
||||
{
|
||||
if (_textures == null || _textures.Length < 3) return;
|
||||
|
||||
// 创建新材质或更新现有材质
|
||||
Material newMaterial;
|
||||
switch (_alphaType)
|
||||
{
|
||||
case PlayAlphaType.RightSide:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_3D_Alpha_Right"));
|
||||
newMaterial.SetFloat(ValidWidthRatioIdx, (float)_videoValidWidth / _videoWidth);
|
||||
break;
|
||||
case PlayAlphaType.BottomSide:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_3D_Alpha_Bottom"));
|
||||
break;
|
||||
case PlayAlphaType.ChromaKey:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_3D_ChromaKey"));
|
||||
break;
|
||||
case PlayAlphaType.None:
|
||||
default:
|
||||
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_3D"));
|
||||
break;
|
||||
}
|
||||
|
||||
// 设置纹理
|
||||
newMaterial.SetTexture(MainTexIdx, _textures[0]);
|
||||
newMaterial.SetTexture(UTexIdx, _textures[1]);
|
||||
newMaterial.SetTexture(VTexIdx, _textures[2]);
|
||||
|
||||
// 设置Y轴翻转
|
||||
newMaterial.SetFloat("_ReverseY", _reverseY ? 1 : 0);
|
||||
|
||||
// 清理旧材质
|
||||
if (_material != null)
|
||||
{
|
||||
Destroy(_material);
|
||||
}
|
||||
_material = newMaterial;
|
||||
|
||||
// 应用材质到渲染器
|
||||
var materials = meshRenderer.materials;
|
||||
if (_materialIndex >= 0 && _materialIndex < materials.Length)
|
||||
{
|
||||
materials[_materialIndex] = newMaterial;
|
||||
meshRenderer.materials = materials;
|
||||
}
|
||||
else
|
||||
{
|
||||
meshRenderer.material = newMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
// 清理材质
|
||||
if (_material != null)
|
||||
{
|
||||
Destroy(_material);
|
||||
_material = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否翻转Y轴
|
||||
/// </summary>
|
||||
public void SetReverseY(bool reverseY)
|
||||
{
|
||||
_reverseY = reverseY;
|
||||
if (_material != null)
|
||||
{
|
||||
_material.SetFloat("_ReverseY", _reverseY ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置材质索引
|
||||
/// </summary>
|
||||
public void SetMaterialIndex(int index)
|
||||
{
|
||||
_materialIndex = index;
|
||||
if (_textures != null && _textures.Length > 0)
|
||||
{
|
||||
OnUpdateMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
// 使用 delayCall 避免在 OnValidate 中调用 SendMessage 相关操作
|
||||
var currentBytesFile = bytesFile;
|
||||
var lastBytesFile = _lastBytesFile;
|
||||
UnityEditor.EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
SetAlphaType(_alphaType);
|
||||
|
||||
// 检测 bytesFile 是否发生变化
|
||||
if (currentBytesFile != lastBytesFile)
|
||||
{
|
||||
_lastBytesFile = currentBytesFile;
|
||||
if (IsPlaying && currentBytesFile != null)
|
||||
{
|
||||
Log($"编辑器中检测到 bytesFile 变化,自动播放新视频");
|
||||
Stop();
|
||||
Play();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(LeviathanVideo3DRenderer))]
|
||||
public class LeviathanVideo3DRendererEditor : Editor
|
||||
{
|
||||
// 格式化时间:秒转换为 HH:MM:SS.mmm 格式
|
||||
private static string FormatTimeSeconds(double totalSeconds)
|
||||
{
|
||||
int hours = (int)(totalSeconds / 3600);
|
||||
int minutes = (int)((totalSeconds % 3600) / 60);
|
||||
int seconds = (int)(totalSeconds % 60);
|
||||
int milliseconds = (int)((totalSeconds % 1) * 1000);
|
||||
return $"{hours:D2}:{minutes:D2}:{seconds:D2}.{milliseconds:D3}";
|
||||
}
|
||||
|
||||
// 格式化时间:微秒转换为 HH:MM:SS.mmm 格式
|
||||
private static string FormatTimeMicroseconds(long microseconds)
|
||||
{
|
||||
return FormatTimeSeconds(microseconds / 1000000.0);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
var mono = (LeviathanVideo3DRenderer)target;
|
||||
|
||||
// Display video dimensions as read-only fields
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.TextField("帧数", $"{mono.PlayFrameIndex} / {mono.FrameCount}");
|
||||
// FrameInterval 单位是秒,Duration 单位是微秒
|
||||
EditorGUILayout.TextField("播放时间", FormatTimeSeconds(mono.PlayFrameIndex * mono.FrameInterval));
|
||||
EditorGUILayout.TextField("总时间", FormatTimeMicroseconds(mono.Duration));
|
||||
EditorGUILayout.TextField("FPS", mono.FrameInterval > 0 ? $"{1.0 / mono.FrameInterval:F2}" : "0");
|
||||
EditorGUILayout.TextField("纹理尺寸", $"{mono.VideoWidth} × {mono.VideoHeight}");
|
||||
EditorGUILayout.TextField("视频有效尺寸", $"{mono.VideoValidWidth} × {mono.VideoHeight}");
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
{
|
||||
bool oldValue = mono.IsPlaying;
|
||||
// 绘制checkbox
|
||||
bool newValue = EditorGUILayout.Toggle("isPlaying", oldValue);
|
||||
|
||||
// 如果用户切换了checkbox
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
if (newValue)
|
||||
{
|
||||
mono.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
mono.Stop();
|
||||
}
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
}
|
||||
{
|
||||
bool oldValue = mono.IsPause;
|
||||
// 绘制checkbox
|
||||
bool newValue = EditorGUILayout.Toggle("isPause", oldValue);
|
||||
|
||||
// 如果用户切换了checkbox
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
if (newValue)
|
||||
{
|
||||
mono.Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
mono.Resume();
|
||||
}
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
}
|
||||
this.Repaint();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user