第二十章 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變量和輸入。

發佈了5 篇原創文章 · 獲贊 27 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章