第二十章 Skeletal Animation

第二十章 Skeletal Animation

Skeletal animation(骨骼动画)是指互相连接的变换(骨头)组成的分层集合,以及对应的模型mesh(即骨骼的皮肤)。当这些变换随着时间变化而变化时,模型的mesh就会形成动画效果。本章,我们将会探讨skeketal animation,并开发一些系统用于支持模型动画。

Skinning

把一个skeleton映射到一个mesh的过程称为skinning(换肤)。这项工作主要由一个美术设计师完成,并涉及到与具体bone关联的顶点。一个vertex可以对应于多个bone(通常情况下最多可以有4个),每一个bone使用一个给定的数量值对vertex产生影响(比如,结合每一个bone的影响)。因此,vertex的最终坐标位置由所有对应的bones加权平均计算得到。
术语skinning还可以用于表示在运行时vertices如何进行变换。CPU skinning表示使用CPU执行vertices的变换运算,而GPU skinning则表示使用GPU执行变换运算。尽管GPU skinning对可用的bones数量有限制,但是一般情况下都比CPU skinning要快的多。列表20.1列出一个GPU skinning shader代码。
列表20.1 The SkinnedModel.fx Shader

#include "include\\Common.fxh"

#define MaxBones 60

/************* Resources *************/
cbuffer CBufferPerFrame
{
    float4 AmbientColor = { 1.0f, 1.0f, 1.0f, 0.0f };
    float4 LightColor = { 1.0f, 1.0f, 1.0f, 1.0f };
    float3 LightPosition = { 0.0f, 0.0f, 0.0f };
    float LightRadius = 10.0f;
    float3 CameraPosition;
}

cbuffer CBufferPerObject
{
    float4x4 WorldViewProjection : WORLDVIEWPROJECTION;
    float4x4 World : WORLD;
    float4 SpecularColor : SPECULAR = { 1.0f, 1.0f, 1.0f, 1.0f };
    float SpecularPower : SPECULARPOWER  = 25.0f;
}

cbuffer CBufferSkinning
{
    float4x4 BoneTransforms[MaxBones];
}

Texture2D ColorTexture;

SamplerState ColorSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

/************* Data Structures *************/

struct VS_INPUT
{
    float4 ObjectPosition : POSITION;
    float2 TextureCoordinate : TEXCOORD;
    float3 Normal : NORMAL;
    uint4 BoneIndices : BONEINDICES;
    float4 BoneWeights : WEIGHTS;	
};

struct VS_OUTPUT
{
    float4 Position : SV_Position;
    float3 Normal : NORMAL;
    float2 TextureCoordinate : TEXCOORD0;
    float3 WorldPosition : TEXCOORD1;
    float Attenuation : TEXCOORD2;
};

/************* Vertex Shader *************/

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;      

    float4x4 skinTransform = (float4x4)0;
    skinTransform += BoneTransforms[IN.BoneIndices.x] * IN.BoneWeights.x;
    skinTransform += BoneTransforms[IN.BoneIndices.y] * IN.BoneWeights.y;
    skinTransform += BoneTransforms[IN.BoneIndices.z] * IN.BoneWeights.z;
    skinTransform += BoneTransforms[IN.BoneIndices.w] * IN.BoneWeights.w;
    
    float4 position = mul(IN.ObjectPosition, skinTransform);	
    OUT.Position = mul(position, WorldViewProjection);
    OUT.WorldPosition = mul(position, World).xyz;
    
    float4 normal = mul(float4(IN.Normal, 0), skinTransform);
    OUT.Normal = normalize(mul(normal, World).xyz);
    
    OUT.TextureCoordinate = IN.TextureCoordinate;

    float3 lightDirection = LightPosition - OUT.WorldPosition;
    OUT.Attenuation = saturate(1.0f - (length(lightDirection) / LightRadius));

    return OUT;
}

/************* Pixel Shaders *************/

float4 pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 OUT = (float4)0;

    float3 lightDirection = LightPosition - IN.WorldPosition;
    lightDirection = normalize(lightDirection);

    float3 viewDirection = normalize(CameraPosition - IN.WorldPosition);

    float3 normal = normalize(IN.Normal);
    float n_dot_l = dot(normal, lightDirection);
    float3 halfVector = normalize(lightDirection + viewDirection);
    float n_dot_h = dot(normal, halfVector);

    float4 color = ColorTexture.Sample(ColorSampler, IN.TextureCoordinate);
    float4 lightCoefficients = lit(n_dot_l, n_dot_h, SpecularPower);

    float3 ambient = get_vector_color_contribution(AmbientColor, color.rgb);
    float3 diffuse = get_vector_color_contribution(LightColor, lightCoefficients.y * color.rgb) * IN.Attenuation;
    float3 specular = get_scalar_color_contribution(SpecularColor, min(lightCoefficients.z, color.w)) * IN.Attenuation;

    OUT.rgb = ambient + diffuse + specular;
    OUT.a = 1.0f;

    return OUT;
}

/************* Techniques *************/

technique11 main11
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, pixel_shader()));
    }
}


该shader只是在一个point light shader的基础上增加了对skinned models的支持。所有关于skinning的操作都在vertex shader执行,执行过程中主要是使用了新的shader变量BoneTransforms,以及vertex shader输入参数中的BoneIndices和BoneWeights变量。其中,变量BoneTransforms是一个矩阵数组,包含模型骨骼中每一个bone的变换矩阵。VS_INPUT结构体中的BoneIndices变量是一个含有4个无符号整形数的数组(uint4类型),数组中的每一个元素表示访问BoneTransforms数组的索引。此外,BoneWeight变量主要用于vertex对应多个bones时,对所有影响vertex的bones进行加权平均。

在vertex shader中产生使用以下的代码构建一个矩阵skinTransform:

float4x4 skinTransform = (float4x4)0;
skinTransform += BoneTransforms[IN.BoneIndices.x] * IN.BoneWeights.x;
skinTransform += BoneTransforms[IN.BoneIndices.y] * IN.BoneWeights.y;
skinTransform += BoneTransforms[IN.BoneIndices.z] * IN.BoneWeights.z;
skinTransform += BoneTransforms[IN.BoneIndices.w] * IN.BoneWeights.w;


如果某个bone不会对vertex产生影响,那么该bone的权重就为0,因此在计算矩阵skinTransform时就会去掉该bone的Transform值。在CPU端的应用程序中需要保证与vertex关联的所有bones的权重和为1。

然后,把vertex position与矩阵skinTransform相乘(同样,如果vertex中含有surface normal,tangent,以及binormal也需要与该矩阵相乘)。通过矩阵乘法变换之后,bone的矩阵依然位于object的局部坐标系,因此再把position与WorldVeiwProjection矩阵相乘,使得在下一个shader阶段之前把vertex从animated local space变换到homogenous clip space。
Pixel shader的代码与原始的point light pixel shader完全一样。实际上,可以把本书中所有的lighting techniques应用到skinned models中,只需要像vertex shader讨论的那样,增加一些与bone相关的shader变量和输入。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值