Directx11教程四十二下之进行骨骼动画的渲染

前面两小节分别说明了骨骼动画的数学原理和一种骨骼动画格式M3D的读取,这一节讲解渲染的思路。


(1)渲染的思路

其实,进行骨骼动画的渲染,整体思路是:

第一,求出某个动画片段下某一个时间点下所有骨骼的FinalMatrix(由骨骼在某一帧的Pos,Scale,Quat求出)

第二,由于“第一”中渲染骨骼动画的时间点可能不在整数帧数上,比如我假设某个动画片段存在50帧画面,开始时间为0s,结束时间为2.0s,而第48帧画面的开始时间为1.8s,第49帧的画面的开始时间为1.9s,而假设你想渲染的动画时间点为1.85s,所以你想求出所有骨骼的FinalMatrix,就需要进行相应的插值,进行平滑过度。


//得到某个骨头在时间t的变换矩阵
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);

		// XMMatrixAffineTransformation的第二个参数为旋转中心	
		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);

		// XMMatrixAffineTransformation的第二个参数为旋转中心	
		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 = XMVectorLerp(Q0, Q1, lerpPercent);

				XMVECTOR zero = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);

				XMStoreFloat4x4(&M, XMMatrixAffineTransformation(S, zero, Q, P));
			}
		}

	}
}

//得到所有骨头在时间t的变换矩阵数组,boneTransform[i]代表第i根骨头在时间t的变换矩阵
void AnimationClip::Interpolate(float t, vector<XMFLOAT4X4>& boneTransform)const
{
	for (UINT i = 0; i < BoneAnimations.size(); ++i)
	{
		BoneAnimations[i].Interpolate(t, boneTransform[i]);
	}
}

(2)渲染的代码

bool GraphicsClass::RenderSkeletalCharacter(float DeltaTime)
{
	// 变换矩阵
		XMMATRIX WorldMatrix,ViewMatrix, ProjMatrix;

	//限定DeltaTime不能过大,也就是骨骼动画状态不能突变
	if (DeltaTime > 1.0f)
	{
		DeltaTime = 0.0f;
	}

	//更新第一个人物的骨骼动画时间
	mCharacterInstance1.Update(DeltaTime);

	/*---------------绘制第一个人物实例(CharacterInstance1)--------------------*/

	//第一,(更新)获取ViewMatrix(根据CameraClass的mPostion和mRotation来生成的)
	mFirstCameraClass->UpdateViewMatrix();

	//第二,获取三个变换矩阵(WorldMatrix和ProjMatrixViewMatrix来自CameraClass)
	WorldMatrix =XMLoadFloat4x4(&mCharacterInstance1.World);

	ViewMatrix = mFirstCameraClass->GetViewMatrix();

	ProjMatrix = mD3D->GetProjMatrix();

	//第三,进行渲染(人物顶点数据,索引数据,各种常量缓存,纹理资源等)
	for (UINT subset = 0; subset < mCharacterInstance1.Model->SubsetCount; ++subset)
	{
		/**************绘制人物3DMesh的每个子部分******************/
		//设置各种常量缓存资源,纹理资源
		mShaderManageClass->RenderSkeletalCharacterShader(mD3D->GetDeviceContext(), mCharacterInstance1.FinalTransforms.size(), WorldMatrix, ViewMatrix, ProjMatrix,
			 mCharacterInstance1.Model->DiffuseMapSRV[subset], mCharacterInstance1.Model->NormalMapSRV[subset], mLightClass->GetAmbientColor(), mLightClass->GetDiffuseColor(), mLightClass->GetLightDirection(), mFirstCameraClass->GetPositionXM(), &mCharacterInstance1.FinalTransforms[0], mCharacterInstance1.Model->Mat[subset]);
		
		//DrawCall调用
		mCharacterInstance1.Model->ModelMesh.Draw(mD3D->GetDeviceContext(), subset);
	}
	
	return true;
}


SkeletalCharacterShader.fx

Texture2D DiffuseMap:register(t0);  //漫反射纹理资源
Texture2D NormalMap:register(t1);  //法线纹理资源
SamplerState SampleType:register(s0);   //采样方式

