236 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			236 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 
								 | 
							
								---
							 | 
						|||
| 
								 | 
							
								title: FBXAnimation导入逻辑
							 | 
						|||
| 
								 | 
							
								date: 2023-12-11 11:28:36
							 | 
						|||
| 
								 | 
							
								excerpt: 
							 | 
						|||
| 
								 | 
							
								tags: 
							 | 
						|||
| 
								 | 
							
								rating: ⭐
							 | 
						|||
| 
								 | 
							
								---
							 | 
						|||
| 
								 | 
							
								# BVHImport插件中的案例
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								UObject* UBVHImportFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& FileName, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled)
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
									if (!shouldImport) {
							 | 
						|||
| 
								 | 
							
										return nullptr;
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									bool fileSuccess = BVHFile.ImportData(TCHAR_TO_ANSI(*FileName));
							 | 
						|||
| 
								 | 
							
									
							 | 
						|||
| 
								 | 
							
									const bool bIsUnattended = (IsAutomatedImport()
							 | 
						|||
| 
								 | 
							
										|| FApp::IsUnattended()
							 | 
						|||
| 
								 | 
							
										|| IsRunningCommandlet()
							 | 
						|||
| 
								 | 
							
										|| GIsRunningUnattendedScript);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									// Check if it's a re-import
							 | 
						|||
| 
								 | 
							
									if (InParent != nullptr)
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										UObject* ExistingObject = StaticFindObject(UObject::StaticClass(), InParent, *InName.ToString());
							 | 
						|||
| 
								 | 
							
										if (ExistingObject)
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											//reimport逻辑略
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									
							 | 
						|||
| 
								 | 
							
									if (fileSuccess)
							 | 
						|||
| 
								 | 
							
									{	
							 | 
						|||
| 
								 | 
							
										UAnimSequence* AnimSequence = NewObject<UAnimSequence>(InParent, InName, Flags & ~EObjectFlags::RF_Transactional);
							 | 
						|||
| 
								 | 
							
										if (Skeleton == NULL) {
							 | 
						|||
| 
								 | 
							
											//创建骨骼资产略
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
										else {
							 | 
						|||
| 
								 | 
							
											AnimSequence->SetSkeleton(Skeleton);//Skeleton bvhSkel
							 | 
						|||
| 
								 | 
							
											AnimSequence->SetPreviewMesh(PreviewMesh);			
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
										ExtractAnimDataFromBVHFile(AnimSequence);
							 | 
						|||
| 
								 | 
							
										return AnimSequence;
							 | 
						|||
| 
								 | 
							
										
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									return nullptr;
							 | 
						|||
| 
								 | 
							
									
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								void UBVHImportFactory::ExtractAnimDataFromBVHFile(UAnimSequence* AnimSequence) {
							 | 
						|||
| 
								 | 
							
									if (AnimSequence)
							 | 
						|||
| 
								 | 
							
									{		
							 | 
						|||
| 
								 | 
							
										// create animation		
							 | 
						|||
| 
								 | 
							
										IAnimationDataController& Controller = AnimSequence->GetController();
							 | 
						|||
| 
								 | 
							
										Controller.OpenBracket(LOCTEXT("ImportBVHAnimation", "Importing BVH Animation"));
							 | 
						|||
| 
								 | 
							
										Controller.ResetModel();
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										// Write animation data into animation sequence.
							 | 
						|||
| 
								 | 
							
										// Extract transform of hip to create root motion.
							 | 
						|||
| 
								 | 
							
										const FReferenceSkeleton& RefSkeleton = AnimSequence->GetSkeleton()->GetReferenceSkeleton();
							 | 
						|||
| 
								 | 
							
										const FName RootName = RefSkeleton.GetBoneName(0);
							 | 
						|||
| 
								 | 
							
										const int32 NumOfKeys = BVHFile.Root->FrameOffset.Num();
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										AnimSequence->ImportFileFramerate = (float)BVHFile.Header.DataRate;
							 | 
						|||
| 
								 | 
							
										AnimSequence->ImportResampleFramerate = BVHFile.Header.DataRate;
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										Controller.SetPlayLength(float(NumOfKeys - 1) / (float)BVHFile.Header.DataRate);
							 | 
						|||
| 
								 | 
							
										Controller.SetFrameRate(FFrameRate(BVHFile.Header.DataRate, 1));
							 | 
						|||
| 
								 | 
							
										
							 | 
						|||
| 
								 | 
							
										RecursiveReadKeysFromNode(Controller, BVHFile.Root.Get());
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										Controller.NotifyPopulated();
							 | 
						|||
| 
								 | 
							
										Controller.CloseBracket();
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										AnimSequence->AdditiveAnimType = EAdditiveAnimationType::AAT_None;
							 | 
						|||
| 
								 | 
							
										AnimSequence->PostEditChange();
							 | 
						|||
| 
								 | 
							
										FAssetRegistryModule::AssetCreated(AnimSequence);
							 | 
						|||
| 
								 | 
							
										AnimSequence->MarkPackageDirty();
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								void UBVHImportFactory::RecursiveReadKeysFromNode(IAnimationDataController& Controller, FNode* Node)
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
									if (Node)
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										Controller.AddBoneTrack(Node->Name,false);
							 | 
						|||
| 
								 | 
							
										Controller.SetBoneTrackKeys(Node->Name, Node->FrameOffset, Node->FrameQuat, Node->FrameScale,false);
							 | 
						|||
| 
								 | 
							
										if (Node->Children.Num() > 0)
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											for (NodePtr Child : Node->Children)
							 | 
						|||
| 
								 | 
							
											{
							 | 
						|||
| 
								 | 
							
												RecursiveReadKeysFromNode(Controller, Child.Get());
							 | 
						|||
| 
								 | 
							
											}
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# UE5中使用FBXSDK导入动画逻辑
							 | 
						|||
| 
								 | 
							
								- UAnimSequence * UEditorEngine::ImportFbxAnimation
							 | 
						|||
| 
								 | 
							
									- UAnimSequence * UnFbx::FFbxImporter::ImportAnimations
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								UE5中FBXSDK相关函数调用方式:
							 | 
						|||
| 
								 | 
							
								- bool FFbxImporter::OpenFile(FString Filename)
							 | 
						|||
| 
								 | 
							
								- bool FFbxImporter::ImportFile(FString Filename, bool bPreventMaterialNameClash /*=false*/)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# 参考
							 | 
						|||
| 
								 | 
							
								1. Interchange\\Runtime\\Source\\Parsers
							 | 
						|||
| 
								 | 
							
									1. InterchangeFbxParser.Build.cs
							 | 
						|||
| 
								 | 
							
									2. FbxInclude.h:FBXSDK头文件包含问题。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# UE5中使用FBXSDK导出动画逻辑
							 | 
						|||
| 
								 | 
							
								1. FFbxExporter::ExportSkeletalMeshToFbx => FFbxExporter::ExportAnimSequence => FFbxExporter::ExportAnimSequenceToFbx 
							 | 
						|||
| 
								 | 
							
								2. FFbxExporter::CorrectAnimTrackInterpolation
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								直接导出会有问题,所以UE在这里做了一步Correct:
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								// The curve code doesn't differentiate between angles and other data, so an interpolation from 179 to -179  
							 | 
						|||
| 
								 | 
							
								// will cause the bone to rotate all the way around through 0 degrees. So here we make a second pass over the  
							 | 
						|||
| 
								 | 
							
								// rotation tracks to convert the angles into a more interpolation-friendly format.
							 | 
						|||
| 
								 | 
							
								FFbxExporter::CorrectAnimTrackInterpolation()
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
								void FFbxExporter::CorrectAnimTrackInterpolation( TArray<FbxNode*>& BoneNodes, FbxAnimLayer* InAnimLayer )
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
									// Add the animation data to the bone nodes
							 | 
						|||
| 
								 | 
							
									for(int32 BoneIndex = 0; BoneIndex < BoneNodes.Num(); ++BoneIndex)
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										FbxNode* CurrentBoneNode = BoneNodes[BoneIndex];
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										// Fetch the AnimCurves
							 | 
						|||
| 
								 | 
							
										FbxAnimCurve* Curves[3];
							 | 
						|||
| 
								 | 
							
										Curves[0] = CurrentBoneNode->LclRotation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
							 | 
						|||
| 
								 | 
							
										Curves[1] = CurrentBoneNode->LclRotation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true);
							 | 
						|||
| 
								 | 
							
										Curves[2] = CurrentBoneNode->LclRotation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										for(int32 CurveIndex = 0; CurveIndex < 3; ++CurveIndex)
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											FbxAnimCurve* CurrentCurve = Curves[CurveIndex];
							 | 
						|||
| 
								 | 
							
											CurrentCurve->KeyModifyBegin();
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
											float CurrentAngleOffset = 0.f;
							 | 
						|||
| 
								 | 
							
											for(int32 KeyIndex = 1; KeyIndex < CurrentCurve->KeyGetCount(); ++KeyIndex)
							 | 
						|||
| 
								 | 
							
											{
							 | 
						|||
| 
								 | 
							
												float PreviousOutVal	= CurrentCurve->KeyGetValue( KeyIndex-1 );
							 | 
						|||
| 
								 | 
							
												float CurrentOutVal		= CurrentCurve->KeyGetValue( KeyIndex );
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
												float DeltaAngle = (CurrentOutVal + CurrentAngleOffset) - PreviousOutVal;
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
												if(DeltaAngle >= 180)
							 | 
						|||
| 
								 | 
							
												{
							 | 
						|||
| 
								 | 
							
													CurrentAngleOffset -= 360;
							 | 
						|||
| 
								 | 
							
												}
							 | 
						|||
| 
								 | 
							
												else if(DeltaAngle <= -180)
							 | 
						|||
| 
								 | 
							
												{
							 | 
						|||
| 
								 | 
							
													CurrentAngleOffset += 360;
							 | 
						|||
| 
								 | 
							
												}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
												CurrentOutVal += CurrentAngleOffset;
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
												CurrentCurve->KeySetValue(KeyIndex, CurrentOutVal);
							 | 
						|||
| 
								 | 
							
											}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
											CurrentCurve->KeyModifyEnd();
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## AnimSequence生成逻辑
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								//创建UAnimSequence
							 | 
						|||
| 
								 | 
							
								FString     ParentPath = FString::Printf(TEXT("%s/%s"), *FPackageName::GetLongPackagePath(*Outer->GetName()), *SequenceName);  
							 | 
						|||
| 
								 | 
							
								UObject*    ParentPackage = CreatePackage( *ParentPath);  
							 | 
						|||
| 
								 | 
							
								UObject* Object = LoadObject<UObject>(ParentPackage, *SequenceName, NULL, (LOAD_Quiet | LOAD_NoWarn), NULL);  
							 | 
						|||
| 
								 | 
							
								UAnimSequence * DestSeq = Cast<UAnimSequence>(Object);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								//设置骨骼
							 | 
						|||
| 
								 | 
							
								DestSeq->SetSkeleton(Skeleton);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								//设置文件帧率率与重采样率,修改帧率记得调用IAnimationDataController.OpenBracket()、NotifyPopulated()、CloseBracket()
							 | 
						|||
| 
								 | 
							
								IAnimationDataController& AnimationDataController = AnimSequence->GetController();  
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								AnimationDataController.OpenBracket(FText::FromString("Importing Animation"));
							 | 
						|||
| 
								 | 
							
								DestSeq->ImportFileFramerate = GetOriginalFbxFramerate();  
							 | 
						|||
| 
								 | 
							
								DestSeq->ImportResampleFramerate = ResampleRate;
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								AnimationDataController.SetFrameRate(FFrameRate(AnimSequencesFPS,1));  
							 | 
						|||
| 
								 | 
							
								AnimationDataController.SetPlayLength(FacialTime);
							 | 
						|||
| 
								 | 
							
								AnimationDataController.NotifyPopulated();  
							 | 
						|||
| 
								 | 
							
								AnimationDataController.CloseBracket();
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### 导入Curve相关逻辑
							 | 
						|||
| 
								 | 
							
								1. UnFbx::FFbxImporter::ImportAnimation()
							 | 
						|||
| 
								 | 
							
								2. UnFbx::FFbxImporter::ImportBlendShapeCurves()
							 | 
						|||
| 
								 | 
							
								3. UnFbx::FFbxImporter::ImportCurveToAnimSequence() => UnFbx::FFbxImporter::ImportCurve()
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## 异步机制改进
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								void URuntimeAudioImporterLibrary::ImportAudioFromFile(const FString& FilePath, ERuntimeAudioFormat AudioFormat)  
							 | 
						|||
| 
								 | 
							
								{  
							 | 
						|||
| 
								 | 
							
								    AsyncTask(ENamedThreads::AnyBackgroundHiPriTask, [WeakThis = TWeakObjectPtr<URuntimeAudioImporterLibrary>(this), FilePath, AudioFormat]() mutable  
							 | 
						|||
| 
								 | 
							
								    {  
							 | 
						|||
| 
								 | 
							
								       if (!WeakThis.IsValid())  
							 | 
						|||
| 
								 | 
							
								       {          UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to import audio from file '%s' because the RuntimeAudioImporterLibrary object has been destroyed"), *FilePath);  
							 | 
						|||
| 
								 | 
							
								          return;  
							 | 
						|||
| 
								 | 
							
								       }  
							 | 
						|||
| 
								 | 
							
								       FGCObjectScopeGuard Guard(WeakThis.Get());  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								       if (!FPaths::FileExists(FilePath))  
							 | 
						|||
| 
								 | 
							
								       {          WeakThis->OnResult_Internal(nullptr, ERuntimeImportStatus::AudioDoesNotExist);  
							 | 
						|||
| 
								 | 
							
								          return;  
							 | 
						|||
| 
								 | 
							
								       }  
							 | 
						|||
| 
								 | 
							
								       AudioFormat = AudioFormat == ERuntimeAudioFormat::Auto ? GetAudioFormat(FilePath) : AudioFormat;  
							 | 
						|||
| 
								 | 
							
								       AudioFormat = AudioFormat == ERuntimeAudioFormat::Invalid ? ERuntimeAudioFormat::Auto : AudioFormat;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								       TArray64<uint8> AudioBuffer;  
							 | 
						|||
| 
								 | 
							
								       if (!LoadAudioFileToArray(AudioBuffer, *FilePath))  
							 | 
						|||
| 
								 | 
							
								       {          WeakThis->OnResult_Internal(nullptr, ERuntimeImportStatus::LoadFileToArrayError);  
							 | 
						|||
| 
								 | 
							
								          return;  
							 | 
						|||
| 
								 | 
							
								       }  
							 | 
						|||
| 
								 | 
							
								       WeakThis->ImportAudioFromBuffer(MoveTemp(AudioBuffer), AudioFormat);  
							 | 
						|||
| 
								 | 
							
								    });}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# 其他导入代码
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								USoundWave* FFacialAnimationImportItem::ImportSoundWave(const FString& InSoundWavePackageName, const FString& InSoundWaveAssetName, const FString& InWavFilename) const
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 |