Vertex Texture Fetch Skinning Animation Instancing
- 현재까지 사용했던 Constant Buffer를 활용한 Skinning Animation은 한계점이 많았다. Constant Buffer를 통한 Skinning이 아닌 Texture Buffer를 활용하여 Skinning Animation을 Vertex Shader 측에서 Sampling 하여 적용하는 방식이다.
Constant Buffer Skinning Animation 단점
- Constant Buffer는 미리 크기를 지정해두어야 한다는 단점이 있다.
- Instancing을 하기 위해선 각각의 Object의 Animation Data를 담을 공간이 필요한데 모든 Object의 Animation Data를 올리는 것은 말도 안되는 일이다.
- CPU 측에서 계산한 Bone Animation Matrix를 매 프레임 Update 하는것은 비용 낭비가 심하다.
Vertex Texture Fetch Skinning Animation 장점
- 해당 Animation의 Bone Matrix를 미리 계산해두어 GPU Resource Update가 최소화 된다.
- 실질적으로 해당 Object의 현재 Animation Frame, 다음 Animation Frame, 현재 프레임 진행 시간만 Update 해주면 되어 CPU에서 GPU 측의 Copy가 줄어든다.
- 같은 Model의 Animation Buffer를 돌려 쓸 수 있으므로 Skinning Instancing 자체도 어렵지 않아진다.
// 새로 생성한 Animation Buffer 삽입..
AnimationBuffer* animationBuf = *ppResource;
animationBuf->FrameOffset = row_offset;
animationBuf->AnimationOffset.clear();
animationBuf->AnimationOffset.push_back(total_offset);
animationBuf->AnimationCount = animation->AnimationCount;
// Animation Buffer Data 설정..
std::vector<XMFLOAT3X4> animationList;
for (auto& aniList : animation->AnimList)
{
nowModelAni = aniList.second;
for (int frame = 0; frame < nowModelAni->m_TotalFrame; frame++)
{
for (int ani = 0; ani < nowModelAni->m_AnimationList.size(); ani++)
{
nowAni = nowModelAni->m_AnimationList[ani];
nowOffsetTM = offsetList[ani];
nowFrame = nowAni->m_AniData[frame];
// Animation Bone World Matrix * Bone Offset Matrix
nowFrameTM = Matrix::CreateScale(nowFrame->m_WorldScale) * Matrix::CreateFromQuaternion(nowFrame->m_WorldRotQt) * Matrix::CreateTranslation(nowFrame->m_WorldPos);
nowFrameTM = nowOffsetTM * nowFrameTM;
XMFLOAT3X4 matrix;
matrix._11 = nowFrameTM._11, matrix._12 = nowFrameTM._12; matrix._13 = nowFrameTM._13; matrix._14 = nowFrameTM._41;
matrix._21 = nowFrameTM._21, matrix._22 = nowFrameTM._22; matrix._23 = nowFrameTM._23; matrix._24 = nowFrameTM._42;
matrix._31 = nowFrameTM._31, matrix._32 = nowFrameTM._32; matrix._33 = nowFrameTM._33; matrix._34 = nowFrameTM._43;
animationList.emplace_back(std::move(matrix));
}
}
column_offset = nowModelAni->m_TotalFrame;
total_offset += row_offset * column_offset;
// Next Frame Offset Size 삽입..
if (animationBuf->AnimationOffset.size() < animation->AnimList.size())
{
animationBuf->AnimationOffset.push_back(total_offset);
}
}
D3D11_BUFFER_DESC bufferDesc;
bufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
bufferDesc.ByteWidth = total_offset * struct_size;
bufferDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
bufferDesc.CPUAccessFlags = 0;
bufferDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
bufferDesc.StructureByteStride = struct_size;
D3D11_SUBRESOURCE_DATA initData;
initData.pSysMem = &animationList[0];
initData.SysMemPitch = 0;
initData.SysMemSlicePitch = 0;
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
srvDesc.Format = DXGI_FORMAT_UNKNOWN;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = total_offset;
/// Buffer 생성..
ID3D11Buffer* buffer = nullptr;
ID3D11ShaderResourceView* srv = (ID3D11ShaderResourceView*)animationBuf->pAnimationBuf;
// 이전 Animation Buffer 제거..
RELEASE_COM(srv);
m_Result = g_Graphic->CreateBuffer(&bufferDesc, &initData, &buffer);
if (FAILED(m_Result))
{
PROFILE_RESULT(PROFILE_OUTPUT::LOG_FILE, m_Result, "[ Graphic ][ Create ][ Buffer ] '%s' FAILED!!", model->Name.c_str());
PROFILE_RESULT(PROFILE_OUTPUT::VS_CODE, m_Result, "[ Graphic ][ Create ][ Buffer ] '%s' FAILED!!", model->Name.c_str());
}
m_Result = g_Graphic->CreateShaderResourceView(buffer, &srvDesc, &srv);
if (FAILED(m_Result))
{
PROFILE_RESULT(PROFILE_OUTPUT::LOG_FILE, m_Result, "[ Graphic ][ Create ][ ShaderResourceView ] '%s' FAILED!!", model->Name.c_str());
PROFILE_RESULT(PROFILE_OUTPUT::VS_CODE, m_Result, "[ Graphic ][ Create ][ ShaderResourceView ] '%s' FAILED!!", model->Name.c_str());
}
// Animation Buffer 저장..
animationBuf->pAnimationBuf = srv;
GPU_RESOURCE_DEBUG_NAME(srv, std::string(model->Name + "_AnimationBuffer").c_str());
// Reset Resource..
buffer->Release();
- Animation Data가 Load 될 때마다 해당 Model Animation Buffer을 재생성하여 설정해준다.
- Animation Data에 필요한 Bone Offset Matrix와 해당 Animation Bone World Matrix를 미리 연산하여 Buffer에 저장한다.
- Animation Frame에 해당하는 Buffer의 Frame Offset Size도 미리 계산하여 Renderer측에 넘기기 전 해당 Frame의 Offset을 넘겨주어 Skinning Animation을 실행한다.
#ifndef SKIN_MESH
#define SKIN_MESH
#endif
#include "Input_Header.hlsli"
#include "Instance_Header.hlsli"
#include "VertexFetch_Header.hlsli"
// Bone Animation float3x4 Struct
struct BoneAnimation
{
float4 Row1; // Matrix Row1(x,y,z), Row4(x)
float4 Row2; // Matrix Row2(x,y,z), Row4(y)
float4 Row3; // Matrix Row3(x,y,z), Row4(z)
};
// Bone Matrix float3x4 -> float4x4 Convert
float4x4 LoadBoneMatrix(in BoneAnimation bone)
{
return float4x4(float4(bone.Row1.xyz, 0.0f),
float4(bone.Row2.xyz, 0.0f),
float4(bone.Row3.xyz, 0.0f),
float4(bone.Row1.w, bone.Row2.w, bone.Row3.w, 1.0f));
}
StructuredBuffer<BoneAnimation> gAnimationBuffer : register(t0);
cbuffer cbInstanceSkinMesh
{
float4x4 gView : packoffset(c0);
float4x4 gProj : packoffset(c4);
};
MeshVertexOut SkinMesh_Instance_VS(MeshVertexIn vin, MeshInstanceIn instance)
{
MeshVertexOut vout;
uint nowIndex;
uint nextIndex;
float4x4 prevBoneTM;
float4x4 nextBoneTM;
float4x4 nowBoneTM;
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)
{
nowIndex = instance.PrevAnimationIndex + vin.BoneIndices1[i];
nextIndex = instance.NextAnimationIndex + vin.BoneIndices1[i];
prevBoneTM = LoadBoneMatrix(gAnimationBuffer.Load(nowIndex));
nextBoneTM = LoadBoneMatrix(gAnimationBuffer.Load(nextIndex));
nowBoneTM = lerp(prevBoneTM, nextBoneTM, instance.FrameTime);
posL += vin.BoneWeights1[i] * mul(float4(vin.PosL, 1.0f), nowBoneTM).xyz;
normalL += vin.BoneWeights1[i] * mul(vin.NormalL, (float3x3) nowBoneTM);
tangentL += vin.BoneWeights1[i] * mul(vin.TangentL, (float3x3) nowBoneTM);
}
// Object -> World 변환..
vout.PosW = mul(instance.World, float4(posL, 1.0f)).xyz;
// World -> View 변환..
vout.PosV = mul(gView, float4(vout.PosW, 1.0f)).xyz;
// View -> Proj 변환..
vout.PosH = mul(gProj, float4(vout.PosV, 1.0f));
vout.NormalW = mul((float3x3) instance.InvWorld, normalL);
vout.NormalV = mul((float3x3) gView, vout.NormalW);
vout.TangentW = mul((float3x3) instance.World, tangentL);
vout.TangentV = mul((float3x3) gView, vout.TangentW);
vout.Tex = vin.Tex;
return vout;
}
- Instance Buffer를 통해 해당 Animation Frame의 Start Point와 Vertex Buffer를 통해 Link되 있는 Bone List의 Matrix를 가져와서 다음 Frame Animation과 보간을 실행하여 Skinning Animation Instancing을 실행한다.
생성 방식
'DirectX 11' 카테고리의 다른 글
[DirectX 11] Material Property Block (0) | 2022.05.22 |
---|---|
[DirectX 11] Rim Lighting (0) | 2022.04.25 |
[DirectX 11] Hierarchical Z-Buffer Occlusion Culling (0) | 2022.04.04 |
[DirectX 11] Fast Picking (Object ID Picking) (6) | 2022.03.29 |
[DirectX 11] Game Engine Render Resource 동기화 방식 및 Resource 관리 방식 (0) | 2022.03.16 |