일반적인 인스턴싱과 다르게 AnimationInstancing은 조금 다른 데이터를 더 주어야 한다.
위치 Matrix의 값과 Animation의 객체값을 따로 넣어야 한다.
먼저 HLSL을 새로 만들어준다.
ModelAnimationInstancing.hlsl을 만든다.
struct VertexInstancing
{
float4 pos : POSITION;
float2 uv : UV;
float3 normal : NORMAL;
float3 tangent : TANGENT;
float4 indices : BLENDINDICES;
// 특정 본들, 본에 꼽혀있을 때, 어떤 본에 가중치를 줄 것인지 웨이트가 들어감.
float4 weights : BLENDWEIGHTS;
matrix transform : INSTANCE_TRANSFORM;
int index : INSTANCE_INDEX;
};
//SpecularLight
#include "../VertexHeader.hlsli"
#include "../PixelHeader.hlsli"
// 꼭짓점 셰이더 함수입니다.
LightPixelInput VS(VertexInstancing input)
{
LightPixelInput output;
// 입력 꼭짓점을 월드 공간으로 변환합니다.
matrix transform = SkinWorld(input.index, input.indices, input.weights);
transform = mul(transform, input.transform);
output.pos = mul(input.pos, transform);
// 월드 공간에서의 위치를 저장합니다.
output.worldPos = output.pos;
// 뷰 공간에서의 위치를 저장합니다.
output.viewPos = invView._41_42_43;
// 위치를 뷰 공간으로 변환합니다.
output.pos = mul(output.pos, view);
// 위치를 투영 공간으로 변환합니다.
output.pos = mul(output.pos, projection);
// UV 좌표를 저장합니다.
output.uv = input.uv;
// 노멀과 탄젠트를 월드 공간으로 변환하고,
// 빈노멀(노멀과 탄젠트에 수직인 벡터)을 계산합니다.
output.normal = mul(input.normal, (float3x3) transform);
output.tangent = mul(input.tangent, (float3x3) transform);
output.binormal = cross(output.normal, output.tangent);
return output;
}
// 픽셀 셰이더 함수입니다.
float4 PS(LightPixelInput input) : SV_TARGET
{
Material material = GetMaterial(input);
float4 ambient = CalcAmbient(material);
float4 result = 0;
for (int i = 0; i < lightCount; i++)
{
if (!lights[i].isActive)
continue;
if (lights[i].type == 0)
result += CalcDirectional(material, lights[i]);
else if (lights[i].type == 1)
result += CalcPoint(material, lights[i]);
else if (lights[i].type == 2)
result += CalcSpot(material, lights[i]);
}
return ambient + result + mEmissive;
}
원래 hlsl에서 데이터를 받아오는것 대신, transform과 index를 받아온다.
transform은 데이터의 위치값을 참조하고, index는 애니메이션 데이터의 위치값을 참조한다.
transform을 동작시킬 때, index에 해당하는 transform일 경우, 해당되는 Matrial의 Aniamtion PNG를 할당하는 식 이다.
ModelAnimatorInstancing
#pragma once
class ModelAnimatorInstancing : public ModelAnimator
{
private:
// FrameBuffer 클래스: 상속된 ConstBuffer를 사용하여 프레임 데이터를 GPU에 전달하는 클래스
class FrameInstancingBuffer : public ConstBuffer
{
public:
struct Data
{
Motion motions[MAX_INSTANCE];
};
public:
FrameInstancingBuffer() : ConstBuffer(&data, sizeof(Data)) {}; // 프레임 데이터를 상속된 ConstBuffer에 전달
Data* GetData() { return &data; } // 프레임 데이터에 대한 포인터 반환
private:
Data data; // 프레임 데이터를 저장하는 구조체 인스턴스
};
public:
ModelAnimatorInstancing(string name);
~ModelAnimatorInstancing();
void Update();
void Render();
void GUIRender();
Transform* Add();
void PlayClip(UINT instanceInex, int clip, float scale = 1.0f, float takeTime = 0.1f);
Motion* GetMotion(UINT instanceIndex)
{
return &frameInstancingBuffer->GetData()->motions[instanceIndex];
}
private:
vector<Transform*> transforms;
InstanceData instanceDatas[MAX_INSTANCE];
VertexBuffer* instanceBuffer;
FrameInstancingBuffer* frameInstancingBuffer;
UINT drawCount = 0;
};
FrameInstancingBuffer 를 새로 만들어서 관리한다.
Motion데이터를 배열로 만들어서 Data로 저장한다.
Instancing은 데이터의 각 값을 정적으로 관리하는게 더 편하고 더 효율적이기 때문에 정적으로 관리한다.
(Buffer을 생성하고 그 Buffer값을 사용해야 하기 때문)
Trasforms을 여러개 관리하고. 해당되는 위치에 이동시킨 뒤, index에 해당하는 값을 대입하고 연산한다.
모션은 위치에 이동한 index데이터를 참조해서 해당되는 animation을 호출하는 형태로 되어있다.
#include "Framework.h"
ModelAnimatorInstancing::ModelAnimatorInstancing(string name)
: ModelAnimator(name)
{
SetShader(L"Instancing/ModelAnimatorInstancing.hlsl");
instanceBuffer = new VertexBuffer(instanceDatas, sizeof(InstanceData),
MAX_INSTANCE);
frameInstancingBuffer = new FrameInstancingBuffer();
}
ModelAnimatorInstancing::~ModelAnimatorInstancing()
{
delete instanceBuffer;
delete frameInstancingBuffer;
for (Transform* transform : transforms)
delete transform;
}
void ModelAnimatorInstancing::Update()
{
drawCount = 0;
FOR(transforms.size())
{
if (transforms[i]->IsActive())
{
UpdateFrame(&frameInstancingBuffer->GetData()->motions[i]);
transforms[i]->UpdateWorld();
instanceDatas[drawCount].world =
XMMatrixTranspose(transforms[i]->GetWorld());
instanceDatas[drawCount].index = i;
drawCount++;
}
}
instanceBuffer->Update(instanceDatas, drawCount);
}
void ModelAnimatorInstancing::Render()
{
instanceBuffer->Set(1);
frameInstancingBuffer->SetVS(4);
DC->VSSetShaderResources(0, 1, &srv);
for (ModelMesh* mesh : meshes)
mesh->RenderInstanced(drawCount);
}
void ModelAnimatorInstancing::GUIRender()
{
ImGui::Text("DrawCount : %d", drawCount);
for (Transform* transform : transforms)
transform->GUIRender();
}
Transform* ModelAnimatorInstancing::Add()
{
Transform* transform = new Transform();
transform->SetTag(name + "_" + to_string(transforms.size()));
transforms.push_back(transform);
return transform;
}
void ModelAnimatorInstancing::PlayClip(UINT instanceInex, int clip, float scale, float takeTime)
{
frameInstancingBuffer->GetData()->motions[instanceInex].next.clip = clip;
frameInstancingBuffer->GetData()->motions[instanceInex].next.scale = scale;
frameInstancingBuffer->GetData()->motions[instanceInex].takeTime = takeTime;
frameInstancingBuffer->GetData()->motions[instanceInex].runningTime = 0.0f;
}
각 데이터는 배열형대로 Buffer로 전달하여 사용한다.
현재 SRV값과 scail, Time 등을 Buffer로 전달하여 조작한다.
이 방법은 DrawCall을 줄이는 방법이기 때문에 오브젝트 수가 많아지면, FPS가 떨어지는건 막을 수 없다.
GPU연산량에 따라서 속도가 차이가 난다.
오브젝트 수가 400개이다.
'서울게임아카데미 교육과정 6개월 국비과정' 카테고리의 다른 글
20231201 42일차 AnimationInstancing응용2 (0) | 2023.12.01 |
---|---|
20231130 41일차 AnimationInstancing응용 (0) | 2023.11.30 |
20231128 39일차 Instancing , SkyBox (0) | 2023.11.28 |
20231127 38일차 CapsuleCollider (0) | 2023.11.27 |
20231124 37일차 ModelMatarialReImport (0) | 2023.11.24 |