프로그래밍 공부
작성일
2023. 11. 29. 15:59
작성자
WDmil
728x90

일반적인 인스턴싱과 다르게 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개이다.

728x90