[DirectX12学习笔记] 四元组

四元组


这篇文章内容将比较偏数学,为方便快速复习,这里只列出关键的结论。

四元组简介

四元组类似于一个复数,有实部和虚部,只不过四元组的虚部是个矢量,因此总共有四维,一般来说我们把矢量放在前面,也就是说存在XMFLOAT4里面的时候,前三维是虚部,最后一维是实部。

运算的定义
在这里插入图片描述
乘法可以写成矩阵形式
在这里插入图片描述

注意因为涉及叉乘,左手系和右手系里的四元组不一样。

乘法和矩阵乘法一样,有结合律没有交换律。

单位四元组(0,0,0,1),纯四元组 ( v , 0 ) (\vec{v},0)

共轭

在这里插入图片描述

共轭的性质
在这里插入图片描述

模长的定义就是二阶范数

在这里插入图片描述

求逆

在这里插入图片描述

逆的性质
在这里插入图片描述

单位四元组

在这里插入图片描述
可以写成
在这里插入图片描述

旋转操作符

复数相乘的时候,模长相乘,辐角相加。乘单位复数相当于作旋转操作。
四元组也类似,假如我们有单位四元组 q = ( u , \bf{q} = (\bf{u}, w ) ) ,有 q 1 = q \bf{q}^{-1}=q^* ,假如p是一个三维空间中的点或矢量,我们把p看作一个纯四元组,即(v,0),考虑如下计算
在这里插入图片描述
其中
在这里插入图片描述
相当于对着 n \vec{n} 这根轴旋转了2θ。
我们可以这样构造q
在这里插入图片描述
这样就是绕 n \vec{n} 轴旋转θ角(左手系)。

旋转操作符转矩阵:
在这里插入图片描述
旋转矩阵转旋转操作符:
在这里插入图片描述
在这里插入图片描述

旋转操作符复合
在这里插入图片描述

四元组插值

定义点乘
在这里插入图片描述

插值
在这里插入图片描述
可以看出这种spherical插值不是线性的,但是对角度而言是匀速的插值,相比之下,(1-t)a+tb这种线性插值再归一化得到的结果是不匀速的旋转,会先加速再减速。

旋转操作符有这么一个性质
在这里插入图片描述
即负的q也会得到同样的旋转结果,然而这个过程是不同的
在这里插入图片描述
负的q旋转是绕 n \vec{n} 轴转 2 π θ 2\pi-\theta 度,因此q和-q旋转的方向不同。
在这里插入图片描述
我们要怎样判断哪个近呢?我们计算两个四元向量之差的模长就能知道哪个更近了,如果 a + b 2 < a b 2 ||a+b||^2<||a-b||^2 ,那么-b就比b离a更近,我们选-b作为旋转四元组就能走最短路径插值。

插值的代码实现:

// Linear interpolation (for small theta).
public static Quaternion LerpAndNormalize(Quaternion p, Quaternion q, float s)
{
	// Normalize to make sure it is a unit quaternion.
	return Normalize((1.0f - s)*p + s*q);
}
public static Quaternion Slerp(Quaternion p, Quaternion q, float s)
{
	// Recall that q and -q represent the same orientation, but
	// interpolating between the two is different: One will take the
	// shortest arc and one will take the long arc. To find
	// the shortest arc, compare the magnitude of p-q with the
	// magnitude p-(-q) = p+q.
	if(LengthSq(p-q) > LengthSq(p+q))
		q = -q;
	float cosPhi = DotP(p, q);
	// For very small angles, use linear interpolation.
	if(cosPhi > (1.0f - 0.001))
		return LerpAndNormalize(p, q, s);
	// Find the angle between the two quaternions.
	float phi = (float)Math.Acos(cosPhi);
	float sinPhi = (float)Math.Sin(phi);
	// Interpolate along the arc formed by the intersection of the 4D
	// unit sphere and the plane passing through p, q, and the origin of
	// the unit sphere.
	return ((float)Math.Sin(phi*(1.0-s))/sinPhi)*p +
	((float)Math.Sin(phi*s)/sinPhi)*q;
}

DX中已经有了四元组相关的接口给我们调用,下面列出一些常用的

