using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; using System.Collections.Generic; [ExecuteAlways] public sealed class Voxelizer : MonoBehaviour, ITimeControl, IPropertyPreview { #region Editable attributes [SerializeField, Range(0, 1)] float _density = 0.05f; [SerializeField, Range(0, 10)] float _scale = 3; [SerializeField, Range(0, 20)] float _stretch = 5; [SerializeField, Range(0, 1000)] float _fallDistance = 1; [SerializeField, Range(0, 10)] float _fluctuation = 1; [SerializeField, ColorUsage(false, true)] Color _emissionColor1 = Color.black; [SerializeField, ColorUsage(false, true)] Color _emissionColor2 = Color.black; [SerializeField, ColorUsage(false, true)] Color _transitionColor = Color.white; [SerializeField, ColorUsage(false, true)] Color _lineColor = Color.white; [SerializeField] Renderer[] _renderers; #endregion #region Shader property IDs static class ShaderIDs { public static readonly int VoxelDensity = Shader.PropertyToID("_VoxelDensity"); public static readonly int VoxelScale = Shader.PropertyToID("_VoxelScale"); public static readonly int Stretch = Shader.PropertyToID("_Stretch"); public static readonly int FallDistance = Shader.PropertyToID("_FallDistance"); public static readonly int Fluctuation = Shader.PropertyToID("_Fluctuation"); public static readonly int EmissionHsvm1 = Shader.PropertyToID("_EmissionHsvm1"); public static readonly int EmissionHsvm2 = Shader.PropertyToID("_EmissionHsvm2"); public static readonly int TransitionColor = Shader.PropertyToID("_TransitionColor"); public static readonly int LineColor = Shader.PropertyToID("_LineColor"); public static readonly int EffectorPlane = Shader.PropertyToID("_EffectorPlane"); public static readonly int VoxelParams = Shader.PropertyToID("_VoxelParams"); public static readonly int AnimParams = Shader.PropertyToID("_AnimParams"); public static readonly int PrevEffectorPlane = Shader.PropertyToID("_PrevEffectorPlane"); public static readonly int LocalTime = Shader.PropertyToID("_LocalTime"); } #endregion #region Time and animation helpers float _controlTime = -1f; float _prevTime = 0f; Vector4 _prevEffectorPlane = Vector3.one * 1e5f; MaterialPropertyBlock _sheet; Vector4 GetCurrentEffectorPlane() { var normal = transform.forward; var scaleZ = Mathf.Abs(transform.localScale.z); if (scaleZ <= float.Epsilon) scaleZ = 1f; normal /= scaleZ; var dist = Vector3.Dot(normal, transform.position); return new Vector4(normal.x, normal.y, normal.z, dist); } Vector4 EffectorPlane { get { var targetPlane = GetCurrentEffectorPlane(); return Vector4.Lerp(_prevEffectorPlane, targetPlane, Time.deltaTime * 5f); } } float LocalTime => _controlTime < 0 ? (Application.isPlaying ? Time.time : 0f) : _controlTime; Vector4 ColorToHsvm(Color color) { var max = color.maxColorComponent; if (max <= 0f) return Vector4.zero; Color.RGBToHSV(color / max, out var h, out var s, out var v); return new Vector4(h, s, v, max); } #endregion #region Unity callbacks void Reset() { RefreshRenderers(); } void OnValidate() { RefreshRenderers(); } void OnEnable() { RefreshRenderers(); _prevEffectorPlane = GetCurrentEffectorPlane(); _prevTime = LocalTime; } void LateUpdate() { var renderers = GetRendererList(); if (renderers == null || renderers.Length == 0) return; if (_sheet == null) _sheet = new MaterialPropertyBlock(); else _sheet.Clear(); var currentPlane = GetCurrentEffectorPlane(); var plane = Vector4.Lerp(_prevEffectorPlane, currentPlane, Time.deltaTime * 5f); var time = LocalTime; float smoothTime; if (Time.deltaTime > 0.2f) { // 首帧或错误时间步时,直接采样当前值,避免突变动画 _prevEffectorPlane = currentPlane; _prevTime = time; _prevEffectorPlane = plane; smoothTime = time; } else { _prevEffectorPlane = plane; smoothTime = Mathf.Lerp(_prevTime, time, Time.deltaTime * 5f); _prevTime = smoothTime; } var emission1 = ColorToHsvm(_emissionColor1); var emission2 = ColorToHsvm(_emissionColor2); var voxelParams = new Vector2(_density, _scale); var animParams = new Vector3(_stretch, _fallDistance, _fluctuation); foreach (var renderer in renderers) { if (renderer == null || !renderer.enabled) continue; renderer.GetPropertyBlock(_sheet); _sheet.SetFloat(ShaderIDs.VoxelDensity, _density); _sheet.SetFloat(ShaderIDs.VoxelScale, _scale); _sheet.SetFloat(ShaderIDs.Stretch, _stretch); _sheet.SetFloat(ShaderIDs.FallDistance, _fallDistance); _sheet.SetFloat(ShaderIDs.Fluctuation, _fluctuation); _sheet.SetVector(ShaderIDs.EmissionHsvm1, emission1); _sheet.SetVector(ShaderIDs.EmissionHsvm2, emission2); _sheet.SetColor(ShaderIDs.TransitionColor, _transitionColor); _sheet.SetColor(ShaderIDs.LineColor, _lineColor); _sheet.SetVector(ShaderIDs.EffectorPlane, plane); _sheet.SetVector(ShaderIDs.VoxelParams, voxelParams); _sheet.SetVector(ShaderIDs.AnimParams, animParams); _sheet.SetVector(ShaderIDs.PrevEffectorPlane, _prevEffectorPlane); _sheet.SetFloat(ShaderIDs.LocalTime, smoothTime); renderer.SetPropertyBlock(_sheet); } _prevEffectorPlane = plane; } Mesh _gridMesh; void OnDestroy() { if (_gridMesh != null) { if (Application.isPlaying) Destroy(_gridMesh); else DestroyImmediate(_gridMesh); } } #endregion #region Renderers management Renderer[] GetRendererList() { if (_renderers != null && _renderers.Length > 0) return _renderers; var cached = GetComponentsInChildren(true); _renderers = cached; return _renderers; } void RefreshRenderers() { var renderers = GetComponentsInChildren(true); if (renderers.Length > 0) _renderers = renderers; } #endregion #region ITimeControl implementation public void OnControlTimeStart() { } public void OnControlTimeStop() => _controlTime = -1f; public void SetTime(double time) => _controlTime = (float)time; #endregion #region IPropertyPreview implementation public void GatherProperties(PlayableDirector director, IPropertyCollector driver) { // No animated properties in this component. } #endregion #region Editor gizmo implementation #if UNITY_EDITOR void OnDrawGizmos() { if (_gridMesh == null) InitGridMesh(); Gizmos.matrix = transform.localToWorldMatrix; Gizmos.color = new Color(1f, 1f, 0f, 0.5f); Gizmos.DrawWireMesh(_gridMesh, Vector3.zero); Gizmos.DrawWireMesh(_gridMesh, Vector3.forward); Gizmos.color = new Color(1f, 0f, 0f, 0.5f); Gizmos.DrawWireCube(Vector3.forward * 0.5f, new Vector3(0.02f, 0.02f, 1f)); } void InitGridMesh() { const float ext = 0.5f; const int columns = 10; var vertices = new List(columns * 4 + 4); var indices = new List(columns * 4 + 4); for (var i = 0; i <= columns; i++) { var x = ext * (2f * i / columns - 1f); indices.Add(vertices.Count); vertices.Add(new Vector3(x, -ext, 0f)); indices.Add(vertices.Count); vertices.Add(new Vector3(x, +ext, 0f)); indices.Add(vertices.Count); vertices.Add(new Vector3(-ext, x, 0f)); indices.Add(vertices.Count); vertices.Add(new Vector3(+ext, x, 0f)); } _gridMesh = new Mesh { hideFlags = HideFlags.DontSave }; _gridMesh.SetVertices(vertices); _gridMesh.SetNormals(vertices); _gridMesh.SetIndices(indices.ToArray(), MeshTopology.Lines, 0); _gridMesh.UploadMeshData(true); } #endif #endregion }