필요한 개념
Model이니까 인스턴스 정보를 2차원 배열로 만들어서 텍스쳐로 보내줄 것이다.(이전에 애니메이션 2차원 행렬과 동일한 방법)
InstanceID -> Row
Bone Tranform -> Col
Model도 ModelRender를 통해 그려준다. Model에는 그릴 정보만 있다.
* 왜 2차원 배열로 넘겨줄까?
각각 1차원 배열로 넘겨도 된다.
그러나 2차원 배열로 넘기는 이유는 해당 인스턴스의 본을 수정하기 위해 사용한다.
ex) 포탑 회전 이런걸 처리하기 위해(본 transform을 수정해야한다.)
몇번 인스턴스가 몇번 위치를 가졌는지 알고싶다?
SV_InstanceID : 인스턴싱의 번호를 리턴(0번 인스턴스는 0번이나오고...)
uint InstanceID : SV_InstanceID;
원래는 Model을 100개만 깔아도 프레임이 박살이 나는데
Instancing(인스턴싱)으로 하면 속도 문제 없이(크게 프레임이 떨어지지 않음) 돌아감
모델 같은 경우는 본이 수정될 수 있다는 전제이고,
애니메이션은 따로 회전이 없다.(따로 머리를 회전시킨다던가(거의 하지 않음))
ModelRender.h
더보기
#pragma once
// Model 렌더링 코드가 들어감
class ModelRender
{
public:
ModelRender(Shader* shader);
~ModelRender();
void Update();
void Render();
public:
void ReadMesh(wstring file);
void ReadMaterial(wstring file);
Model* GetModel() { return model; }
// 그릴 개수만큼 추가
Transform* AddTransform();
Transform* GetTransform(UINT index) { return transforms[index]; }
// transforms들을 worlds Matrix에 복사해주고 worlds Matrix들을
// Gpu에 복사해줌
// UpdateTransforms() -> 전체 위치를 GPU로 보내주는 것
void UpdateTransforms();
void Pass(UINT pass);
// UpdateTransform() -> 본 정보를 계산해주기 위한 함수
// 1 : 수정할 인스턴스 번호, 2 : 수정할 본 번호, 3번 : 얼마만큼 움직여서 수정할지
// boneTransform을 수정하기 위한 함수
void UpdateTransform(UINT instanceId, UINT boneIndex, Transform& transform);
private:
void CreateTexture();
private:
// 기존 bRead도 제거됨
// 텍스쳐를 넣을거라 텍스쳐가 있느냐 없느냐에따라 읽었는지를 판단가능하다.
Shader* shader;
// Model Wrapper class
Model* model;
// Instancing을 위한
vector<Transform*> transforms;
Matrix worlds[MAX_MODEL_INSTANCE];
VertexBuffer* instanceBuffer;
// 행 - Instance
// 열 - BoneTransforms
Matrix boneTransforms[MAX_MODEL_INSTANCE][MAX_MODEL_TRANSFORMS];
// Texture로 해서 넘겨줄것
ID3D11Texture2D* texture = nullptr;
// texture를 shader에 넘겨주기 위해
ID3D11ShaderResourceView* srv;
};
ModelRender.Cpp
더보기
#include "Framework.h"
#include "ModelRender.h"
ModelRender::ModelRender(Shader* shader)
: shader(shader)
{
model = new Model();
for (UINT i = 0; i < MAX_MODEL_INSTANCE; i++)
D3DXMatrixIdentity(&worlds[i]);
instanceBuffer = new VertexBuffer(worlds, MAX_MODEL_INSTANCE, sizeof(Matrix), 1, true);
}
ModelRender::~ModelRender()
{
SafeDelete(model);
for (Transform* transform : transforms)
SafeDelete(transform);
SafeDelete(instanceBuffer);
SafeRelease(texture);
SafeRelease(srv);
}
void ModelRender::Update()
{
// 텍스쳐로 따로 처리할 것이다.
if (texture == NULL)
{
for (ModelMesh* mesh : model->Meshes())
mesh->SetShader(shader);
CreateTexture();
}
for (ModelMesh* mesh : model->Meshes())
mesh->Update();
}
void ModelRender::Render()
{
instanceBuffer->Render();
// 그릴 개수가 넘어감(instancing 실제 그릴 개수)
for (ModelMesh* mesh : model->Meshes())
mesh->Render(transforms.size());
}
void ModelRender::ReadMesh(wstring file)
{
model->ReadMesh(file);
}
void ModelRender::ReadMaterial(wstring file)
{
model->ReadMaterial(file);
}
void ModelRender::Pass(UINT pass)
{
for (ModelMesh* mesh : model->Meshes())
mesh->Pass(pass);
}
void ModelRender::CreateTexture()
{
D3D11_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));
// 16 바이트씩 쪼개야 하니까(Matrix는 64바이트니까)
desc.Width = MAX_MODEL_TRANSFORMS * 4;
desc.Height = MAX_MODEL_INSTANCE;
// 1장이면 된다.
desc.ArraySize = 1;
// 한 픽셀이 16바이트(최대값)
desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
// Cpu에서 쓰고 GPU에서 읽어야 하니까(즉 데이터가 필요할때, map걸어서 보내줄려고)
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
Matrix bones[MAX_MODEL_TRANSFORMS];
// Global 행렬을 저장, Parent하고 계산된 world 행렬
for (UINT i = 0; i < MAX_MODEL_INSTANCE; i++)
{
for (UINT b = 0; b < model->BoneCount(); b++)
{
ModelBone* bone = model->BoneByIndex(b);
Matrix parent;
int parentIndex = bone->ParentIndex();
// 부모가 없다는 건 root이다.
if (parentIndex < 0)
D3DXMatrixIdentity(&parent);
else
parent = bones[parentIndex];
Matrix matrix = bone->Transform();
bones[b] = parent;
// 부모랑 곱해서 global을 만들어서 저장한다.(부모의 영향을 자식 본이 받아야 하니까)
boneTransforms[i][b] = matrix * bones[b];
}//for(b)
}//for(i)
// 1장만 할꺼라서 여러장 만들 필요 x
D3D11_SUBRESOURCE_DATA subResource;
subResource.pSysMem = boneTransforms;
// 한 줄의 크기
subResource.SysMemPitch = MAX_MODEL_TRANSFORMS * sizeof(Matrix);
// 한 면의 크기
subResource.SysMemSlicePitch = MAX_MODEL_TRANSFORMS * sizeof(Matrix) * MAX_MODEL_INSTANCE;
Check(D3D::GetDevice()->CreateTexture2D(&desc, &subResource, &texture));
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
srvDesc.Format = desc.Format;
Check(D3D::GetDevice()->CreateShaderResourceView(texture, &srvDesc, &srv));
// mesh에 srv 정보를 넘겨줌(mesh가 실제로 드로잉하니까)
for (ModelMesh* mesh : model->Meshes())
mesh->TransformsSRV(srv);
}
Transform* ModelRender::AddTransform()
{
Transform* transform = new Transform();
transforms.push_back(transform);
return transform;
}
void ModelRender::UpdateTransforms()
{
// memcpy가 조금 더 빠르다.
for (UINT i = 0; i < transforms.size(); i++)
memcpy(worlds[i], transforms[i]->World(), sizeof(Matrix));
// D3D11_SUBRESOURCE_DATA를 이용해서 GPU에 값 복사해줌
D3D11_MAPPED_SUBRESOURCE subResource;
D3D::GetDC()->Map(instanceBuffer->Buffer(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
{
// 어차피 개수가 안되면 안그려지는 거니까(최대값 만큼 복사)
// 최대 한번에 복사가 빠름(100개 그려질거 어차피 최대는 500이어도 그려질 부분만 그려짐)
memcpy(subResource.pData, worlds, sizeof(Matrix) * MAX_MESH_INSTANCE);
}
D3D::GetDC()->Unmap(instanceBuffer->Buffer(), 0);
}
void ModelRender::UpdateTransform(UINT instanceId, UINT boneIndex, Transform& transform)
{
Matrix destMatrix = transform.World();
ModelBone* bone = model->BoneByIndex(boneIndex);
// 원래 있던 bone에 다가 destMatrix 곱한다.
boneTransforms[instanceId][boneIndex] = destMatrix * boneTransforms[instanceId][boneIndex];
// 자식들과 부모 자식 관계 맺어줌
int tempBoneIndex = boneIndex;
for (ModelBone* child : bone->Childs())
{
Matrix parent = boneTransforms[instanceId][boneIndex];
Matrix invParent;
D3DXMatrixInverse(&invParent, NULL, &parent);
tempBoneIndex++;
Matrix temp = boneTransforms[instanceId][tempBoneIndex] * invParent;
boneTransforms[instanceId][tempBoneIndex] = temp * destMatrix * parent;
}
// map으로 전체 크기만큼 shader에 밀어줌
D3D11_MAPPED_SUBRESOURCE subResource;
D3D::GetDC()->Map(texture, 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
{
memcpy(subResource.pData, boneTransforms, MAX_MODEL_INSTANCE * MAX_MODEL_TRANSFORMS * sizeof(Matrix));
}
D3D::GetDC()->Unmap(texture, 0);
}
ModelDemo.h
더보기
#pragma once
#include "Systems/IExecute.h"
class ModelDemo : public IExecute
{
public:
virtual void Initialize() override;
virtual void Ready() override {};
virtual void Destroy() override {};
virtual void Update() override;
virtual void PreRender() override {};
virtual void Render() override;
virtual void PostRender() override {};
virtual void ResizeScreen() override {};
private:
void AirPlane();
void Tower();
// 탱크 모델을 불러들임
void Tank();
private:
Shader* shader;
ModelRender* airplane = nullptr;
ModelRender* tower = nullptr;
ModelRender* tank = nullptr;
};
ModelDemo.cpp
더보기
#include "stdafx.h"
#include "ModelDemo.h"
#include "Converter.h"
void ModelDemo::Initialize()
{
Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
Context::Get()->GetCamera()->Position(1, 36, -85);
shader = new Shader(L"52_InstancingModel.fx");
AirPlane();
Tower();
Tank();
}
void ModelDemo::Update()
{
if (airplane != nullptr) airplane->Update();
if (tower != nullptr) tower->Update();
if (tank != nullptr) tank->Update();
}
void ModelDemo::Render()
{
if (airplane != nullptr)
airplane->Render();
if (tower != nullptr)
tower->Render();
if (tank != nullptr)
tank->Render();
}
void ModelDemo::AirPlane()
{
airplane = new ModelRender(shader);
airplane->ReadMesh(L"B787/Airplane");
airplane->ReadMaterial(L"B787/Airplane");
for (float x = -50; x <= 50; x += 2.5f)
{
Transform* transform = airplane->AddTransform();
transform->Position(Vector3(x, 0.0f, 2.5f));
transform->RotationDegree(0, Math::Random(-180.0f, 180.0f), 0);
transform->Scale(0.00025f, 0.00025f, 0.00025f);
}
airplane->UpdateTransforms();
// shader에서 0번이 mesh, 1번이 model
airplane->Pass(1);
}
void ModelDemo::Tower()
{
tower = new ModelRender(shader);
tower->ReadMesh(L"Tower/Tower");
tower->ReadMaterial(L"Tower/Tower");
for (float x = -50; x <= 50; x += 2.5f)
{
Transform* transform = tower->AddTransform();
transform->Position(Vector3(x, 0.0f, 7.5f));
transform->RotationDegree(0, Math::Random(-180.0f, 180.0f), 0);
transform->Scale(0.003f, 0.003f, 0.003f);
}
tower->UpdateTransforms();
tower->Pass(1);
}
void ModelDemo::Tank()
{
tank = new ModelRender(shader);
tank->ReadMesh(L"Tank/Tank");
tank->ReadMaterial(L"Tank/Tank");
for (float x = -50; x <= 50; x += 2.5f)
{
Transform* transform = tank->AddTransform();
transform->Position(Vector3(x, 0.0f, 5.0f));
transform->RotationDegree(0, Math::Random(-180.0f, 180.0f), 0);
transform->Scale(0.1f, 0.1f, 0.1f);
}
tank->UpdateTransforms();
tank->Pass(1);
}
52_InstancingModel.fx
더보기
#include "00_Global.fx"
#include "00_Light.fx"
#include "00_Render.fx"
/*
00_Render.fx 파일에 포함되어 있다.
struct VertexModel
{
float4 Position : Position;
float2 Uv : Uv;
float3 Normal : Normal;
float3 Tangent : Tangent;
float4 BlendIndices : BlendIndices;
float4 BlendWeights : BlendWeights;
// Inst라는 키워드가 반드시 앞에 붙어야 한다.
matrix Transform : InstTransform; // Instancing
// SV 키워드는 우리가 시스템에 알려주거나, 시스템이 우리에게 데이터를 넘겨줄때
// SV_InstanceID : 얘가 우리에게 Instance id를 리턴해줌(현재 그리는 애의 인스턴스를 알 수 있다.)
uint InstanceID : SV_InstanceID;
};
// 지금 인스턴스는 하나만 보내지만, 추후 애니메이션도 여기다 보낼 것이어서
// 애니메이션도 이것을 쓸거여서 Array해줌
Texture2DArray TransformsMap;
#define MAX_MODEL_TRANSFORMS 250
cbuffer CB_Bone
{
// bone 행렬 자체가 texture로 넘어와서 얘가 필요 없다.
//matrix BoneTransforms[MAX_MODEL_TRANSFORMS];
uint BoneIndex;
};
void SetModelWorld(inout matrix world, VertexModel input)
{
// 이전에 world는 이렇게 계산해왔다.
//World = mul(BoneTransforms[BoneIndex], World);
// 이번에는 texture로 보내와서 texture로 쪼개야 한다.
float4 m0 = TransformsMap.Load(int4(BoneIndex * 4 + 0, input.InstanceID, 0, 0));
float4 m1 = TransformsMap.Load(int4(BoneIndex * 4 + 1, input.InstanceID, 0, 0));
float4 m2 = TransformsMap.Load(int4(BoneIndex * 4 + 2, input.InstanceID, 0, 0));
float4 m3 = TransformsMap.Load(int4(BoneIndex * 4 + 3, input.InstanceID, 0, 0));
matrix transform = matrix(m0, m1, m2, m3);
// transform : 본의 행렬
// input.Transform : global에서 그려질 애(인스턴스 world)
world = mul(transform, input.Transform);
}
// 정점 셰이더를 묶어서 사용할 것이다.
MeshOutput VS_Model(VertexModel input)
{
MeshOutput output;
SetModelWorld(World, input);
VS_GENERATE
return output;
}
*/
float4 PS(MeshOutput input) : SV_Target
{
float3 normal = normalize(input.Normal);
float3 light = -GlobalLight.Direction;
return DiffuseMap.Sample(LinearSampler, input.Uv) * dot(light, normal);
}
technique11 T0
{
// 왜 이렇게 사용하냐? Model만 하면되지?
// VertexShader는 고정시키고 PixelShader만 작업하기 위함
// 나중에 pixelshader에 공통적인 효과를 적용시킬라고
// 좀 뒤에 설명
P_VP(P0, VS_Mesh, PS)
P_VP(P1, VS_Model, PS)
}
결과
'DirectX11 3D > 기본 문법' 카테고리의 다른 글
<DirectX11 3D> 56 - Thread(스레드) (0) | 2022.02.23 |
---|---|
<DirectX11 3D> 53 - Instancing(Animation) (0) | 2022.02.22 |
<DirectX11 3D> 51 - Instancing(Mesh) (0) | 2022.02.22 |
<DirectX11 3D> 50 - Instancing(인스턴싱) (0) | 2022.02.21 |
<DirectX11 3D> 49 - Animation(Blending) (0) | 2022.02.21 |