// Returns the quaternion dot product Q1·Q2.
XMVECTOR XMQuaternionDot(XMVECTOR Q1, XMVECTOR Q2);
// Returns the identity quaternion (0, 0, 0, 1).
XMVECTOR XMQuaternionIdentity();
// Returns the conjugate of the quaternion Q.
XMVECTOR XMQuaternionConjugate(XMVECTOR Q);
// Returns the norm of the quaternion Q.
XMVECTOR XMQuaternionLength(XMVECTOR Q);
// Normalizes a quaternion by treating it as a 4D vector.
XMVECTOR XMQuaternionNormalize(XMVECTOR Q);
// Computes the quaternion product Q1Q2.
XMVECTOR XMQuaternionMultiply(XMVECTOR Q1, XMVECTOR Q2);
// Returns a quaternions from axis-angle rotation representation.
XMVECTOR XMQuaternionRotationAxis(XMVECTOR Axis, FLOAT Angle);
// Returns a quaternions from axis-angle rotation representation, where the axis
// vector is normalized—this is faster than XMQuaternionRotationAxis.
XMVECTOR XMQuaternionRotationNormal(XMVECTOR NormalAxis,FLOAT Angle);
// Returns a quaternion from a rotation matrix.
XMVECTOR XMQuaternionRotationMatrix(XMMATRIX M);
// Returns a rotation matrix from a unit quaternion.
XMMATRIX XMMatrixRotationQuaternion(XMVECTOR Quaternion);
// Extracts the axis and angle rotation representation from the quaternion Q.
VOID XMQuaternionToAxisAngle(XMVECTOR *pAxis, FLOAT *pAngle, XMVECTOR Q);
// Returns slerp(Q1, Q2, t)
XMVECTOR XMQuaternionSlerp(XMVECTOR Q0, XMVECTOR Q1, FLOAT t);

旋转动画demo

接下来实现一个简单的动画demo。

我们先定义一下骨骼动画和关键帧

struct Keyframe
{
	Keyframe();
	~Keyframe();

    float TimePos;
	DirectX::XMFLOAT3 Translation;
    DirectX::XMFLOAT3 Scale;
    DirectX::XMFLOAT4 RotationQuat;
};

struct BoneAnimation
{
	float GetStartTime()const;
	float GetEndTime()const;

    void Interpolate(float t, DirectX::XMFLOAT4X4& M)const;

	std::vector<Keyframe> Keyframes; 	

};

实现骨骼动画的时候,我们在两个关键帧之间,scale和trans用线性插值,rotation用四元组的spherical插值。

骨骼动画的插值实现如下,给定时间t,输出世界矩阵M

void BoneAnimation::Interpolate(float t, XMFLOAT4X4& M)const
{
	if( t <= Keyframes.front().TimePos )
	{
		XMVECTOR S = XMLoadFloat3(&Keyframes.front().Scale);
		XMVECTOR P = XMLoadFloat3(&Keyframes.front().Translation);
		XMVECTOR Q = XMLoadFloat4(&Keyframes.front().RotationQuat);

		XMVECTOR zero = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
		XMStoreFloat4x4(&M, XMMatrixAffineTransformation(S, zero, Q, P));
	}
	else if( t >= Keyframes.back().TimePos )
	{
		XMVECTOR S = XMLoadFloat3(&Keyframes.back().Scale);
		XMVECTOR P = XMLoadFloat3(&Keyframes.back().Translation);
		XMVECTOR Q = XMLoadFloat4(&Keyframes.back().RotationQuat);

		XMVECTOR zero = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
		XMStoreFloat4x4(&M, XMMatrixAffineTransformation(S, zero, Q, P));
	}
	else
	{
		for(UINT i = 0; i < Keyframes.size()-1; ++i)
		{
			if( t >= Keyframes[i].TimePos && t <= Keyframes[i+1].TimePos )
			{
				float lerpPercent = (t - Keyframes[i].TimePos) / (Keyframes[i+1].TimePos - Keyframes[i].TimePos);

				XMVECTOR s0 = XMLoadFloat3(&Keyframes[i].Scale);
				XMVECTOR s1 = XMLoadFloat3(&Keyframes[i+1].Scale);

				XMVECTOR p0 = XMLoadFloat3(&Keyframes[i].Translation);
				XMVECTOR p1 = XMLoadFloat3(&Keyframes[i+1].Translation);

				XMVECTOR q0 = XMLoadFloat4(&Keyframes[i].RotationQuat);
				XMVECTOR q1 = XMLoadFloat4(&Keyframes[i+1].RotationQuat);

				XMVECTOR S = XMVectorLerp(s0, s1, lerpPercent);
				XMVECTOR P = XMVectorLerp(p0, p1, lerpPercent);
				XMVECTOR Q = XMQuaternionSlerp(q0, q1, lerpPercent);

				XMVECTOR zero = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
				XMStoreFloat4x4(&M, XMMatrixAffineTransformation(S, zero, Q, P));

				break;
			}
		}
	}
}

