原文 Automated Semi-Procedural Animation for Character Locomotion

运动插值

  • 使用 梯度带(Gradient Bands)插值 方案(原文第6章第3节
    • 每个采样点向外辐射一个Band,这个Band的weight会根据距离进行调整
    • 权重=min(1 - 相对坐标向量投影/Band在此距离下的weight, 0)
    • 采样点P(i)在当前位置P的权重H公式(公式中j等于除了i以外的其他采样点)
      • // 采样点I与P(当前)的速度方向与强度 Vector3 pointI, pointP; // 采样点I相对于当前的速度差 Vector3 ItoCurrent = pointI - pointP; float H = Mathf.Infinity; for(k = 0; k < 采样点数量; k++) { if(k == i) continue; Vector3 pointJ = point[k]; //采样点I相对于J的速度差 Vector3 IToJ = pointJ - pointI; // 1 - (I速度差 在 I向J的速度差上的投影 相对 I向J速率差 的百分比值) H = Min(H, 1 - Vector3.dot(ItoCurrent, ItoJ) / ItoJ.magnitude / ItoJ.magnitude); }
        notion image
    • 采样点I在当前点P的权重H 等于 对于任何其他采样点J中,上图演示中橙色线段相对于黄色线段的百分比 中的最小值
  • Gradient Bands in Polar Space 极坐标空间渐变带插值
    • 所有采样点的权重除了有根据距离远近产生变化以外,还应该受到与当前前进方向夹角的影响。理论上夹角越大的采样点的权重越低。比如当角色向前运动时,向左与向右移动的采样点权重应当降低。这种受夹角产生的权重无法直接的用笛卡尔坐标空间进行表示,因此在这里需要额外进行一种极坐标空间的标识(参数中包含夹角)
      • notion image
        notion image
      • 这里p指的是采样点的速度向量,包括长度(速率)与方向|p|即速率信息
      • 这里使用模长差异与角度差异来确认权重
      • 第一分量将模长差异归一化到[-1,1],j位置模长远大于i时为差异无限接近1,反之无限接近-1,两点重合时此项为0。即第一项代表速率差
      • 第二分量计算两采样点在速度方向上的弧度差,使用α控制权重大小
 

足迹预测(原文第7.3节)

  • 每段移动动画=左右脚各两个脚印(抬脚→落地)
  • 移动动画是一个循环(cycle)
  • CycleTime = (当前循环时间 - 站姿时间)的整数取余
下个足迹的时间 = 当前时间 + 动画时长 * (1 - CycleTime) 下个足迹的角色位置 = 当前角色位置 + 角色当前速度与方向 * (下个足迹的时间 - 当前时间) 下个足迹的角色旋转 = 当前角色旋转向量 * (下个足迹的时间 - 当前时间) * 当前角色旋转 下个足迹的位置 = 下个足迹的角色位置 + 下个足迹的角色旋转 * 动画站姿时对应的腿在对象空间的相对坐标 下个足迹的旋转 = 下个足迹的角色旋转 * 动画站姿时对应的腿在对象空间的相对旋转
  • 脚印落点使用引擎内的射线检测和碰撞系统以查找预测位置的地表高度和法线向量。射线由预测位置上方指定距离向正下方发送射线以检查光线与地面的交点(LegAnimator.FindGroundedBase)以计算接地的预测覆盖区位置与方向,以将覆盖区放置于地面上并地面平齐

应用脚部轨迹需要完成以下内容:

  1. 应用原始飞行进度。使用标准化脚踏板轨迹的进度,以保持与原始运动相同的脚踏板加速和减速
  1. 角色跟随轨迹曲线。向直线脚基轨迹添加偏移量,使其跟随角色行走的曲线
  1. 基于地面切线的提升。以合适的弧度抬起脚基,使其不会与(不平坦的)地面相交
  1. 提升至支撑脚的高度。如果支撑腿或腿站在比提升脚更高的高面上,则以弧形将提升脚抬起至相同的高度
  1. 应用原始的抬起和侧向动作。在到目前为止计算的轨迹之上,从标准化的脚基轨迹添加侧向和抬起运动,以保持对原始运动的忠实

2. 脚抬起时需要进行偏移以避免腿交叉

notion image
在脚部于空中飞行时,增加一个偏移向量。该向量是当前站姿在世界空间中的位置相对于该腿从上一个足迹到下一个足迹的线性插值的位置。恒定速度行走时(速度方向与速率都不变)该偏移量始终为0。但是非恒定速度(方向改变或者速率改变)给予一个平滑权重函数
notion image
(红色箭头是偏移方向的方向)
使用前后脚中间过渡动画时间归一化后(前脚起步是0后脚落地是1)的z轴得到的动画进度百分比t,用sin(t*PI)的方式生成飞行弧度权重,以保证开始和结果的权重是0且中间过程过渡平滑
飞行弧度权重flightProgressionArc = sin( 腿归一化飞行坐标的前向移动leg.normalizedFlightPosition.z * PI)
该权重在悬空进度正好一半的时候拥有最大影响力
站姿位置_世界坐标stancePositionInWorld = 角色坐标characterPosition + 角色旋转chracterRotation * 腿站姿_局部坐标leg.stancePosition; 插值足迹位置interpolatedFootprintPosition = lerp(前一个足迹位置prevFootprintPosition,后一个足迹位置nextFootprintPosition,cycleTime); 偏移量offset = 站姿位置_世界坐标 - 插值足迹位置 脚部位置footbasePosition += 偏移量offset * 飞行弧度权重flightProgressionArc
 

3. 基于地面切线提升

前后脚落地的平面向空中延申,与前后脚位置中点向正上方的向量相交,会各产生一个点(下图中两个h),取两者中的更高点以确认曲线如何变化(弧度强度)
notion image
弧度强度 = 最高点h与足迹中点mid的距离 * 2 / PI
脚部位置footbasePosition += 飞行弧度权重flightProcessionArc * 竖直向上方向 * 弧度强度arcMagnitude
即为脚部在轨迹上按弧度进行抬升

4. 提升至脚支撑的高度

当多只脚处于不同高度的平台时,重心一定是有偏向的。更远离重心的脚会更低的承受对象重量,可以比原始动作有更多的弯曲变化
  1. 基于角色速度与旋转生成前后脚印对应的切线方向(包含强度)
    1. 角色空间速度velocityInCharacterSpace = inverse(角色旋转characterRotation)* 世界空间速度velocity
    2. 角色空间朝向directionInCharacterSpace = 角色空间速度在角色空间平面投影的标准化projectedOntoGround(velocityInCharacterSpace).normalized
    3. 上一步切线prevFootprintTangent=上一步脚印的角色旋转leg.prevFootprintCharacterRotation * 角色空间朝向directionInCharacterSpace * 循环距离cycleDistance
    4. 下一步切线nextFootprintTangent=下一步脚印的角色旋转leg.nextFootprintCharacterRotation * 角色空间朝向directionInCharacterSpace * 循环距离cycleDistance
  1. 生成使用SCurve = -cos(pi * strideTime)/2 + 0.5控制过渡
    1. 这其中strideTime = lerp(liftTime, landTime, cycleTime)
      liftTime是脚抬起时间点在动画循环中的百分比位置,landTime是脚落地时间点在动画循环百分比中的位置,cycleTime的取值范围是[0,1]
      近似smoothstep的功能,目的是得到更加平滑的曲线变化
      notion image
  1. 腿的平地高度leg.groundHeightPoint = lerp(上一步位置leg.prevFootprintPosition + 上一步切线prevFootprintTangent * 循环时间cycleTime, 下一步位置leg.nextFootprintPosition + 下一步切线nextFootprintTangent *(cycleTime-1), SCurve)
    1. 这里由于上下步切线方向大体都是朝向角色面向方向,而下一步实际上在该计算中属于未达到的状态。因此腿的平地高度结果在混合时应由下一步逆推,于是下一步切线所乘cycleTime-1的结果范围应当在[-1,0]之间(下一步位置向后)
  1. 腿的平地高度leg.groundHeightPoint += 角色旋转characterRotation * -1 * 腿站立状态坐标leg.stancePosition——用于修改为相对身体原点的轨迹,增加负站姿位置偏移
  1. 由于脚在初始和结束的站立状态受力,中间过程浮空,因此对于多足对象而言,每个脚l对支撑的作用hl是相对于腿部cycleTime定义的
    1. notion image
      影响力与cycletime的关系(横轴为cycleTime,左0右1,纵轴为影响力)。在站姿的影响力为1+ε,在抬脚后中途会减少到ε,最终落地回到站姿时回到1+ε
    2. 单腿影响力hl = (cos(2PI * 循环时间cycleTime)+1) / 2 + ε
    3. 其中ε是一个较小的正值(防止hl为0)
  1. 支撑地面高度supportingGroundHeightPoint = 每条腿的平地高度groundHeightPoint * 该腿的影响力hl / 影响力总和
    1. supportingGroundHeightPoint = (leg[0].groundHeightPoint * leg[0].hl + leg[1].groundHeightPoint * leg[1].hl + …) / (leg[0].hl + leg[1].hl + …)
  1. 由基础脚部位置高度向支撑地面高度插值的强度(相对于原动画的抬高后的高度) 飞行时间弧度flightTimeArc = sin(腿飞行时间leg.flightTime * PI) ,即以飞行时间的正弦弧度值作为插值标准
  1. 最终脚部高度取“抬高”后高度与原高度的更大值
    1. 抬高高度liftedHeight = lerp(脚部坐标高度leg.footbasePosition.height, 支撑地面高度supportingGroundHeightPoint.height, 飞行时间弧度flightTimeArc); if(liftedHeight > leg.footbasePosition.height) leg.footbasePosition.height = liftedHeight;

5. 应用原始的抬起和侧向动作

脚步方向stepDirection = (下一步坐标leg.nextFootprintPosition - 上一步坐标leg.prevFootprintPosition).normalized
脚步侧向stepSidewaysDirection = cross(up, 脚步方向stepDirection)
脚部位置footbasePosition += 脚步侧向stepSidewaysDirection * 归一化飞行坐标的侧向移动leg.normlaizedFlightPosition.x
脚部位置footbasePosition += 上方up * 归一化飞行坐标的垂向移动leg.normlaizedFlightPosition.y

腿部调整(7.4)

臀部调整

notion image
一个移动动画执行时,以脚部位置反推
  • P点是正常动画状态下以当前踝关节位置保持动画中腿部弯曲程度反推的位置,也是期望髋部高度位置h(desired)。由原始动画中a+b向量得到的c,以新的脚踝位置为原点以c长度为半径画圆,与Q点垂直方向相交的位置得到
  • Q点是最大髋关节相对踝关节的位置,也是最大髋部高度位置h(max),由腿部完全伸展反推得到
  • h(desired)和h(max)都是存储的相对偏移量,以满足不同高度对象
  • H点是原始动画中的髋部高度(也就是脚在动画设定的平面没有上下偏移时的位置,此时由于脚部位置变化因此腿部弯曲相对原始动画存在变化),调整值以此为基准做offset
  • a+b是原始姿势(左图)髋相对于脚的初始位置,c是由于脚部偏移产生的新的髋到脚的相对位置。此时c向量由弯曲之后a向量+b向量得到
  • 找到以上点后求多足对象的 最小h(max) 最小h(desired) 和 多足平均h(desired)
  • 如何基于以上数据决定最终臀部位置
      1. 折中方案是直接用多足平均h(desired),但是会导致一些腿过度拉伸
      1. 臀部高度必须低于 最小h(max)
      1. 为了避免髋关节轨迹中产生二阶不连续性导致的不平滑运动,需要避免 多足平均h(desired)与 最小h(max)的值交叉(前者大于后者)。且以最小h(max)为最终臀部高度时,必有一条腿是完全伸展的,运动看起来更不自然
      1. 更弯曲的腿比腿拉伸通常要更不容易出问题,因此基本高度使用 最小h(desired),同事假设 最小h(desired)始终小于等于 平均h(desired)与 最小h(max)
      1. 臀部高度越接近 最小h(max)则越容易有一条腿会非常僵硬(该腿以 最小h(max)作为其最大高度),最终臀部高度越远离平均值,姿势就与原始动作差别越大
      1. 最小h(max)越高,则臀部高度越接近平均高度,则动作越自然
  • 最终偏移量计算
    • minToAvg = 平均h(desired) - 最小h(desired)
    • minToMax = 最小h(max) - 最小h(desired)
    • 最终偏移hipoffset = 最小h(desired) + (minToAvb * minToMax) / (minToAvg + minToMax)
      • notion image
        最小h(desired)最小h(max) 的跨度远大于 最小h(desired)平均h(desired) 时,髋偏移量hipoffset更接近 平均h(desired), 此时没有偏移量到达 最小h(max) 和腿完全拉直的风险,密切关注髋高度
        最小h(desired)平均h(desired) 的跨度远大于 最小h(desired) 最小h(max) 时,髋偏移量hipoffset更接近 最小h(max), 此时髋关节高度远超腿部伸展的耐受

脚部对齐

  • 人类走动会滚动双脚,先用脚跟着地和先抬起脚跟,然后平放在地面或者抬起。非人类则不一定有这种特征
  • 脚最低点必须在 脚平面Footbase 上
  • 具体顺序: 站姿阶段→ 脚滚:抬起后跟→ 飞行阶段 →脚滚:后跟落地 → 站姿阶段
  • 对齐方式
    • 基于地面对齐 —— 站姿阶段
      • 保持脚相对于地面的对齐,支撑角色重量并与地面产生最大的摩擦力
        对齐方案参考如下踝关节旋转对齐,只是没有(d)阶段
    • 基于踝关节旋转对齐 —— 脚没有接触到地面的飞行阶段
      • 调整后的动作应当寻求保持踝关节与原始动作的局部旋转, 也就是浮空状态脚相对于小腿处于自然静止状态(不是完全的仅受重力,那会变成松散的悬挂)
        notion image
        ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑基于踝关节旋转对齐流程↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
        注意这里(d)这一步的旋转,旋转值由一个变量控制,0时为站立阶段,1为飞行阶段
    • 脚滚阶段
      • 上述地面对齐与踝关节旋转对齐之间进行插值(主要是d步骤)

IK

  • 每一帧都需要从头开始执行IK调整,并忽略从前一帧中IK调整的姿势,避免路径依赖,保证某一时间点相同的输入参数始终会有相同的输出
  • IK解算器必须使用当前帧的原始未经调整的姿势作为输入(而不是上一帧调整结果作为输入),从原始动画进行采样,根据当前混合权重混合。结算器必须尝试 使调整后的姿势尽可能和原始姿势匹配,并仍然具有适合的髋部踝部约束。这使得调整后关节角度也尽可能与原始关节角度匹配
  • 要求相似的原始姿势会产生相似的调整姿势以避免不连续的结果
  • 对于人型(髋关节与踝关节之间只有两块骨骼)使用解析解(analytical solution),对于非人型使用 基元数值求解(primitive numerical solver).
  • 以上两种方案都不考虑旋转角度的约束,而只是查找尽可能接近原始运动的姿势
  • IK每帧每条腿执行两次(参考上一章节脚部对齐,需要使用踝关节旋转对齐和地面对齐两种方案各算一遍然后插值)
  • 分段在两个以上的非人型腿可以用更好的 数值解算器(numerical solver) 和 适当的约束(proper constraint) 处理

开始与停止行走(7.5)

如果提前提供示例动作,则角色只能在行走周期中过渡运动开始的时间点启停,无法在任何给定其他时间启停

方案:Decoupled Leg Cycles

  • 每条腿循环单独处理
  • 给定一个阈值,如果正在移动且下一步移动距离小于阈值则到站姿时停止。如果已经停止且下一步移动距离大于阈值则循环超过站姿时间后开始移动
  • 后台有一个隐藏的与运动周期同步的站立动画循环用于判断开始移动
 
 
Semi-Procedual Locomotion研读:总览Half-Life : Alyx 的角色Locomotion改进方案研读