Add support for up to 65536 skeleton bones in skinned meshes
https://forum.flaxengine.com/t/import-fbx-with-bones-more-than-256/1196
This commit is contained in:
@@ -613,6 +613,15 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
|
|||||||
Array<GPUVertexLayout::Elements, FixedAllocation<MODEL_MAX_VB>> vbElements;
|
Array<GPUVertexLayout::Elements, FixedAllocation<MODEL_MAX_VB>> vbElements;
|
||||||
const bool useSeparatePositions = !isSkinned;
|
const bool useSeparatePositions = !isSkinned;
|
||||||
const bool useSeparateColors = !isSkinned;
|
const bool useSeparateColors = !isSkinned;
|
||||||
|
PixelFormat blendIndicesFormat = PixelFormat::R8G8B8A8_UInt;
|
||||||
|
for (const Int4& indices : mesh.BlendIndices)
|
||||||
|
{
|
||||||
|
if (indices.MaxValue() > MAX_uint8)
|
||||||
|
{
|
||||||
|
blendIndicesFormat = PixelFormat::R16G16B16A16_UInt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
{
|
{
|
||||||
byte vbIndex = 0;
|
byte vbIndex = 0;
|
||||||
// TODO: add option to quantize vertex positions (eg. 16-bit)
|
// TODO: add option to quantize vertex positions (eg. 16-bit)
|
||||||
@@ -640,7 +649,7 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
|
|||||||
vb.Add({ VertexElement::Types::Tangent, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
vb.Add({ VertexElement::Types::Tangent, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||||
if (isSkinned)
|
if (isSkinned)
|
||||||
{
|
{
|
||||||
vb.Add({ VertexElement::Types::BlendIndices, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UInt });
|
vb.Add({ VertexElement::Types::BlendIndices, vbIndex, 0, 0, blendIndicesFormat });
|
||||||
vb.Add({ VertexElement::Types::BlendWeights, vbIndex, 0, 0, PixelFormat::R16G16B16A16_Float });
|
vb.Add({ VertexElement::Types::BlendWeights, vbIndex, 0, 0, PixelFormat::R16G16B16A16_Float });
|
||||||
}
|
}
|
||||||
if (!useSeparateColors && hasColors)
|
if (!useSeparateColors && hasColors)
|
||||||
@@ -714,8 +723,18 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
|
|||||||
case VertexElement::Types::BlendIndices:
|
case VertexElement::Types::BlendIndices:
|
||||||
{
|
{
|
||||||
const Int4 blendIndices = mesh.BlendIndices.Get()[vertex];
|
const Int4 blendIndices = mesh.BlendIndices.Get()[vertex];
|
||||||
const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
|
if (blendIndicesFormat == PixelFormat::R8G8B8A8_UInt)
|
||||||
stream.Write(blendIndicesEnc);
|
{
|
||||||
|
// 8-bit indices
|
||||||
|
const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
|
||||||
|
stream.Write(blendIndicesEnc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 16-bit indices
|
||||||
|
const uint16 blendIndicesEnc[4] = { (uint16)blendIndices.X, (uint16)blendIndices.Y, (uint16)blendIndices.Z, (uint16)blendIndices.W };
|
||||||
|
stream.Write(blendIndicesEnc);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case VertexElement::Types::BlendWeights:
|
case VertexElement::Types::BlendWeights:
|
||||||
|
|||||||
@@ -396,8 +396,27 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
|||||||
// Validate input
|
// Validate input
|
||||||
if (nodes.Count() <= 0 || nodes.Count() > MAX_uint16)
|
if (nodes.Count() <= 0 || nodes.Count() > MAX_uint16)
|
||||||
return true;
|
return true;
|
||||||
if (bones.Count() <= 0 || bones.Count() > MODEL_MAX_BONES_PER_MODEL)
|
if (bones.Count() <= 0)
|
||||||
return true;
|
{
|
||||||
|
if (bones.Count() > 255)
|
||||||
|
{
|
||||||
|
for (auto& lod : LODs)
|
||||||
|
{
|
||||||
|
for (auto& mesh : lod.Meshes)
|
||||||
|
{
|
||||||
|
auto* vertexLayout = mesh.GetVertexLayout();
|
||||||
|
VertexElement element = vertexLayout ? vertexLayout->FindElement(VertexElement::Types::BlendIndices) : VertexElement();
|
||||||
|
if (element.Format == PixelFormat::R8G8B8A8_UInt)
|
||||||
|
{
|
||||||
|
LOG(Warning, "Cannot use more than 255 bones if skinned model uses 8-bit storage for blend indices in vertices.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bones.Count() > MODEL_MAX_BONES_PER_MODEL)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
auto model = this;
|
auto model = this;
|
||||||
if (!model->IsVirtual())
|
if (!model->IsVirtual())
|
||||||
return true;
|
return true;
|
||||||
@@ -557,7 +576,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
|
|||||||
lod._model = this;
|
lod._model = this;
|
||||||
lod._lodIndex = lodIndex;
|
lod._lodIndex = lodIndex;
|
||||||
stream.Read(lod.ScreenSize);
|
stream.Read(lod.ScreenSize);
|
||||||
|
|
||||||
// Meshes
|
// Meshes
|
||||||
uint16 meshesCount;
|
uint16 meshesCount;
|
||||||
stream.Read(meshesCount);
|
stream.Read(meshesCount);
|
||||||
@@ -579,7 +598,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
||||||
|
|
||||||
// Bounds
|
// Bounds
|
||||||
BoundingBox box;
|
BoundingBox box;
|
||||||
stream.Read(box);
|
stream.Read(box);
|
||||||
|
|||||||
@@ -32,6 +32,6 @@
|
|||||||
#define MAX_BONES_PER_VERTEX MODEL_MAX_BONES_PER_VERTEX
|
#define MAX_BONES_PER_VERTEX MODEL_MAX_BONES_PER_VERTEX
|
||||||
|
|
||||||
// Defines the maximum allowed amount of skeleton bones to be used with skinned model
|
// Defines the maximum allowed amount of skeleton bones to be used with skinned model
|
||||||
#define MODEL_MAX_BONES_PER_MODEL 256
|
#define MODEL_MAX_BONES_PER_MODEL 0xffff
|
||||||
// [Deprecated in v1.10] Use MODEL_MAX_BONES_PER_MODEL
|
// [Deprecated in v1.10] Use MODEL_MAX_BONES_PER_MODEL
|
||||||
#define MAX_BONES_PER_MODEL MODEL_MAX_BONES_PER_MODEL
|
#define MAX_BONES_PER_MODEL MODEL_MAX_BONES_PER_MODEL
|
||||||
|
|||||||
@@ -1436,6 +1436,66 @@ static PixelFormatSampler PixelFormatSamplers[] =
|
|||||||
Platform::MemoryCopy(ptr, data, sizeof(data));
|
Platform::MemoryCopy(ptr, data, sizeof(data));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
PixelFormat::R16G16B16A16_UInt,
|
||||||
|
sizeof(uint16) * 4,
|
||||||
|
[](const void* ptr)
|
||||||
|
{
|
||||||
|
uint16 data[4];
|
||||||
|
Platform::MemoryCopy(data, ptr, sizeof(data));
|
||||||
|
return Float4(data[0], data[1], data[2], data[3]);
|
||||||
|
},
|
||||||
|
[](void* ptr, const Float4& value)
|
||||||
|
{
|
||||||
|
uint16 data[4] = { (uint16)value.X, (uint16)value.Y, (uint16)value.Z, (uint16)value.W};
|
||||||
|
Platform::MemoryCopy(ptr, data, sizeof(data));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PixelFormat::R16G16B16A16_SInt,
|
||||||
|
sizeof(int16) * 4,
|
||||||
|
[](const void* ptr)
|
||||||
|
{
|
||||||
|
int16 data[4];
|
||||||
|
Platform::MemoryCopy(data, ptr, sizeof(data));
|
||||||
|
return Float4(data[0], data[1], data[2], data[3]);
|
||||||
|
},
|
||||||
|
[](void* ptr, const Float4& value)
|
||||||
|
{
|
||||||
|
int16 data[4] = { (int16)value.X, (int16)value.Y, (int16)value.Z, (int16)value.W};
|
||||||
|
Platform::MemoryCopy(ptr, data, sizeof(data));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PixelFormat::R32G32B32A32_UInt,
|
||||||
|
sizeof(uint32) * 4,
|
||||||
|
[](const void* ptr)
|
||||||
|
{
|
||||||
|
uint32 data[4];
|
||||||
|
Platform::MemoryCopy(data, ptr, sizeof(data));
|
||||||
|
return Float4(data[0], data[1], data[2], data[3]);
|
||||||
|
},
|
||||||
|
[](void* ptr, const Float4& value)
|
||||||
|
{
|
||||||
|
uint32 data[4] = { (uint32)value.X, (uint32)value.Y, (uint32)value.Z, (uint32)value.W};
|
||||||
|
Platform::MemoryCopy(ptr, data, sizeof(data));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PixelFormat::R32G32B32A32_SInt,
|
||||||
|
sizeof(int32) * 4,
|
||||||
|
[](const void* ptr)
|
||||||
|
{
|
||||||
|
int32 data[4];
|
||||||
|
Platform::MemoryCopy(data, ptr, sizeof(data));
|
||||||
|
return Float4(data[0], data[1], data[2], data[3]);
|
||||||
|
},
|
||||||
|
[](void* ptr, const Float4& value)
|
||||||
|
{
|
||||||
|
int32 data[4] = { (int32)value.X, (int32)value.Y, (int32)value.Z, (int32)value.W};
|
||||||
|
Platform::MemoryCopy(ptr, data, sizeof(data));
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
void PixelFormatSampler::Store(void* data, int32 x, int32 y, int32 rowPitch, const Color& color) const
|
void PixelFormatSampler::Store(void* data, int32 x, int32 y, int32 rowPitch, const Color& color) const
|
||||||
|
|||||||
@@ -126,6 +126,16 @@ String GPUVertexLayout::GetElementsString() const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VertexElement GPUVertexLayout::FindElement(VertexElement::Types type) const
|
||||||
|
{
|
||||||
|
for (const VertexElement& e : _elements)
|
||||||
|
{
|
||||||
|
if (e.Type == type)
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
return VertexElement();
|
||||||
|
}
|
||||||
|
|
||||||
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets)
|
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets)
|
||||||
{
|
{
|
||||||
// Hash input layout
|
// Hash input layout
|
||||||
|
|||||||
@@ -47,6 +47,13 @@ public:
|
|||||||
return _stride;
|
return _stride;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Searches for a given element type in a layout.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of element to find.</param>
|
||||||
|
/// <returns>Found element with properties or empty if missing.</returns>
|
||||||
|
API_FUNCTION() VertexElement FindElement(VertexElement::Types type) const;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the vertex layout for a given list of elements. Uses internal cache to skip creating layout if it's already exists for a given list.
|
/// Gets the vertex layout for a given list of elements. Uses internal cache to skip creating layout if it's already exists for a given list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user