//VertexShader
cbuffer CBMatrix:register(b0)
{
	matrix World;
	matrix View;
	matrix Proj;
	matrix WorldInvTranspose;
};

cbuffer CBLight:register(b1)
{
	float4 AmbientLight;
	float4 DiffuseLight;
	float3 LightDirection;
	float pad;
}

cbuffer CBBoneTranform:register(b2)
{
	matrix BoneTranforms[80];
}

cbuffer Material : register(b3)
{
	float4 Ambient;
	float4 Diffuse;
	float4 Specular; // w = SpecPower
}


cbuffer Camera : register(b4)
{
	float3 CameraPos;
	float pad1;
}


struct VertexIn
{
	float3 PosL       : POSITION;
	float3 NormalL    : NORMAL;
	float2 Tex        : TEXCOORD;
	float4 TangentL   : TANGENT;
	float3 Weights    : WEIGHTS;
	uint4 BoneIndices : BONEINDICES;
};


struct VertexOut
{
	float4 Pos:SV_POSITION;
	float2 Tex:TEXCOORD0;
	float3 W_Normal:NORMAL;  //世界空间的法线
	float3 W_Pos:POSITION;
	float4 W_Tangent:TANGENT; //世界空间的切线
};



float3 NormalSampleToWorldSpace(float3 normalMapSample, float3 unitNormalW, float4 tangentW)
{
	//将法线贴图顶点像素颜色大小转为单位向量值
	float3 normalT = 2.0f*normalMapSample - 1.0f;

	//求出T矩阵
	float3 N = unitNormalW;
	float3 T = normalize(tangentW.xyz - dot(tangentW.xyz, N)*N);
	float3 B = tangentW.w*cross(N, T);

	//TBN世界变换矩阵(从切线空间到局部空间再到世界空间)
	float3x3 TBN = float3x3(T, B, N);

	//位于切线空间的法向量乘以TBN矩阵直接从切线空间变到世界空间
	float3 bumpedNormalW = normalize(mul(normalT, TBN));

	return bumpedNormalW;
}


VertexOut VS(VertexIn ina)
{

	VertexOut outa;

	//初始化顶点的骨头权重
	float weights[4] = { 0.0f,0.0f,0.0f,0.0f };
	weights[0] = ina.Weights.x;
	weights[1] = ina.Weights.y;
	weights[2] = ina.Weights.z;
	weights[3] = 1- ina.Weights.x- ina.Weights.y- ina.Weights.z;

	float3 PosL = float3(0.0f, 0.0f, 0.0f);
	float3 NormalL= float3(0.0f, 0.0f, 0.0f);
	float3 TangentL = float3(0.0f, 0.0f, 0.0f);

	//将顶点按骨骼权重比进行相应的骨骼变换
	for (int i = 0; i < 4; ++i)
	{
		PosL += weights[i] * mul(float4(ina.PosL, 1.0f), BoneTranforms[ina.BoneIndices[i]]).xyz;
		NormalL+= weights[i] * mul(ina.NormalL, (float3x3)BoneTranforms[ina.BoneIndices[i]]);
		TangentL+= weights[i] * mul(ina.TangentL.xyz, (float3x3)BoneTranforms[ina.BoneIndices[i]]);
	}

	//变换到齐次裁剪空间
	//outa.Pos = mul(ina.PosL, World); float3*float4X4  这个并不报错,忽略了分值w为1.0f
	outa.Pos = mul(float4(PosL, 1.0f), World);

	outa.W_Pos = outa.Pos.xyz;
	outa.Pos = mul(outa.Pos, View);
	outa.Pos = mul(outa.Pos, Proj);

	//法线变换到世界空间
	outa.W_Normal = mul(NormalL, (float3x3)WorldInvTranspose);  //此事世界逆转置矩阵的第四行本来就没啥用
	outa.W_Normal = normalize(outa.W_Normal);

	outa.Tex= ina.Tex;

	//切线变换到世界空间
	outa.W_Tangent = float4(normalize(mul(TangentL, (float3x3)World)), ina.TangentL.w);

	return outa;
}




