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;
|
||||
const bool useSeparatePositions = !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;
|
||||
// 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 });
|
||||
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 });
|
||||
}
|
||||
if (!useSeparateColors && hasColors)
|
||||
@@ -714,8 +723,18 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
|
||||
case VertexElement::Types::BlendIndices:
|
||||
{
|
||||
const Int4 blendIndices = mesh.BlendIndices.Get()[vertex];
|
||||
const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
|
||||
stream.Write(blendIndicesEnc);
|
||||
if (blendIndicesFormat == PixelFormat::R8G8B8A8_UInt)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
case VertexElement::Types::BlendWeights:
|
||||
|
||||
@@ -396,8 +396,27 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
||||
// Validate input
|
||||
if (nodes.Count() <= 0 || nodes.Count() > MAX_uint16)
|
||||
return true;
|
||||
if (bones.Count() <= 0 || bones.Count() > MODEL_MAX_BONES_PER_MODEL)
|
||||
return true;
|
||||
if (bones.Count() <= 0)
|
||||
{
|
||||
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;
|
||||
if (!model->IsVirtual())
|
||||
return true;
|
||||
@@ -557,7 +576,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
lod._model = this;
|
||||
lod._lodIndex = lodIndex;
|
||||
stream.Read(lod.ScreenSize);
|
||||
|
||||
|
||||
// Meshes
|
||||
uint16 meshesCount;
|
||||
stream.Read(meshesCount);
|
||||
@@ -579,7 +598,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
return true;
|
||||
}
|
||||
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
||||
|
||||
|
||||
// Bounds
|
||||
BoundingBox box;
|
||||
stream.Read(box);
|
||||
|
||||
@@ -32,6 +32,6 @@
|
||||
#define MAX_BONES_PER_VERTEX MODEL_MAX_BONES_PER_VERTEX
|
||||
|
||||
// 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
|
||||
#define MAX_BONES_PER_MODEL MODEL_MAX_BONES_PER_MODEL
|
||||
|
||||
@@ -1436,6 +1436,66 @@ static PixelFormatSampler PixelFormatSamplers[] =
|
||||
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
|
||||
|
||||
@@ -126,6 +126,16 @@ String GPUVertexLayout::GetElementsString() const
|
||||
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)
|
||||
{
|
||||
// Hash input layout
|
||||
|
||||
@@ -47,6 +47,13 @@ public:
|
||||
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>
|
||||
/// 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>
|
||||
|
||||
Reference in New Issue
Block a user