Uneal Blueprint 实现 ControlRig(控制绑定)

173 阅读2分钟

最近在学习Unreal, 学到了处理角色自适应地形部分的内容, 作者使用了 Control Rig, 我学习时遇到的一些问题, 在此记录, 如有大佬路过, 欢迎指点.

蓝图

先把蓝图内容贴上, 蓝图有点大, 可能需要copy 到本地才能看清楚.

image.png

源码

1

FRigUnit_GetBoneTransform_Execute() {
    DECLARE_SCOPE_HIERARCHICAL_COUNTER_RIGUNIT()
	const URigHierarchy* Hierarchy = ExecuteContext.Hierarchy;
	if (Hierarchy) {
		//...
		if (bFirstUpdate){//...}
		if (!CachedBone.UpdateCache(FRigElementKey(Bone, ERigElementType::Bone), Hierarchy)){//...}
		else {
			switch (Space) {
                                //Global 即 Skeleten Space
				case ERigVMTransformSpace::GlobalSpace: {
					Transform = Hierarchy->GetGlobalTransform(CachedBone);
					break;
				}
                                 // LocalSpace 即 Bone Space
				case ERigVMTransformSpace::LocalSpace: {
					Transform = Hierarchy->GetLocalTransform(CachedBone);
					break;
				}
				default: {
					break;
				}
			}
		}
	}
}

关键结论: 从骨骼获取的 Translation, 或者说 bone 的基点, 其实是在 Global 空间中的.

2

FRigUnit_SphereTraceByTraceChannel_Execute()
{
	//...
	if(WorkData.Hash == Hash){//...}
	//...

	FHitResult HitResult;
	bHit = ExecuteContext.GetWorld()->SweepSingleByChannel(HitResult, ExecuteContext.ToWorldSpace(Start), ExecuteContext.ToWorldSpace(End), FQuat::Identity, CollisionChannel, CollisionShape, QueryParams);
	
	if (bHit){
		HitLocation = ExecuteContext.ToVMSpace(HitResult.ImpactPoint);
		HitNormal = ExecuteContext.GetToWorldSpaceTransform().InverseTransformVector(HitResult.ImpactNormal);
	}
	
	//...
}

关键结论:

  • Start 和 End 都被转换到了世界空间再进行计算
  • trace 的过过程是在世界空间中完成的, 计算出的 bHit 也是在世界空间中的
  • bHit 是相对偏移, 而不是HitPoint 的坐标
  • bHit 转换到 VM 空间后负值给 HitLocation
FRigVMFunction_AlphaInterp_Execute(){
        //...
	Result = ScaleBiasClamp.ApplyTo(Value, ExecuteContext.GetDeltaTime());
}

float FInputScaleBiasClamp::ApplyTo(float Value, float InDeltaTime) const
{
	float Result = Value;
	//...

	if (bInterpResult) {
		if (bInitialized) {
			const float InterpSpeed = (Result >= InterpolatedResult) ? InterpSpeedIncreasing : InterpSpeedDecreasing;
			Result = FMath::FInterpTo(InterpolatedResult, Result, InDeltaTime, InterpSpeed);
		}

		InterpolatedResult = Result;
	}
        
        // 对象构造时 bInitialized 被初始化为 false, 这里我没有贴出构造函数代码
	bInitialized = true;
	return Result;
}

关键结论:

  • 刚进入游戏第一帧
    • Result = Value; // Result 即蓝图中的 Target 变量
    • 由于 bInitialized == false, 因而不会进入if (bInitialized) {}, 即不会插值
    • InterpolatedResult = Result;
  • 从第二帧开始插值
    • 如果角色在非水平地形上移动,这里我假设是倾斜向上, 此时 Result 将更新为新的 Target, 会变小(负z方向, 距离原点更远)
    • 在 InterpolatedResult 和 Result 之间, 按一定的速度插值
    • 插值完成后令 InterpolatedResult = Result

问题

  • 什么是 VM 空间, 特点是什么? 这一点我没有搞清楚
  • 我尝试 debug blueprint, 打印出的 GetTransform - Bone 的 z 值一直都是-7, 这说明 ik_foot 的基点在Skeleton 空间中在负z 方向?
  • 我测试还是有一些问题, 虽然角色做出了弯腿的动作, 但是一只脚总是有些轻微的漂浮, 如果有能指点的大佬, 万分感谢!

image.png