float4 PS(VertexOut outa) : SV_Target
{
	float DiffuseFactor;
	float SpecularFactor;
	float4 DiffuseMapColor;
	float4 DiffuseColor;
	float4 AmbientColor;
	float4 SpecualrColor;
	float4 color;
	float3 NormalMapColor;

   
	DiffuseMapColor = float4(1.0f, 1.0f, 1.0f, 1.0f);

	//第一,获取采样颜色
	DiffuseMapColor = DiffuseMap.Sample(SampleType, outa.Tex);

	//对法线贴图进采样
	NormalMapColor=NormalMap.Sample(SampleType, outa.Tex).rgb;

	//利用世界空间的法线和切线求出处于法线贴图上的在世界空间的法向量
	float3 bumpNormalW = NormalSampleToWorldSpace(NormalMapColor, outa.W_Normal, outa.W_Tangent);


	//规格化灯光方向
	float3 NormalLightDir = normalize(LightDirection);

	//第二,求出DiffuseFactor
	float3 InvLightDir = -NormalLightDir;
	DiffuseFactor = saturate(dot(InvLightDir, bumpNormalW));
	 
	//第三,求出SpecularFactor
	float3 ReflectLightDir = reflect(LightDirection, bumpNormalW);

	//规格化反射光反向
	ReflectLightDir = normalize(ReflectLightDir);

	//求出像素到相机的方向
	float3 PixelToCameraDir = CameraPos - outa.W_Pos;

	//规格化PixelToCameraDir
	PixelToCameraDir = normalize(PixelToCameraDir);

	SpecularFactor = saturate(pow(dot(PixelToCameraDir, ReflectLightDir), Specular.w));

	//第四,求出color
	color = saturate(AmbientLight*Ambient + DiffuseFactor*DiffuseLight*Diffuse + SpecularFactor*DiffuseLight*Specular);

	color.a = 1.0f; 
		
	//第五,用灯光颜色调节纹理颜色

	color = color*DiffuseMapColor;


	return color;
}


程序运行GIF画面:





(3)碰到的问题

1.人物网格,乘以缩放矩阵时,注意对Z坐标进行翻转(可能是M3D文件格式的数据问题)

	//第二十二,实例化SkinModelClass
	mCharacterInstance1.Model = mCharacterModel;
	mCharacterInstance2.Model = mCharacterModel;
	mCharacterInstance1.TimePos = 0.0f;
	mCharacterInstance2.TimePos = 0.0f;
	mCharacterInstance1.ClipName = "Take1";
	mCharacterInstance2.ClipName = "Take1";
	mCharacterInstance1.FinalTransforms.resize(mCharacterModel->SkinnedData.BoneCount());
	mCharacterInstance2.FinalTransforms.resize(mCharacterModel->SkinnedData.BoneCount());

	XMMATRIX modelScale = XMMatrixScaling(0.15f, 0.15f, -0.15f); //
	XMMATRIX modelRot = XMMatrixRotationY(0);
	XMMATRIX modelOffset = XMMatrixTranslation(55.0f, 10.0f, 36.0f);
	XMStoreFloat4x4(&mCharacterInstance1.World, modelScale*modelRot*modelOffset);

	XMStoreFloat4x4(&mCharacterInstance2.World, modelScale*modelRot*modelOffset);
若不将坐标进行Z翻转(乘以负数),人物的渲染结果将出现问题,如下所示:



2.访问内存错误的问题

在写骨骼动画渲染的时候,碰上了内存访问错误问题,如下图所示:


一般这种问题的出现,是你访问了未分配内存的地址或者是访问Null指针指向的地址。

3.float3Xfloat4X4的错误情况

在写这个demo的时候。碰上一个到目前为止都没碰上的问题,就是使用float3的PosL乘以float4X4,导致图形完全无法显示,如下所示:

outa.Pos = mul(ina.PosL, World)
其中outa.Pos为float4类型, float3*float4X4按道理编译Shader会报错,但实际上并没有报错,导致了outa.Pos.w默认值为0,而让图形无法显示,这种错误有时候真的难以察觉,因为图形无法显示,连Debug VertexShader和PixelShader的机会都没有。

(4)源代码链接:




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值