/*----------------------------------------------------------------
// 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
///
/// 3D MeshRenderer 视频渲染组件
/// 继承自 LeviathanVideoDecoderBase,用于在 3D 物体(如 Cube、Plane)上显示视频
///
[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);
}
///
/// 启用渲染目标
///
protected override void OnEnableRenderTarget()
{
MeshRenderer meshRenderer = GetComponent();
if (meshRenderer)
{
meshRenderer.enabled = true;
}
}
///
/// 禁用渲染目标
///
protected override void OnDisableRenderTarget()
{
MeshRenderer meshRenderer = GetComponent();
if (meshRenderer)
{
meshRenderer.enabled = false;
}
}
///
/// 更新渲染目标尺寸
/// 3D渲染器不需要调整尺寸,由模型本身决定
///
protected override void OnUpdateRenderTargetSize()
{
// 3D渲染器不需要像UI那样自动调整尺寸
// 尺寸由模型本身决定
}
///
/// 初始化材质
///
protected override void OnInitMaterial()
{
MeshRenderer meshRenderer = GetComponent();
if (meshRenderer)
{
UpdateMeshMaterial(meshRenderer);
}
}
///
/// 更新材质
///
protected override void OnUpdateMaterial()
{
MeshRenderer meshRenderer = GetComponent();
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;
}
}
///
/// 设置是否翻转Y轴
///
public void SetReverseY(bool reverseY)
{
_reverseY = reverseY;
if (_material != null)
{
_material.SetFloat("_ReverseY", _reverseY ? 1 : 0);
}
}
///
/// 设置材质索引
///
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