再来看主程序
主程序的构造里面先定义好骨骼动画的几个关键帧。

QuatApp::QuatApp(HINSTANCE hInstance)
    : D3DApp(hInstance)
{
    DefineSkullAnimation();
}

void QuatApp::DefineSkullAnimation()
{
    //
    // Define the animation keyframes
    //

    XMVECTOR q0 = XMQuaternionRotationAxis(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f), XMConvertToRadians(30.0f));
    XMVECTOR q1 = XMQuaternionRotationAxis(XMVectorSet(1.0f, 1.0f, 2.0f, 0.0f), XMConvertToRadians(45.0f));
    XMVECTOR q2 = XMQuaternionRotationAxis(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f), XMConvertToRadians(-30.0f));
    XMVECTOR q3 = XMQuaternionRotationAxis(XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f), XMConvertToRadians(70.0f));

    mSkullAnimation.Keyframes.resize(5);
    mSkullAnimation.Keyframes[0].TimePos = 0.0f;
    mSkullAnimation.Keyframes[0].Translation = XMFLOAT3(-7.0f, 0.0f, 0.0f);
    mSkullAnimation.Keyframes[0].Scale = XMFLOAT3(0.25f, 0.25f, 0.25f);
    XMStoreFloat4(&mSkullAnimation.Keyframes[0].RotationQuat, q0);

    mSkullAnimation.Keyframes[1].TimePos = 2.0f;
    mSkullAnimation.Keyframes[1].Translation = XMFLOAT3(0.0f, 2.0f, 10.0f);
    mSkullAnimation.Keyframes[1].Scale = XMFLOAT3(0.5f, 0.5f, 0.5f);
    XMStoreFloat4(&mSkullAnimation.Keyframes[1].RotationQuat, q1);

    mSkullAnimation.Keyframes[2].TimePos = 4.0f;
    mSkullAnimation.Keyframes[2].Translation = XMFLOAT3(7.0f, 0.0f, 0.0f);
    mSkullAnimation.Keyframes[2].Scale = XMFLOAT3(0.25f, 0.25f, 0.25f);
    XMStoreFloat4(&mSkullAnimation.Keyframes[2].RotationQuat, q2);

    mSkullAnimation.Keyframes[3].TimePos = 6.0f;
    mSkullAnimation.Keyframes[3].Translation = XMFLOAT3(0.0f, 1.0f, -10.0f);
    mSkullAnimation.Keyframes[3].Scale = XMFLOAT3(0.5f, 0.5f, 0.5f);
    XMStoreFloat4(&mSkullAnimation.Keyframes[3].RotationQuat, q3);

    mSkullAnimation.Keyframes[4].TimePos = 8.0f;
    mSkullAnimation.Keyframes[4].Translation = XMFLOAT3(-7.0f, 0.0f, 0.0f);
    mSkullAnimation.Keyframes[4].Scale = XMFLOAT3(0.25f, 0.25f, 0.25f);
    XMStoreFloat4(&mSkullAnimation.Keyframes[4].RotationQuat, q0);
}

然后在Update里面更新骷髅头的世界矩阵

    mAnimTimePos += gt.DeltaTime();
    if(mAnimTimePos >= mSkullAnimation.GetEndTime())
    {
        // Loop animation back to beginning.
        mAnimTimePos = 0.0f;
    }

    mSkullAnimation.Interpolate(mAnimTimePos, mSkullWorld);
    mSkullRitem->World = mSkullWorld;
    mSkullRitem->NumFramesDirty = gNumFrameResources;

其他渲染的部分和以前一样。

运行结果如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43675955/article/details/85245681