using System; using System.Collections.Generic; using System.Threading; using UnityEngine; using UnityEngine.Rendering; namespace AmplifyMotion { internal class SkinnedState : MotionState { private SkinnedMeshRenderer m_renderer; private int m_boneCount; private Transform[] m_boneTransforms; private Matrix4x4[] m_bones; private int m_weightCount; private int[] m_boneIndices; private float[] m_boneWeights; private int m_vertexCount; private Vector4[] m_baseVertices; private Vector3[] m_prevVertices; private Vector3[] m_currVertices; private int m_gpuBoneTexWidth; private int m_gpuBoneTexHeight; private int m_gpuVertexTexWidth; private int m_gpuVertexTexHeight; private Material m_gpuSkinDeformMat; private Color[] m_gpuBoneData; private Texture2D m_gpuBones; private Texture2D m_gpuBoneIndices; private Texture2D[] m_gpuBaseVertices; private RenderTexture m_gpuPrevVertices; private RenderTexture m_gpuCurrVertices; private Mesh m_clonedMesh; private Matrix4x4 m_worldToLocalMatrix; private Matrix4x4 m_prevLocalToWorld; private Matrix4x4 m_currLocalToWorld; private MaterialDesc[] m_sharedMaterials; private ManualResetEvent m_asyncUpdateSignal; private bool m_asyncUpdateTriggered; private bool m_starting; private bool m_wasVisible; private bool m_useFallback; private bool m_useGPU; public SkinnedState(AmplifyMotionCamera owner, AmplifyMotionObjectBase obj) : base(owner, obj) { m_renderer = m_obj.GetComponent(); } internal override void Initialize() { Transform[] bones = m_renderer.bones; m_useFallback = bones == null || bones.Length == 0; if (!m_useFallback) { m_useGPU = m_owner.Instance.CanUseGPU; } base.Initialize(); m_vertexCount = m_renderer.sharedMesh.vertexCount; m_prevVertices = new Vector3[m_vertexCount]; m_currVertices = new Vector3[m_vertexCount]; m_clonedMesh = new Mesh(); if (!m_useFallback) { if (m_renderer.quality == SkinQuality.Auto) { m_weightCount = (int)QualitySettings.blendWeights; } else { m_weightCount = (int)m_renderer.quality; } m_boneTransforms = m_renderer.bones; m_boneCount = m_renderer.bones.Length; m_bones = new Matrix4x4[m_boneCount]; Vector4[] baseVertices = new Vector4[m_vertexCount * m_weightCount]; int[] boneIndices = new int[m_vertexCount * m_weightCount]; float[] boneWeights = ((m_weightCount <= 1) ? null : new float[m_vertexCount * m_weightCount]); if (m_weightCount == 1) { InitializeBone1(baseVertices, boneIndices); } else if (m_weightCount == 2) { InitializeBone2(baseVertices, boneIndices, boneWeights); } else { InitializeBone4(baseVertices, boneIndices, boneWeights); } m_baseVertices = baseVertices; m_boneIndices = boneIndices; m_boneWeights = boneWeights; Mesh sharedMesh = m_renderer.sharedMesh; m_clonedMesh.vertices = sharedMesh.vertices; m_clonedMesh.normals = sharedMesh.vertices; m_clonedMesh.uv = sharedMesh.uv; m_clonedMesh.subMeshCount = sharedMesh.subMeshCount; for (int i = 0; i < sharedMesh.subMeshCount; i++) { m_clonedMesh.SetTriangles(sharedMesh.GetTriangles(i), i); } if (m_useGPU) { if (!InitializeGPUSkinDeform()) { Debug.LogWarning("[AmplifyMotion] Failed initializing GPU skin deform for object " + m_obj.name + ". Falling back to CPU path."); m_useGPU = false; } else { m_boneIndices = null; m_boneWeights = null; m_baseVertices = null; m_prevVertices = null; m_currVertices = null; } } if (!m_useGPU) { m_asyncUpdateSignal = new ManualResetEvent(false); m_asyncUpdateTriggered = false; } } m_sharedMaterials = ProcessSharedMaterials(m_renderer.sharedMaterials); m_wasVisible = false; } internal override void Shutdown() { if (!m_useFallback && !m_useGPU) { WaitForAsyncUpdate(); } if (m_useGPU) { ShutdownGPUSkinDeform(); } if (m_clonedMesh != null) { UnityEngine.Object.Destroy(m_clonedMesh); m_clonedMesh = null; } m_boneTransforms = null; m_bones = null; m_boneIndices = null; m_boneWeights = null; m_baseVertices = null; m_prevVertices = null; m_currVertices = null; m_sharedMaterials = null; } private bool InitializeGPUSkinDeform() { bool result = true; try { m_gpuBoneTexWidth = Mathf.NextPowerOfTwo(m_boneCount); m_gpuBoneTexHeight = 4; m_gpuVertexTexWidth = Mathf.NextPowerOfTwo(Mathf.CeilToInt(Mathf.Sqrt(m_vertexCount))); m_gpuVertexTexHeight = Mathf.NextPowerOfTwo(m_vertexCount / m_gpuVertexTexWidth); m_gpuSkinDeformMat = new Material(Shader.Find("Hidden/Amplify Motion/GPUSkinDeform")) { hideFlags = HideFlags.DontSave }; m_gpuBones = new Texture2D(m_gpuBoneTexWidth, m_gpuBoneTexHeight, TextureFormat.RGBAFloat, false, true); m_gpuBones.filterMode = FilterMode.Point; m_gpuBoneData = new Color[m_gpuBoneTexWidth * m_gpuBoneTexHeight]; UpdateBonesGPU(); TextureFormat textureFormat = TextureFormat.RHalf; textureFormat = ((m_weightCount != 2) ? textureFormat : TextureFormat.RGHalf); textureFormat = ((m_weightCount != 4) ? textureFormat : TextureFormat.RGBAHalf); m_gpuBoneIndices = new Texture2D(m_gpuVertexTexWidth, m_gpuVertexTexHeight, textureFormat, false, true); m_gpuBoneIndices.filterMode = FilterMode.Point; m_gpuBoneIndices.wrapMode = TextureWrapMode.Clamp; BoneWeight[] boneWeights = m_renderer.sharedMesh.boneWeights; Color[] array = new Color[m_gpuVertexTexWidth * m_gpuVertexTexHeight]; for (int i = 0; i < m_vertexCount; i++) { int num = i % m_gpuVertexTexWidth; int num2 = i / m_gpuVertexTexWidth; int num3 = num2 * m_gpuVertexTexWidth + num; BoneWeight boneWeight = boneWeights[i]; array[num3] = new Vector4(boneWeight.boneIndex0, boneWeight.boneIndex1, boneWeight.boneIndex2, boneWeight.boneIndex3); } m_gpuBoneIndices.SetPixels(array); m_gpuBoneIndices.Apply(); m_gpuBaseVertices = new Texture2D[m_weightCount]; for (int j = 0; j < m_weightCount; j++) { m_gpuBaseVertices[j] = new Texture2D(m_gpuVertexTexWidth, m_gpuVertexTexHeight, TextureFormat.RGBAFloat, false, true); m_gpuBaseVertices[j].filterMode = FilterMode.Point; } List list = new List(m_weightCount); for (int k = 0; k < m_weightCount; k++) { list.Add(new Color[m_gpuVertexTexWidth * m_gpuVertexTexHeight]); } for (int l = 0; l < m_vertexCount; l++) { int num4 = l % m_gpuVertexTexWidth; int num5 = l / m_gpuVertexTexWidth; int num6 = num5 * m_gpuVertexTexWidth + num4; for (int m = 0; m < m_weightCount; m++) { list[m][num6] = m_baseVertices[l * m_weightCount + m]; } } for (int n = 0; n < m_weightCount; n++) { m_gpuBaseVertices[n].SetPixels(list[n]); m_gpuBaseVertices[n].Apply(); } m_gpuPrevVertices = new RenderTexture(m_gpuVertexTexWidth, m_gpuVertexTexHeight, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear); m_gpuPrevVertices.filterMode = FilterMode.Point; m_gpuPrevVertices.wrapMode = TextureWrapMode.Clamp; m_gpuPrevVertices.Create(); m_gpuCurrVertices = new RenderTexture(m_gpuVertexTexWidth, m_gpuVertexTexHeight, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear); m_gpuCurrVertices.filterMode = FilterMode.Point; m_gpuCurrVertices.wrapMode = TextureWrapMode.Clamp; m_gpuCurrVertices.Create(); m_gpuSkinDeformMat.SetTexture("_AM_BONE_TEX", m_gpuBones); m_gpuSkinDeformMat.SetTexture("_AM_BONE_INDEX_TEX", m_gpuBoneIndices); for (int num7 = 0; num7 < m_weightCount; num7++) { m_gpuSkinDeformMat.SetTexture("_AM_BASE_VERTEX" + num7 + "_TEX", m_gpuBaseVertices[num7]); } Vector4 vector = new Vector4(1f / (float)m_gpuBoneTexWidth, 1f / (float)m_gpuBoneTexHeight, m_gpuBoneTexWidth, m_gpuBoneTexHeight); Vector4 vector2 = new Vector4(1f / (float)m_gpuVertexTexWidth, 1f / (float)m_gpuVertexTexHeight, m_gpuVertexTexWidth, m_gpuVertexTexHeight); m_gpuSkinDeformMat.SetVector("_AM_BONE_TEXEL_SIZE", vector); m_gpuSkinDeformMat.SetVector("_AM_BONE_TEXEL_HALFSIZE", vector * 0.5f); m_gpuSkinDeformMat.SetVector("_AM_VERTEX_TEXEL_SIZE", vector2); m_gpuSkinDeformMat.SetVector("_AM_VERTEX_TEXEL_HALFSIZE", vector2 * 0.5f); Vector2[] array2 = new Vector2[m_vertexCount]; for (int num8 = 0; num8 < m_vertexCount; num8++) { int num9 = num8 % m_gpuVertexTexWidth; int num10 = num8 / m_gpuVertexTexWidth; float x = (float)num9 / (float)m_gpuVertexTexWidth + vector2.x * 0.5f; float y = (float)num10 / (float)m_gpuVertexTexHeight + vector2.y * 0.5f; array2[num8] = new Vector2(x, y); } m_clonedMesh.uv2 = array2; } catch (Exception) { result = false; } return result; } private void ShutdownGPUSkinDeform() { if (m_gpuSkinDeformMat != null) { UnityEngine.Object.DestroyImmediate(m_gpuSkinDeformMat); m_gpuSkinDeformMat = null; } m_gpuBoneData = null; if (m_gpuBones != null) { UnityEngine.Object.DestroyImmediate(m_gpuBones); m_gpuBones = null; } if (m_gpuBoneIndices != null) { UnityEngine.Object.DestroyImmediate(m_gpuBoneIndices); m_gpuBoneIndices = null; } if (m_gpuBaseVertices != null) { for (int i = 0; i < m_gpuBaseVertices.Length; i++) { UnityEngine.Object.DestroyImmediate(m_gpuBaseVertices[i]); } m_gpuBaseVertices = null; } if (m_gpuPrevVertices != null) { RenderTexture.active = null; UnityEngine.Object.DestroyImmediate(m_gpuPrevVertices); m_gpuPrevVertices = null; } if (m_gpuCurrVertices != null) { RenderTexture.active = null; UnityEngine.Object.DestroyImmediate(m_gpuCurrVertices); m_gpuCurrVertices = null; } } private void UpdateBonesGPU() { for (int i = 0; i < m_boneCount; i++) { for (int j = 0; j < m_gpuBoneTexHeight; j++) { m_gpuBoneData[j * m_gpuBoneTexWidth + i] = m_bones[i].GetRow(j); } } m_gpuBones.SetPixels(m_gpuBoneData); m_gpuBones.Apply(); } private void UpdateVerticesGPU(CommandBuffer updateCB, bool starting) { if (!starting && m_wasVisible) { AmplifyMotionEffectBase.DiscardContents(m_gpuPrevVertices); updateCB.Blit(new RenderTargetIdentifier(m_gpuCurrVertices), m_gpuPrevVertices); } updateCB.SetGlobalMatrix("_AM_WORLD_TO_LOCAL_MATRIX", m_worldToLocalMatrix); AmplifyMotionEffectBase.DiscardContents(m_gpuCurrVertices); RenderTexture tex = null; updateCB.Blit(new RenderTargetIdentifier(tex), m_gpuCurrVertices, m_gpuSkinDeformMat, Mathf.Min(m_weightCount - 1, 2)); if (starting || !m_wasVisible) { AmplifyMotionEffectBase.DiscardContents(m_gpuPrevVertices); updateCB.Blit(new RenderTargetIdentifier(m_gpuCurrVertices), m_gpuPrevVertices); } } private void UpdateBones() { for (int i = 0; i < m_boneCount; i++) { m_bones[i] = ((!(m_boneTransforms[i] != null)) ? Matrix4x4.identity : m_boneTransforms[i].localToWorldMatrix); } m_worldToLocalMatrix = m_transform.worldToLocalMatrix; if (m_useGPU) { UpdateBonesGPU(); } } private void UpdateVerticesFallback(bool starting) { if (!starting && m_wasVisible) { Array.Copy(m_currVertices, m_prevVertices, m_vertexCount); } m_renderer.BakeMesh(m_clonedMesh); Array.Copy(m_clonedMesh.vertices, m_currVertices, m_vertexCount); if (starting || !m_wasVisible) { Array.Copy(m_currVertices, m_prevVertices, m_vertexCount); } } private void AsyncUpdateVertices(bool starting) { if (!starting && m_wasVisible) { Array.Copy(m_currVertices, m_prevVertices, m_vertexCount); } for (int i = 0; i < m_boneCount; i++) { m_bones[i] = m_worldToLocalMatrix * m_bones[i]; } if (m_weightCount == 1) { UpdateVerticesBone1(); } else if (m_weightCount == 2) { UpdateVerticesBone2(); } else { UpdateVerticesBone4(); } if (starting || !m_wasVisible) { Array.Copy(m_currVertices, m_prevVertices, m_vertexCount); } } private void InitializeBone1(Vector4[] baseVertices, int[] boneIndices) { Vector3[] vertices = m_renderer.sharedMesh.vertices; Matrix4x4[] bindposes = m_renderer.sharedMesh.bindposes; BoneWeight[] boneWeights = m_renderer.sharedMesh.boneWeights; for (int i = 0; i < m_vertexCount; i++) { int num = i * m_weightCount; Vector3 vector = bindposes[boneIndices[num] = boneWeights[i].boneIndex0].MultiplyPoint3x4(vertices[i]); baseVertices[num] = new Vector4(vector.x, vector.y, vector.z, 1f); } } private void InitializeBone2(Vector4[] baseVertices, int[] boneIndices, float[] boneWeights) { Vector3[] vertices = m_renderer.sharedMesh.vertices; Matrix4x4[] bindposes = m_renderer.sharedMesh.bindposes; BoneWeight[] boneWeights2 = m_renderer.sharedMesh.boneWeights; for (int i = 0; i < m_vertexCount; i++) { int num = i * m_weightCount; int num2 = num + 1; BoneWeight boneWeight = boneWeights2[i]; int num3 = (boneIndices[num] = boneWeight.boneIndex0); int num4 = (boneIndices[num2] = boneWeight.boneIndex1); float weight = boneWeight.weight0; float weight2 = boneWeight.weight1; float num5 = 1f / (weight + weight2); weight = (boneWeights[num] = weight * num5); weight2 = (boneWeights[num2] = weight2 * num5); Vector3 vector = weight * bindposes[num3].MultiplyPoint3x4(vertices[i]); Vector3 vector2 = weight2 * bindposes[num4].MultiplyPoint3x4(vertices[i]); baseVertices[num] = new Vector4(vector.x, vector.y, vector.z, weight); baseVertices[num2] = new Vector4(vector2.x, vector2.y, vector2.z, weight2); } } private void InitializeBone4(Vector4[] baseVertices, int[] boneIndices, float[] boneWeights) { Vector3[] vertices = m_renderer.sharedMesh.vertices; Matrix4x4[] bindposes = m_renderer.sharedMesh.bindposes; BoneWeight[] boneWeights2 = m_renderer.sharedMesh.boneWeights; for (int i = 0; i < m_vertexCount; i++) { int num = i * m_weightCount; int num2 = num + 1; int num3 = num + 2; int num4 = num + 3; BoneWeight boneWeight = boneWeights2[i]; int num5 = (boneIndices[num] = boneWeight.boneIndex0); int num6 = (boneIndices[num2] = boneWeight.boneIndex1); int num7 = (boneIndices[num3] = boneWeight.boneIndex2); int num8 = (boneIndices[num4] = boneWeight.boneIndex3); float num9 = (boneWeights[num] = boneWeight.weight0); float num10 = (boneWeights[num2] = boneWeight.weight1); float num11 = (boneWeights[num3] = boneWeight.weight2); float num12 = (boneWeights[num4] = boneWeight.weight3); Vector3 vector = num9 * bindposes[num5].MultiplyPoint3x4(vertices[i]); Vector3 vector2 = num10 * bindposes[num6].MultiplyPoint3x4(vertices[i]); Vector3 vector3 = num11 * bindposes[num7].MultiplyPoint3x4(vertices[i]); Vector3 vector4 = num12 * bindposes[num8].MultiplyPoint3x4(vertices[i]); baseVertices[num] = new Vector4(vector.x, vector.y, vector.z, num9); baseVertices[num2] = new Vector4(vector2.x, vector2.y, vector2.z, num10); baseVertices[num3] = new Vector4(vector3.x, vector3.y, vector3.z, num11); baseVertices[num4] = new Vector4(vector4.x, vector4.y, vector4.z, num12); } } private void UpdateVerticesBone1() { for (int i = 0; i < m_vertexCount; i++) { MotionState.MulPoint3x4_XYZ(ref m_currVertices[i], ref m_bones[m_boneIndices[i]], m_baseVertices[i]); } } private void UpdateVerticesBone2() { Vector3 result = Vector3.zero; for (int i = 0; i < m_vertexCount; i++) { int num = i * 2; int num2 = num + 1; int num3 = m_boneIndices[num]; int num4 = m_boneIndices[num2]; float num5 = m_boneWeights[num2]; MotionState.MulPoint3x4_XYZW(ref result, ref m_bones[num3], m_baseVertices[num]); if (num5 != 0f) { MotionState.MulAddPoint3x4_XYZW(ref result, ref m_bones[num4], m_baseVertices[num2]); } m_currVertices[i] = result; } } private void UpdateVerticesBone4() { Vector3 result = Vector3.zero; for (int i = 0; i < m_vertexCount; i++) { int num = i * 4; int num2 = num + 1; int num3 = num + 2; int num4 = num + 3; int num5 = m_boneIndices[num]; int num6 = m_boneIndices[num2]; int num7 = m_boneIndices[num3]; int num8 = m_boneIndices[num4]; float num9 = m_boneWeights[num2]; float num10 = m_boneWeights[num3]; float num11 = m_boneWeights[num4]; MotionState.MulPoint3x4_XYZW(ref result, ref m_bones[num5], m_baseVertices[num]); if (num9 != 0f) { MotionState.MulAddPoint3x4_XYZW(ref result, ref m_bones[num6], m_baseVertices[num2]); } if (num10 != 0f) { MotionState.MulAddPoint3x4_XYZW(ref result, ref m_bones[num7], m_baseVertices[num3]); } if (num11 != 0f) { MotionState.MulAddPoint3x4_XYZW(ref result, ref m_bones[num8], m_baseVertices[num4]); } m_currVertices[i] = result; } } internal override void AsyncUpdate() { try { AsyncUpdateVertices(m_starting); } catch (Exception ex) { Debug.LogError("[AmplifyMotion] Failed on SkinnedMeshRenderer data. Please contact support.\n" + ex.Message); } finally { m_asyncUpdateSignal.Set(); } } internal override void UpdateTransform(CommandBuffer updateCB, bool starting) { if (!m_initialized) { Initialize(); return; } if (!starting && m_wasVisible) { m_prevLocalToWorld = m_currLocalToWorld; } bool isVisible = m_renderer.isVisible; if (!m_error && (isVisible || starting)) { UpdateBones(); m_starting = !m_wasVisible || starting; if (!m_useFallback) { if (!m_useGPU) { m_asyncUpdateSignal.Reset(); m_asyncUpdateTriggered = true; m_owner.Instance.WorkerPool.EnqueueAsyncUpdate(this); } else { UpdateVerticesGPU(updateCB, m_starting); } } else { UpdateVerticesFallback(m_starting); } } if (!m_useFallback) { m_currLocalToWorld = m_transform.localToWorldMatrix; } else { m_currLocalToWorld = Matrix4x4.TRS(m_transform.position, m_transform.rotation, Vector3.one); } if (starting || !m_wasVisible) { m_prevLocalToWorld = m_currLocalToWorld; } m_wasVisible = isVisible; } private void WaitForAsyncUpdate() { if (m_asyncUpdateTriggered) { if (!m_asyncUpdateSignal.WaitOne(100)) { Debug.LogWarning("[AmplifyMotion] Aborted abnormally long Async Skin deform operation. Not a critical error but might indicate a problem. Please contact support."); } else { m_asyncUpdateTriggered = false; } } } internal override void RenderVectors(Camera camera, CommandBuffer renderCB, float scale, Quality quality) { if (!m_initialized || m_error || !m_renderer.isVisible) { return; } if (!m_useFallback && !m_useGPU) { WaitForAsyncUpdate(); } if (!m_useGPU) { if (!m_useFallback) { m_clonedMesh.vertices = m_currVertices; } m_clonedMesh.normals = m_prevVertices; } bool flag = ((int)m_owner.Instance.CullingMask & (1 << m_obj.gameObject.layer)) != 0; int num = ((!flag) ? 255 : m_owner.Instance.GenerateObjectId(m_obj.gameObject)); Matrix4x4 value = ((!m_obj.FixedStep) ? (m_owner.PrevViewProjMatrixRT * m_prevLocalToWorld) : (m_owner.PrevViewProjMatrixRT * m_currLocalToWorld)); renderCB.SetGlobalMatrix("_AM_MATRIX_PREV_MVP", value); renderCB.SetGlobalFloat("_AM_OBJECT_ID", (float)num * 0.003921569f); renderCB.SetGlobalFloat("_AM_MOTION_SCALE", (!flag) ? 0f : scale); if (m_useGPU) { Vector4 vector = new Vector4(1f / (float)m_gpuVertexTexWidth, 1f / (float)m_gpuVertexTexHeight, m_gpuVertexTexWidth, m_gpuVertexTexHeight); renderCB.SetGlobalVector("_AM_VERTEX_TEXEL_SIZE", vector); renderCB.SetGlobalVector("_AM_VERTEX_TEXEL_HALFSIZE", vector * 0.5f); renderCB.SetGlobalTexture("_AM_PREV_VERTEX_TEX", m_gpuPrevVertices); renderCB.SetGlobalTexture("_AM_CURR_VERTEX_TEX", m_gpuCurrVertices); } int num2 = (m_useGPU ? 4 : 0); int num3 = ((quality != Quality.Mobile) ? 2 : 0); int num4 = num2 + num3; for (int i = 0; i < m_sharedMaterials.Length; i++) { MaterialDesc materialDesc = m_sharedMaterials[i]; int shaderPass = num4 + (materialDesc.coverage ? 1 : 0); materialDesc.propertyBlock.Clear(); if (materialDesc.coverage) { materialDesc.propertyBlock.SetTexture("_MainTex", materialDesc.material.mainTexture); if (materialDesc.cutoff) { materialDesc.propertyBlock.SetFloat("_Cutoff", materialDesc.material.GetFloat("_Cutoff")); } } renderCB.DrawMesh(m_clonedMesh, m_currLocalToWorld, m_owner.Instance.SkinnedVectorsMaterial, i, shaderPass, materialDesc.propertyBlock); } } } }