Skip to content

Commit

Permalink
feat: forceUnityLayout parameter, to enforce a blend-shape and skin…
Browse files Browse the repository at this point in the history
…ning compatible vertex buffer layout
  • Loading branch information
atteneder committed Jul 12, 2021
1 parent 7b0dd60 commit 9f5161b
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 34 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.1.0] - 2020-07-12
### Added
- `forceUnityLayout` parameter, to enforce a blend-shape and skinning compatible vertex buffer layout

## [3.0.3] - 2020-06-09
### Added
- Support for Lumin / Magic Leap
Expand Down
128 changes: 112 additions & 16 deletions Runtime/Scripts/DracoMeshLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,31 @@ public void Dispose() {
/// <param name="requireTangents">If draco does not contain tangents and this is set to true, tangents and normals are calculated.</param>
/// <param name="weightsAttributeId">Draco attribute ID that contains bone weights (for skinning)</param>
/// <param name="jointsAttributeId">Draco attribute ID that contains bone joint indices (for skinning)</param>
/// <param name="forceUnityLayout">Enforces vertex buffer layout with highest compatibility. Enable this if you want to use blend shapes on the resulting mesh</param>
/// <returns>Unity Mesh or null in case of errors</returns>
public async Task<Mesh> ConvertDracoMeshToUnity(
NativeSlice<byte> encodedData,
bool requireNormals = false,
bool requireTangents = false,
int weightsAttributeId = -1,
int jointsAttributeId = -1
int jointsAttributeId = -1,
bool forceUnityLayout = false
)
{
var encodedDataPtr = GetUnsafeReadOnlyIntPtr(encodedData);
#if DRACO_MESH_DATA
var meshDataArray = Mesh.AllocateWritableMeshData(1);
var mesh = meshDataArray[0];
var result = await ConvertDracoMeshToUnity(mesh, encodedDataPtr, encodedData.Length, requireNormals, requireTangents, weightsAttributeId, jointsAttributeId);
var result = await ConvertDracoMeshToUnity(
mesh,
encodedDataPtr,
encodedData.Length,
requireNormals,
requireTangents,
weightsAttributeId,
jointsAttributeId,
forceUnityLayout
);
if (!result.success) {
meshDataArray.Dispose();
return null;
Expand All @@ -128,7 +139,15 @@ public async Task<Mesh> ConvertDracoMeshToUnity(
}
return unityMesh;
#else
return await ConvertDracoMeshToUnity(encodedDataPtr, encodedData.Length, requireNormals, requireTangents, weightsAttributeId, jointsAttributeId);
return await ConvertDracoMeshToUnity(
encodedDataPtr,
encodedData.Length,
requireNormals,
requireTangents,
weightsAttributeId,
jointsAttributeId,
forceUnityLayout
);
#endif
}

Expand All @@ -140,13 +159,15 @@ public async Task<Mesh> ConvertDracoMeshToUnity(
/// <param name="requireTangents">If draco does not contain tangents and this is set to true, tangents and normals are calculated.</param>
/// <param name="weightsAttributeId">Draco attribute ID that contains bone weights (for skinning)</param>
/// <param name="jointsAttributeId">Draco attribute ID that contains bone joint indices (for skinning)</param>
/// <param name="forceUnityLayout">Enforces vertex buffer layout with highest compatibility. Enable this if you want to use blend shapes on the resulting mesh</param>
/// <returns>Unity Mesh or null in case of errors</returns>
public async Task<Mesh> ConvertDracoMeshToUnity(
byte[] encodedData,
bool requireNormals = false,
bool requireTangents = false,
int weightsAttributeId = -1,
int jointsAttributeId = -1
int jointsAttributeId = -1,
bool forceUnityLayout = false
#if UNITY_EDITOR
,bool sync = false
#endif
Expand All @@ -156,7 +177,15 @@ public async Task<Mesh> ConvertDracoMeshToUnity(
#if DRACO_MESH_DATA
var meshDataArray = Mesh.AllocateWritableMeshData(1);
var mesh = meshDataArray[0];
var result = await ConvertDracoMeshToUnity(mesh, encodedDataPtr, encodedData.Length, requireNormals, requireTangents, weightsAttributeId, jointsAttributeId
var result = await ConvertDracoMeshToUnity(
mesh,
encodedDataPtr,
encodedData.Length,
requireNormals,
requireTangents,
weightsAttributeId,
jointsAttributeId,
forceUnityLayout
#if UNITY_EDITOR
,sync
#endif
Expand All @@ -176,7 +205,14 @@ public async Task<Mesh> ConvertDracoMeshToUnity(
}
return unityMesh;
#else
var result = await ConvertDracoMeshToUnity(encodedDataPtr, encodedData.Length, requireNormals, requireTangents, weightsAttributeId, jointsAttributeId
var result = await ConvertDracoMeshToUnity(
encodedDataPtr,
encodedData.Length,
requireNormals,
requireTangents,
weightsAttributeId,
jointsAttributeId,
forceUnityLayout
#if UNITY_EDITOR
,sync
#endif
Expand All @@ -187,34 +223,78 @@ public async Task<Mesh> ConvertDracoMeshToUnity(
}

#if DRACO_MESH_DATA
/// <summary>
/// Decodes a Draco mesh
/// </summary>
/// <param name="mesh">MeshData used to create the mesh</param>
/// <param name="encodedData">Compressed Draco data</param>
/// <param name="requireNormals">If draco does not contain normals and this is set to true, normals are calculated.</param>
/// <param name="requireTangents">If draco does not contain tangents and this is set to true, tangents and normals are calculated.</param>
/// <param name="weightsAttributeId">Draco attribute ID that contains bone weights (for skinning)</param>
/// <param name="jointsAttributeId">Draco attribute ID that contains bone joint indices (for skinning)</param>
/// <param name="forceUnityLayout">Enforces vertex buffer layout with highest compatibility. Enable this if you want to use blend shapes on the resulting mesh</param>
/// <returns>A DecodeResult</returns>
public async Task<DecodeResult> ConvertDracoMeshToUnity(
Mesh.MeshData mesh,
byte[] encodedData,
bool requireNormals = false,
bool requireTangents = false,
int weightsAttributeId = -1,
int jointsAttributeId = -1
int jointsAttributeId = -1,
bool forceUnityLayout = false
)
{
var encodedDataPtr = PinGCArrayAndGetDataAddress(encodedData, out var gcHandle);
var result = await ConvertDracoMeshToUnity(mesh, encodedDataPtr, encodedData.Length, requireNormals, requireTangents, weightsAttributeId, jointsAttributeId);
var result = await ConvertDracoMeshToUnity(
mesh,
encodedDataPtr,
encodedData.Length,
requireNormals,
requireTangents,
weightsAttributeId,
jointsAttributeId,
forceUnityLayout
);
UnsafeUtility.ReleaseGCObject(gcHandle);
return result;
}

public async Task<DecodeResult> ConvertDracoMeshToUnity(Mesh.MeshData mesh, NativeArray<byte> encodedData, bool requireNormals = false,
/// <summary>
/// Decodes a Draco mesh
/// </summary>
/// <param name="mesh">MeshData used to create the mesh</param>
/// <param name="encodedData">Compressed Draco data</param>
/// <param name="requireNormals">If draco does not contain normals and this is set to true, normals are calculated.</param>
/// <param name="requireTangents">If draco does not contain tangents and this is set to true, tangents and normals are calculated.</param>
/// <param name="weightsAttributeId">Draco attribute ID that contains bone weights (for skinning)</param>
/// <param name="jointsAttributeId">Draco attribute ID that contains bone joint indices (for skinning)</param>
/// <param name="forceUnityLayout">Enforces vertex buffer layout with highest compatibility. Enable this if you want to use blend shapes on the resulting mesh</param>
/// <returns>A DecodeResult</returns>
public async Task<DecodeResult> ConvertDracoMeshToUnity(
Mesh.MeshData mesh,
NativeArray<byte> encodedData,
bool requireNormals = false,
bool requireTangents = false,
int weightsAttributeId = -1,
int jointsAttributeId = -1
int jointsAttributeId = -1,
bool forceUnityLayout = false
#if UNITY_EDITOR
,bool sync = false
#endif
)
{
var encodedDataPtr = GetUnsafeReadOnlyIntPtr(encodedData);
return await ConvertDracoMeshToUnity(mesh, encodedDataPtr, encodedData.Length, requireNormals, requireTangents, weightsAttributeId, jointsAttributeId
return await ConvertDracoMeshToUnity(
mesh,
encodedDataPtr,
encodedData.Length,
requireNormals,
requireTangents,
weightsAttributeId,
jointsAttributeId,
forceUnityLayout
#if UNITY_EDITOR
,sync
,sync
#endif
);
}
Expand All @@ -228,7 +308,8 @@ async Task<DecodeResult> ConvertDracoMeshToUnity(
bool requireNormals,
bool requireTangents,
int weightsAttributeId = -1,
int jointsAttributeId = -1
int jointsAttributeId = -1,
bool forceUnityLayout = false
#if UNITY_EDITOR
,bool sync = false
#endif
Expand All @@ -240,7 +321,8 @@ async Task<Mesh> ConvertDracoMeshToUnity(
bool requireNormals,
bool requireTangents,
int weightsAttributeId = -1,
int jointsAttributeId = -1
int jointsAttributeId = -1,
bool forceUnityLayout = false
#if UNITY_EDITOR
,bool sync = false
#endif
Expand Down Expand Up @@ -275,9 +357,23 @@ async Task<Mesh> ConvertDracoMeshToUnity(
requireNormals = true;
}
#if DRACO_MESH_DATA
dracoNative.CreateMesh(out result.calculateNormals, requireNormals, requireTangents, weightsAttributeId, jointsAttributeId);
dracoNative.CreateMesh(
out result.calculateNormals,
requireNormals,
requireTangents,
weightsAttributeId,
jointsAttributeId,
forceUnityLayout
);
#else
dracoNative.CreateMesh(out var calculateNormals, requireNormals, requireTangents, weightsAttributeId, jointsAttributeId);
dracoNative.CreateMesh(
out var calculateNormals,
requireNormals,
requireTangents,
weightsAttributeId,
jointsAttributeId,
forceUnityLayout
);
#endif

#if UNITY_EDITOR
Expand Down
61 changes: 44 additions & 17 deletions Runtime/Scripts/DracoNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ void CalculateVertexParams(
bool requireTangents,
int weightsAttributeId,
int jointsAttributeId,
out bool calculateNormals
out bool calculateNormals,
bool forceUnityLayout = false
)
{
Profiler.BeginSample("CalculateVertexParams");
Expand Down Expand Up @@ -258,54 +259,71 @@ bool CreateAttributeMapById(VertexAttribute type, int id, DracoMesh* draco, out
if (requireTangents) {
attributes.Add(new CalculatedAttributeMap(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4, 4 ));
}
CreateAttributeMaps(AttributeType.COLOR, 1, dracoMesh);
var hasTexCoords = CreateAttributeMaps(AttributeType.TEX_COORD, 8, dracoMesh);
var hasTexCoordOrColor = CreateAttributeMaps(AttributeType.COLOR, 1, dracoMesh);
hasTexCoordOrColor |= CreateAttributeMaps(AttributeType.TEX_COORD, 8, dracoMesh);

var hasBlend = false;
var hasSkinning = false;
if (weightsAttributeId >= 0) {
if (CreateAttributeMapById(VertexAttribute.BlendWeight, weightsAttributeId, dracoMesh, out var map)) {
// BLENDHACK: Don't add bone weights, as they won't exist after Mesh.SetBoneWeights
// attributes.Add(map);
boneWeightMap = map;
hasBlend = true;
hasSkinning = true;
}
}
if (jointsAttributeId >= 0) {
if (CreateAttributeMapById(VertexAttribute.BlendIndices, jointsAttributeId, dracoMesh, out var map)) {
attributes.Add(map);
boneIndexMap = map;
hasBlend = true;
hasSkinning = true;
}
}

streamStrides = new int[maxStreamCount];
streamMemberCount = new int[maxStreamCount];
var streamIndex = 0;

// skinning requires SkinnedMeshRenderer layout
forceUnityLayout |= hasSkinning;

foreach (var attributeMap in attributes) {
// Stream assignment:
// Positions get a dedicated stream (0)
// The rest lands on stream 1

// If blend weights or blend indices are present, they land on stream 1
// while the rest is combined in stream 0
// TODO: BLENDHACK;
// A potentially following Mesh.SetBoneWeights changes stream assignment again!
// Maybe it would be better to rebuild Unity's logic?

// Mesh layout SkinnedMeshRenderer (used for skinning and blend shapes)
// requires:
// stream 0: position,normal,tangent
// stream 1: UVs,colors
// stream 2: blend weights/indices

switch (attributeMap.attribute) {
case VertexAttribute.Position:
// Attributes that define/change the position go to stream 0
streamIndex = 0;
break;
default:
// The rest to stream 1, but not if blend weights/joints are present
// In this case putting everything in stream 0 (except blend weights/joints) proved to work
streamIndex = hasBlend ? 0 : 1;
case VertexAttribute.Normal:
case VertexAttribute.Tangent:
streamIndex = forceUnityLayout ? 0 : 1;
break;
case VertexAttribute.TexCoord0:
case VertexAttribute.TexCoord1:
case VertexAttribute.TexCoord2:
case VertexAttribute.TexCoord3:
case VertexAttribute.TexCoord4:
case VertexAttribute.TexCoord5:
case VertexAttribute.TexCoord6:
case VertexAttribute.TexCoord7:
case VertexAttribute.Color:
streamIndex = 1;
break;
case VertexAttribute.BlendWeight:
case VertexAttribute.BlendIndices:
// Special case: blend weights/joints always have a special stream
streamIndex = 1;
streamIndex = hasTexCoordOrColor ? 2 : 1;
break;
}
#if !DRACO_MESH_DATA
Expand Down Expand Up @@ -465,20 +483,29 @@ public JobHandle DecodeVertexData(
return releaseDreacoMeshJobHandle;
}

public void CreateMesh(
internal void CreateMesh(
out bool calculateNormals,
bool requireNormals = false,
bool requireTangents = false,
int weightsAttributeId = -1,
int jointsAttributeId = -1
int jointsAttributeId = -1,
bool forceUnityLayout = false
)
{
Profiler.BeginSample("CreateMesh");

var dracoMesh = (DracoMesh*)dracoTempResources[meshPtrIndex];
allocator = dracoMesh->numVertices > persistentDataThreshold ? Allocator.Persistent : Allocator.TempJob;

CalculateVertexParams(dracoMesh, requireNormals, requireTangents, weightsAttributeId, jointsAttributeId, out calculateNormals);
CalculateVertexParams(
dracoMesh,
requireNormals,
requireTangents,
weightsAttributeId,
jointsAttributeId,
out calculateNormals,
forceUnityLayout
);

Profiler.BeginSample("SetParameters");
#if DRACO_MESH_DATA
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "com.atteneder.draco",
"version": "3.0.3",
"version": "3.1.0",
"displayName": "Draco 3D Data Compression",
"description": "Draco is an open-source library for compressing and decompressing 3D geometric meshes and point clouds. It is intended to improve the storage and transmission of 3D graphics. This package allows you to apply Draco compression to meshes, import Draco files and load them at runtime.",
"unity": "2019.3",
Expand Down

0 comments on commit 9f5161b

Please sign in to comment.