您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
333 行
8.9 KiB
333 行
8.9 KiB
using System;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
using Unity.DemoTeam.Attributes;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace Unity.DemoTeam.DigitalHuman
|
|
{
|
|
[CreateAssetMenu(menuName = "Digital Human/Skin Deformation Clip")]
|
|
public class SkinDeformationClip : ScriptableObject
|
|
{
|
|
public unsafe struct Frame
|
|
{
|
|
public float* deltaPositions;
|
|
public float* deltaNormals;
|
|
public float* fittedWeights;
|
|
public Texture2D albedo;
|
|
}
|
|
|
|
[Serializable]
|
|
public struct Subframe
|
|
{
|
|
public int frameIndexLo;
|
|
public int frameIndexHi;
|
|
public float fractionLo;
|
|
public float fractionHi;
|
|
}
|
|
|
|
[HideInInspector]
|
|
public int frameCount = 0;
|
|
|
|
[HideInInspector]
|
|
public float frameRate = 30.0f;
|
|
|
|
[HideInInspector]
|
|
public int frameVertexCount = 0;
|
|
|
|
[HideInInspector]
|
|
public int frameFittedWeightsCount = 0;
|
|
|
|
[HideInInspector]
|
|
public SkinDeformation[] frames = new SkinDeformation[0];
|
|
|
|
[HideInInspector]
|
|
public bool framesContainAlbedo;
|
|
|
|
[HideInInspector]
|
|
public bool framesContainDeltas;
|
|
|
|
[HideInInspector]
|
|
public bool framesContainFittedWeights;
|
|
|
|
[HideInInspector]
|
|
public NativeFrameStream frameData;
|
|
|
|
[HideInInspector]
|
|
public string frameDataStreamingAssetsPath = null;
|
|
|
|
[HideInInspector]
|
|
private bool frameDataPending = true;
|
|
|
|
[HideInInspector]
|
|
public int subframeCount = 0;
|
|
|
|
[HideInInspector]
|
|
public Subframe[] subframes = new Subframe[0];
|
|
|
|
[HideInInspector]
|
|
public int version = -1;
|
|
|
|
//--- accessors ---
|
|
public double Duration
|
|
{
|
|
get
|
|
{
|
|
return subframeCount / frameRate;
|
|
}
|
|
}
|
|
|
|
public unsafe Frame GetFrame(int frameIndex)
|
|
{
|
|
if (frameDataPending)
|
|
{
|
|
Debug.Log("hotloading frame data");
|
|
LoadFrameData();
|
|
}
|
|
|
|
var floatPtr = (float*)frameData.ReadFrame(frameIndex);
|
|
{
|
|
Frame frame;
|
|
frame.deltaPositions = floatPtr + 0 * frameVertexCount;
|
|
frame.deltaNormals = floatPtr + 3 * frameVertexCount;
|
|
frame.fittedWeights = floatPtr + 6 * frameVertexCount;
|
|
frame.albedo = frames[frameIndex].albedo;
|
|
return frame;
|
|
}
|
|
}
|
|
|
|
public int GetFrameSizeBytes()
|
|
{
|
|
return (6 * frameVertexCount + 1 * frameFittedWeightsCount) * sizeof(float);
|
|
}
|
|
|
|
public void PrepareFrame(int frameIndex)
|
|
{
|
|
frameData.SeekFrame(frameIndex);
|
|
}
|
|
|
|
//--- import settings begin ---
|
|
public enum TransferMode
|
|
{
|
|
PassThrough,
|
|
PassThroughWithFirstFrameDelta,
|
|
}
|
|
|
|
public enum InputType
|
|
{
|
|
ExternalObj,
|
|
ProjectAssets,
|
|
}
|
|
|
|
[Serializable]
|
|
public class ImportSettings
|
|
{
|
|
[Header("Sequence")]
|
|
public InputType readFrom = InputType.ExternalObj;
|
|
[VisibleIf("readFrom", InputType.ExternalObj)] public string externalObjPath;
|
|
[VisibleIf("readFrom", InputType.ExternalObj)] public string externalObjPattern = "*.obj";
|
|
[VisibleIf("readFrom", InputType.ProjectAssets)] public string meshAssetPath;
|
|
[VisibleIf("readFrom", InputType.ProjectAssets)] public string meshAssetPrefix;
|
|
[VisibleIf("readFrom", InputType.ProjectAssets)] public string albedoAssetPath;
|
|
[VisibleIf("readFrom", InputType.ProjectAssets)] public string albedoAssetPrefix;
|
|
|
|
[Space]
|
|
public bool keyframes = false;
|
|
[Tooltip("CSV specifying how the frames are distributed. The first column is ignored. Rows read as follows:\nRow 1: Frame indices\nRow 2: Keyframe indices\nRow 3: Frame progress (0-100) between keys")]
|
|
[EditableIf("keyframes", true)]
|
|
public TextAsset keyframesCSV;
|
|
|
|
[Header("Mesh transform")]
|
|
public Vector3 applyRotation = Vector3.zero;
|
|
public float applyScale = 1.0f;
|
|
|
|
[Header("Mesh processing")]
|
|
[Tooltip("Regions are specified in text files. Each file should contain an array of vertex indices on the form: [i, j, k, ...]")]
|
|
public TextAsset[] denoiseRegions;
|
|
[Range(0.0f, 1.0f)]
|
|
public float denoiseStrength = 0.0f;
|
|
[Tooltip("Regions are specified in text files. Each file should contain an array of vertex indices on the form: [i, j, k, ...]")]
|
|
public TextAsset[] transplantRegions;
|
|
[Range(0.0f, 1.0f)]
|
|
public float transplantStrength = 0.0f;
|
|
public bool solveRegionPreview = false;
|
|
public bool solveWelded = true;
|
|
|
|
[Header("Frame transfer")]
|
|
public Mesh transferTarget;
|
|
public TransferMode transferMode;
|
|
|
|
[Header("Frame fitting")]
|
|
public bool fitToBlendShapes = false;
|
|
[TextArea(1, 20)]
|
|
public string fittedIndices = "";
|
|
public SkinDeformationFittingOptions.Method fittingMethod = SkinDeformationFittingOptions.Method.LinearLeastSquares;
|
|
public SkinDeformationFittingOptions.Param fittingParam = SkinDeformationFittingOptions.Param.DeltaPosition;
|
|
|
|
public ImportSettings Clone()
|
|
{
|
|
var c = this.MemberwiseClone() as ImportSettings;
|
|
c.externalObjPath = c.externalObjPath.Clone() as string;
|
|
c.externalObjPattern = c.externalObjPattern.Clone() as string;
|
|
c.meshAssetPath = c.meshAssetPath.Clone() as string;
|
|
c.meshAssetPrefix = c.meshAssetPrefix.Clone() as string;
|
|
c.albedoAssetPath = c.albedoAssetPath.Clone() as string;
|
|
c.albedoAssetPrefix = c.albedoAssetPrefix.Clone() as string;
|
|
c.denoiseRegions = c.denoiseRegions.Clone() as TextAsset[];
|
|
c.transplantRegions = c.transplantRegions.Clone() as TextAsset[];
|
|
c.fittedIndices = c.fittedIndices.Clone() as string;
|
|
return c;
|
|
}
|
|
}
|
|
|
|
[ReadOnly]
|
|
public ImportSettings lastImport = new ImportSettings();
|
|
public ImportSettings importSettings = new ImportSettings();
|
|
//--- import settings end ---
|
|
|
|
//--- frame data serialization begin ---
|
|
void OnEnable()
|
|
{
|
|
if (frameDataPending)
|
|
{
|
|
LoadFrameData();
|
|
}
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
UnloadFrameData();
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
UnloadFrameData();
|
|
}
|
|
|
|
void LoadFrameData()
|
|
{
|
|
#if UNITY_EDITOR
|
|
string filename = AssetDatabase.GetAssetPath(this) + "_frames.bin";
|
|
#else
|
|
string filename = Application.streamingAssetsPath + frameDataStreamingAssetsPath;
|
|
Debug.Log("LoadFrameData " + filename + ")");
|
|
#endif
|
|
|
|
int frameOffset = 3 * sizeof(Int32);
|
|
int frameSize = GetFrameSizeBytes();
|
|
|
|
frameData.Dispose();
|
|
frameData = new NativeFrameStream(filename, frameOffset, frameCount, frameSize, 2, 16);
|
|
frameDataPending = false;
|
|
|
|
if (!File.Exists(filename))
|
|
{
|
|
Debug.LogError("failed to load frame data (filename = " + filename + ")");
|
|
return;
|
|
}
|
|
}
|
|
|
|
public void UnloadFrameData()
|
|
{
|
|
frameData.Dispose();
|
|
frameDataPending = true;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
public void SaveFrameData()
|
|
{
|
|
string filenameAsset = AssetDatabase.GetAssetPath(this);
|
|
string filenameFrameData = filenameAsset + "_frames.bin";
|
|
|
|
UnloadFrameData();
|
|
|
|
if (File.Exists(filenameFrameData))
|
|
File.Delete(filenameFrameData);
|
|
|
|
using (FileStream stream = File.Create(filenameFrameData))
|
|
{
|
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
|
{
|
|
writer.Write(frameCount);
|
|
writer.Write(frameVertexCount);
|
|
writer.Write(frameFittedWeightsCount);
|
|
|
|
var fltCursor = 0;
|
|
var fltBuffer = new float[6 * frameVertexCount + 1 * frameFittedWeightsCount];
|
|
var dstBuffer = new byte[4 * fltBuffer.Length];
|
|
|
|
for (int i = 0; i != frameCount; i++)
|
|
{
|
|
Debug.Assert(frames[i].deltaPositions.Length == frameVertexCount, "invalid vertex count");
|
|
Debug.Assert(frames[i].fittedWeights.Length == frameFittedWeightsCount, "invalid fitted weights count");
|
|
|
|
fltCursor = 0;
|
|
|
|
// write positions
|
|
for (int j = 0; j != frameVertexCount; j++)
|
|
{
|
|
fltBuffer[fltCursor++] = frames[i].deltaPositions[j].x;
|
|
fltBuffer[fltCursor++] = frames[i].deltaPositions[j].y;
|
|
fltBuffer[fltCursor++] = frames[i].deltaPositions[j].z;
|
|
}
|
|
|
|
// write normals
|
|
for (int j = 0; j != frameVertexCount; j++)
|
|
{
|
|
fltBuffer[fltCursor++] = frames[i].deltaNormals[j].x;
|
|
fltBuffer[fltCursor++] = frames[i].deltaNormals[j].y;
|
|
fltBuffer[fltCursor++] = frames[i].deltaNormals[j].z;
|
|
}
|
|
|
|
// write fitted weights
|
|
for (int j = 0; j != frameFittedWeightsCount; j++)
|
|
{
|
|
fltBuffer[fltCursor++] = frames[i].fittedWeights[j];
|
|
}
|
|
|
|
Buffer.BlockCopy(fltBuffer, 0, dstBuffer, 0, dstBuffer.Length);
|
|
|
|
writer.Write(dstBuffer, 0, dstBuffer.Length);
|
|
writer.Flush();
|
|
}
|
|
|
|
frameDataPending = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
[ContextMenu("Copy To StreamingAssets")]
|
|
public void CopyToStreamingAssets()
|
|
{
|
|
string filenameAsset = AssetDatabase.GetAssetPath(this);
|
|
string filenameFrameData = filenameAsset + "_frames.bin";
|
|
|
|
frameDataStreamingAssetsPath = "/SkinDeformationClip/" + AssetDatabase.AssetPathToGUID(filenameAsset) + "__" + this.name;
|
|
|
|
var copySrc = filenameFrameData;
|
|
var copyDst = Application.streamingAssetsPath + frameDataStreamingAssetsPath;
|
|
|
|
//Debug.Log("filenameAsset: " + filenameAsset);
|
|
//Debug.Log("copySrc: " + copySrc);
|
|
//Debug.Log("copyDst: " + copyDst);
|
|
|
|
var copyDstDir = copyDst.Substring(0, copyDst.LastIndexOf('/'));
|
|
try
|
|
{
|
|
if (File.Exists(copyDst))
|
|
File.Delete(copyDst);
|
|
|
|
Directory.CreateDirectory(copyDstDir);
|
|
File.Copy(copySrc, copyDst);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError(ex.ToString());
|
|
}
|
|
}
|
|
#endif
|
|
//--- frame data serialization end ---
|
|
}
|
|
}
|