You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
8.2 KiB
C#

using FluffyGroomingTool;
using UnityEngine;
using UnityEngine.Rendering;
public class MeshBaker {
public readonly SkinnedMeshRenderer skinnedMeshRenderer;
private const int FullMeshBufferStride = sizeof(float) * 16;
public const int MeshBufferStride = sizeof(float) * 10;
public ComputeBuffer bakedMesh;
private ComputeShader computeShader;
public Mesh sourceMesh;
private readonly Matrix4x4 indentity = Matrix4x4.identity;
private readonly int stride;
internal GraphicsBuffer indexBuffer;
private readonly ComputeBuffer rayCastHitsBuffer;
private readonly Transform sourceTransform;
public MeshBaker(GameObject source, ComputeShader computeShader, bool autoFixIndexFormat = false) {
sourceTransform = source.transform;
stride = MeshBufferStride;
var mf = source.GetComponent<MeshFilter>();
skinnedMeshRenderer = source.GetComponent<SkinnedMeshRenderer>();
sourceMesh = mf ? mf.sharedMesh : skinnedMeshRenderer.sharedMesh;
if (autoFixIndexFormat && sourceMesh != null && sourceMesh.indexFormat != IndexFormat.UInt32) sourceMesh.fixRwAndIndexFormat();
setupBaker(computeShader);
getIndexBuffer();
rayCastHitsBuffer = new ComputeBuffer(10, stride, ComputeBufferType.Append);
computeShader.SetBuffer(1, "bakedMesh", bakedMesh);
computeShader.SetBuffer(1, "meshIndexBuffer", indexBuffer);
computeShader.SetBuffer(1, "rayCastHits", rayCastHitsBuffer);
}
internal GraphicsBuffer getIndexBuffer() {
if (indexBuffer == null) {
indexBuffer = sourceMesh.GetIndexBuffer();
}
return indexBuffer;
}
/**
* Used for the hair cards data. This has Colors and UV in addition.
*/
public MeshBaker(Mesh source, ComputeShader computeShader, int meshStride = FullMeshBufferStride) {
sourceMesh = source;
stride = meshStride;
setupBaker(computeShader);
}
private void setupBaker(ComputeShader computeShader) {
if (sourceMesh == null) return;
setIndexFormat32(sourceMesh);
if (sourceMesh.normals == null) {
Debug.Log("Fluffy: Source mesh was missing normal. Recalculating and adding Normals.");
sourceMesh.RecalculateNormals();
}
if (sourceMesh.uv == null) {
Debug.Log("Fluffy: Source mesh was missing UVs. Recalculating and adding UVs.");
sourceMesh.uv = new Vector2[sourceMesh.vertexCount].also((it) => {
for (int i = 0; i < sourceMesh.vertexCount; i++) it[i] = zero;
});
}
if (sourceMesh.tangents == null) {
Debug.Log("Fluffy: Source mesh was missing normal. Recalculating and adding Tangents.");
sourceMesh.RecalculateTangents();
}
if (skinnedMeshRenderer) {
skinnedMeshRenderer.vertexBufferTarget |= GraphicsBuffer.Target.Raw;
} else {
sourceMesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw;
}
sourceMesh.indexBufferTarget |= GraphicsBuffer.Target.Raw;
this.computeShader = computeShader;
bakedMesh = new ComputeBuffer(sourceMesh.vertexCount, stride, ComputeBufferType.Default);
computeShader.SetBuffer(0, "bakedMesh", bakedMesh);
bakeMesh(false);
}
private static void setIndexFormat32(Mesh sourceMesh) {
if (sourceMesh.indexFormat != IndexFormat.UInt32) {
Debug.Log("Fluffy automatically changed the Mesh index format to 32. This may force only one Material on your Mesh. To avoid " +
"this from happening. Please change the import settings of your asset to index format 32.");
var triangles = sourceMesh.triangles;
sourceMesh.indexFormat = IndexFormat.UInt32;
sourceMesh.triangles = triangles;
}
}
public bool bakeSkinnedMesh(bool isCreateMeshPass) {
bool didSucceed = false;
if (skinnedMeshRenderer || isCreateMeshPass) {
didSucceed = bakeMesh(isCreateMeshPass);
}
return didSucceed;
}
private bool bakeMesh(bool isCreateMeshPass) {
bool didSucceed = false;
var useSkinnedMeshBuffer = skinnedMeshRenderer && !isCreateMeshPass;
var sourceMeshData = useSkinnedMeshBuffer ? skinnedMeshRenderer.GetVertexBuffer() : sourceMesh.GetVertexBuffer(0);
if (sourceMeshData != null) {
didSucceed = true;
computeShader.SetBuffer(0, "sourceMeshData", sourceMeshData);
computeShader.SetInt("vertexBufferStride", sourceMesh.GetVertexBufferStride(0));
if (skinnedMeshRenderer && !isCreateMeshPass) {
var transformMatrix = skinnedMeshRenderer.rootBone
? getSourceMatrix() * createTransAndRotationMatrix(skinnedMeshRenderer.rootBone.localToWorldMatrix, Vector3.one)
: Matrix4x4.identity;
computeShader.SetMatrix("transformMatrix", transformMatrix);
computeShader.SetMatrix("rotationMatrix", Matrix4x4.Rotate(transformMatrix.rotation));
} else {
computeShader.DisableKeyword(ShaderID.IS_SKINNED_MESH);
computeShader.SetMatrix("transformMatrix", indentity);
computeShader.SetMatrix("rotationMatrix", indentity);
}
computeShader.SetInt("verticesCount", sourceMesh.vertexCount);
computeShader.Dispatch(0, sourceMesh.vertexCount.toCsGroups(), 1, 1);
}
sourceMeshData?.Release();
return didSucceed;
}
private Matrix4x4 getSourceMatrix() { return sourceTransform.worldToLocalMatrix; }
private static Matrix4x4 createTransAndRotationMatrix(Matrix4x4 inMatrix, Vector3 transformLossyScale) {
var trans = new Vector3(inMatrix[0, 3], inMatrix[1, 3], inMatrix[2, 3]);
Quaternion rotation = Quaternion.LookRotation(
inMatrix.GetColumn(2),
inMatrix.GetColumn(1)
);
return Matrix4x4.TRS(trans, rotation, transformLossyScale);
}
public void dispose() {
bakedMesh?.Dispose();
indexBuffer?.Dispose();
rayCastHitsBuffer?.Dispose();
}
public bool isSkinnedMesh() { return skinnedMeshRenderer != null && skinnedMeshRenderer.rootBone; }
private MeshProperties currentHit;
public MeshProperties? rayCast(Ray worldRay) {
MeshProperties[] meshProperties = new MeshProperties[6];
computeShader.SetMatrix("transformMatrix", sourceTransform.localToWorldMatrix);
computeShader.SetMatrix("rotationMatrix", Matrix4x4.Rotate(sourceTransform.rotation));
computeShader.SetVector("origin", worldRay.origin);
computeShader.SetVector("direction", worldRay.direction);
rayCastHitsBuffer.SetCounterValue(0);
rayCastHitsBuffer.SetData(meshProperties);
computeShader.Dispatch(1, ((int) (indexBuffer.count / 3f)).toCsGroupsEditor(), 1, 1);
rayCastHitsBuffer.GetData(meshProperties);
return getClosestHit(worldRay.origin, meshProperties);
}
private Vector3 zero = Vector3.zero;
private MeshProperties? getClosestHit(Vector3 worldRayOrigin, MeshProperties[] meshProperties) {
MeshProperties returnValue = meshProperties[0];
if (returnValue.sourceVertex == zero) return null;
var minDistance = Vector3.Distance(worldRayOrigin, returnValue.sourceVertex);
foreach (var property in meshProperties) {
if (property.sourceVertex != zero) {
var distance = Vector3.Distance(worldRayOrigin, property.sourceVertex);
if (distance < minDistance) {
minDistance = distance;
returnValue = property;
}
}
}
return returnValue;
}
public Vector3 getObjectPosition() { return isSkinnedMesh() ? skinnedMeshRenderer.rootBone.position : sourceTransform.position; }
}
public struct MeshProperties {
public Vector3 sourceVertex;
public Vector3 sourceNormal;
public Vector4 sourceTangent;
public static MeshProperties zero() {
return new MeshProperties() {
sourceVertex = Vector3.zero,
sourceNormal = Vector3.zero,
sourceTangent = Vector3.zero
};
}
}