/*---------------------------------------------------------------- // 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