Use case: Decoding
Draco for Unity let's you decode Draco™ data at run-time.
Before you start
Make sure you have Draco for Unity installed and referenced the Draco
assembly.
Prepare a Draco file
You need a Draco file to load, so either create one or download a sample (like the famous and cute Stanford Bunny).
If you copy a Draco (.drc
) into the Assets folder directly, it'll get decoded and imported as mesh in the Editor instantly. To avoid that and preserve the compressed state until run-time you can either:
- Change the file extension from
.drc
to.drc.bytes
to convert it into a [TextAsset](xref: UnityEngine.TextAsset) - Copy the file into the
StreamingAssets
folder withinAssets
Decoding
Decoding can be achieved by calling one of the DecodeMesh overloads.
They differ in input data type (byte[]
/NativeSlice<byte>
) and simple vs. advanced Mesh API and optionally take decodeSettings
and attributeIdMap
parameters.
Attribute assignment via Draco identifier
DecodeMesh's attributeIdMap
parameter allows assignment of Draco vertex attributes to Unity Mesh vertex attributes.
In Draco data vertex attributes have a unique identifier (integer value) and one of these types:
- Position
- Color
- TextureCoordinate
- Generic
Source: geometry_attribute.h
(bit-stream version 2,2)
Upon decoding, Draco for Unity assign Draco attributes to Unity attributes via the identifier in the attributeIdMap
. If the id for a particular vertex attribute is not provided, it falls back to assigning by type. In other words, all attributes are queried and if one matches the given type, it gets assigned and decoded.
You might notice that some Unity vertex attributes are missing in Draco (most notably tangents, blend weights and blend indices). They can still be encoded as generic attributes, but they require re-assignment via an identifier via attributeIdMap
. In practice this is usually done via an enclosing data format (like glTF™) that stores this assignment information.
Decode to Mesh
Here's sample code that decodes a TextAsset containing Draco data and assigns it to the GameObject's MeshFilter, in order to have it rendered.
[SerializeField]
TextAsset m_DracoData;
async void Start()
{
// Async decoding has to start on the main thread and spawns multiple C# jobs.
var mesh = await DracoDecoder.DecodeMesh(m_DracoData.bytes);
if (mesh != null)
{
// Use the resulting mesh
GetComponent<MeshFilter>().mesh = mesh;
}
}
Decode using the advanced Mesh API
Starting with Unity 2020.2 you can create Meshes more efficiently via MeshData
and MeshDataArray
.
Draco for Unity allows you to leverage that improved performance at the cost of a bit more overhead code.
The important difference is that instead of returning a Mesh
directly, it just configures the MeshData
properly and fills its buffers. It's up to the user to:
- Create the
Mesh
instance(s) - Apply the data via
ApplyAndDisposeWritableMeshData
- Calculate correct normals/tangents if required (as explained in shading parameters)
- In case the mesh had bone weight data, apply and dispose those as well (optional extra step)
Here's a full example
[SerializeField]
TextAsset m_DracoData;
[SerializeField]
bool m_ConvertSpace;
[SerializeField]
bool m_RequireNormals;
[SerializeField]
bool m_RequireTangents;
async void Start()
{
// Allocate single mesh data (you can/should bulk allocate multiple at once, if you're loading multiple
// Draco meshes)
var meshDataArray = Mesh.AllocateWritableMeshData(1);
// DecodeSettings hold a couple of decode settings
var decodeSettings = DecodeSettings.None;
if (m_ConvertSpace)
{
// Coordinate space is converted from right-hand (like in glTF) to left-hand (Unity) by inverting the
// x-axis.
decodeSettings |= DecodeSettings.ConvertSpace;
}
if (m_RequireTangents)
{
// Ensures normal and tangent vertex attributes. If Draco data does not contain them, they are still
// allocated and we have to calculate them afterwards (see below).
decodeSettings |= DecodeSettings.RequireNormalsAndTangents;
}
else if (m_RequireNormals)
{
// Ensures normal vertex attribute. If Draco data does not normals, they are still allocated and we have
// to calculate them afterwards (see below)
decodeSettings |= DecodeSettings.RequireNormals;
}
// Async decoding has to start on the main thread and spawns multiple C# jobs.
var result = await DracoDecoder.DecodeMesh(meshDataArray[0], m_DracoData.bytes, decodeSettings);
if (result.success)
{
// Apply onto new Mesh
var mesh = new Mesh();
Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, mesh);
// If Draco mesh has bone weights, apply them now.
// To get these, you have to supply the correct attribute IDs
// to `ConvertDracoMeshToUnity` above (optional parameters).
if (result.boneWeightData != null)
{
result.boneWeightData.ApplyOnMesh(mesh);
result.boneWeightData.Dispose();
}
if (m_RequireNormals && result.calculateNormals)
{
// If draco didn't contain normals, calculate them.
mesh.RecalculateNormals();
}
if (m_RequireTangents && m_RequireTangents)
{
// If required (e.g. for consistent specular shading), calculate tangents
mesh.RecalculateTangents();
}
// Use the resulting mesh
GetComponent<MeshFilter>().mesh = mesh;
}
}
Shading parameters
Some shaders might require a mesh to have correct normals (or normals plus tangents). For example, the Unlit (a flat-shaded color) shader doesn't require neither while another one with a normal map requires both. A Draco mesh might not contain normals or tangents and in those cases normals (or normals and tangents) need to be calculated. The DecodeMesh's decodeSettings
parameter's RequireNormals
, RequireTangents
and RequireNormalsAndTangents
fields allow you to pass on that aspect of the anticipated mesh application.
DecodeMesh overloads the return a ready-to-use Mesh will perform the normals/tangents calculations internally.
On DecodeMesh overloads that work on MeshData you'll have to perform those calculations yourself. It'll only ensure that the vertex buffer layout allocates a normal/tangent attribute and the returned DecodeResult's calculateNormals field indicates whether normals need to be calculated. This can be achieved by calling RecalculateNormals after the data was applied (via ApplyAndDisposeWritableMeshData
). Tangents decoding is not supported so they always need to be calculated.
Blend shapes parameters
Bone weight and joint attributes (required for blend shapes, also known as skinning or morph targets) are stored as generic attributes within Draco, thus it's required to assign those by identifier. See Attribute assignment via Draco identifier for details about attribute assignment.
Not all vertex buffer layout variants are compatible with blend shapes. If you encounter problems, enforce a Unity specific layout that promises high compatibility by setting the forceUnityLayout
to true
.
Decode Sample
A fully setup sample scene can be found in the Decode Sample.
Trademarks
Unity is a registered trademark of Unity Technologies.
Draco™ is a trademark of Google LLC.
Khronos® is a registered trademark and glTF™ is a trademark of The Khronos Group Inc.