필요한 개념
- Mesh는 그릴 정보(Vertex, Index)만을 남기고 렌더링 가능하도록 세팅하고
MeshRender 클래스를 만들어서 인스턴싱을 세팅해주고, Mesh를 인스턴싱으로 렌더링해준다.
- MeshRender에서 세팅해줄 거여서 각 Mesh class들은 Shader를 생성자에서 받지 않아도 된다.(MeshRender를 통해서 각 Mesh 객체들(인스턴싱으로 여러개를 찍어낼수 있도록 함)을 만들 것이다.)
========================================================================
주 조명 처리
더보기
Shader를 통합하려고 함
공통적인 값들은 /Renders/Context에가 몰아 넣음(조명(light)도 넣음)
실외 환경에서는 주가 되는 태양이나.. 이런것들은 주빛이 1개이다.(주빛이 있다고 가정)
주 빛을 Context에 넣어줌
매 프레임마다 Shader에 밀어주는 Perframe에다가 값을 똑같이 넣어줌
-> 이렇게 하면 장점 : 주 조명을 어디서든 편집이 가능하다.
ex) ImGui::SliderFloat3("Light Direction", Context::Get()->Direction(), -1, +1);
MeshRender.h
더보기
#pragma once
// 인스턴싱으로 메시를 그릴 최대 개수
#define MAX_MESH_INSTANCE 500
class MeshRender
{
public:
// 그려질 셰이더, 어떤 메시가 그려질지
MeshRender(Shader* shader, Mesh* mesh);
~MeshRender();
Mesh* GetMesh() { return mesh; }
void Update();
void Render();
void Pass(UINT val) { mesh->Pass(val); }
// 그릴 개수만큼 추가
Transform* AddTransform();
Transform* GetTransform(UINT index) { return transforms[index]; }
// transforms들을 worlds Matrix에 복사해주고 worlds Matrix들을
// Gpu에 복사해줌
void UpdateTransforms();
private:
// Render가 그릴 메시
Mesh* mesh;
// 외부에서 그릴 개수 만큼 push함
// transforms의 개수가 instancing의 개수(실제로 그려질 개수)
vector<Transform*> transforms;
// 그릴려는 것 만큼 복사를 해줌
// MAX_MESH_INSTANCE : 최대 갯수
Matrix worlds[MAX_MESH_INSTANCE];
VertexBuffer* instanceBuffer;
};
MeshRender.Cpp
더보기
#include "Framework.h"
#include "MeshRender.h"
MeshRender::MeshRender(Shader* shader, Mesh* mesh)
: mesh(mesh)
{
Pass(0);
mesh->SetShader(shader);
for (UINT i = 0; i < MAX_MESH_INSTANCE; i++)
D3DXMatrixIdentity(&worlds[i]);
// 인자에 CpuWrite를 켜준다. SRT가 변경되면 GPU로 복사가능하게 하기 위함
// 시시때때로 바뀌기 때문에, 쓰게 가능해줘야 Buffer를 수정가능하다.
instanceBuffer = new VertexBuffer(worlds, MAX_MESH_INSTANCE, sizeof(Matrix), 1, true);
}
MeshRender::~MeshRender()
{
for (Transform* transform : transforms)
SafeDelete(transform);
SafeDelete(instanceBuffer);
SafeDelete(mesh);
}
void MeshRender::Update()
{
mesh->Update();
}
void MeshRender::Render()
{
// 한번만 세팅해주면 됨
// 어차피 GPU에 복사된 상태로 렌더링에 들어가니까
instanceBuffer->Render();
mesh->Render(transforms.size());
}
Transform* MeshRender::AddTransform()
{
Transform* transform = new Transform();
transforms.push_back(transform);
return transform;
}
void MeshRender::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);
}
MeshDemo.h
더보기
#pragma once
#include "Systems/IExecute.h"
class MeshDemo : 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:
Shader* shader;
// 먼저 Material 세팅하고 MeshRender가 그릴 예정임
Material* gridMaterial;
MeshRender* grid;
Material* cubeMaterial;
MeshRender* cube;
};
MeshDemo.cpp
더보기
#include "stdafx.h"
#include "MeshDemo.h"
#include "Converter.h"
void MeshDemo::Initialize()
{
Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
Context::Get()->GetCamera()->Position(1, 36, -85);
shader = new Shader(L"51_InstancingMesh.fx");
// 그리드도 똑같은 셰이더로 그려진다.
gridMaterial = new Material(shader);
gridMaterial->DiffuseMap("White.png");
// 셰이더, 그릴려는 메시
grid = new MeshRender(shader, new MeshGrid());
// transform 추가
grid->AddTransform()->Scale(10, 1, 2);
// 하나라도 데이터가 바뀌면 Update 해줘야함
// 반드시 한번 콜 필요
grid->UpdateTransforms();
cubeMaterial = new Material(shader);
cubeMaterial->DiffuseMap(L"Box.png");
cube = new MeshRender(shader, new MeshCube());
for (float x = -50; x <= 50; x += 2.5f)
{
// 하나씩 추가
Transform* transform = cube->AddTransform();
transform->Scale(0.25f, 0.25f, 0.25f);
transform->Position(Vector3(x, 0.25f * 0.5f, 0.0f));
transform->RotationDegree(0, Math::Random(-180.0f, 180.0f), 0);
}
// 데이터 gpu 밀어줌
cube->UpdateTransforms();
}
void MeshDemo::Update()
{
grid->Update();
cube->Update();
}
void MeshDemo::Render()
{
// 텍스쳐 데이터 밀어주고
gridMaterial->Render();
// 그 정보(메터리얼)를 가지고 그림
grid->Render();
cubeMaterial->Render();
cube->Render();
}
51_InstancingMesh.fx
더보기
#include "00_Global.fx"
#include "00_Light.fx"
#include "00_Render.fx"
/*
00_Render.fx 파일에 포함되어 있다.
struct VertexMesh
{
// 0번 슬롯
float4 Position : Position;
float2 Uv : Uv;
float3 Normal : Normal;
// 1번 슬롯
// Inst가 붙은 애들을 만든 Shader 클래스에서 1번으로 취급(이후에 설명)
// world 정보가 들어가 있다.
matrix Transform : InstTransform;
};
// VertexShader의 부분은 총 3가지로 통합해서 사용한다.
////////////////////////////////////////////////////////////////////////////
// 이 매크로로 정점 셰이더를 정의할 수 있다. 자주사용하고 변하지 않는 것들
// original position 사용하는 일들이 있다.(ex) 큐브맵)
#define VS_GENERATE \
output.oPosition = input.Position.xyz;\
\
output.Position = WorldPosition(input.Position);\
output.Position = ViewProjection(output.Position);\
\
output.Normal = WorldNormal(input.Normal);\
\
output.Uv = input.Uv;
////////////////////////////////////////////////////////////////////////////
// inout : 입력도 되고 출력도 되도록 파라미터를 사용
// 아무것도 안붙은 애들은 입력용이다.
void SetMeshWorld(inout matrix world, VertexMesh input)
{
world = input.Transform;
}
// 메쉬 뿐만 아니라, 모델, 애니메이션도 이 친구로 output 시킴
// 모든 정점 셰이더가 이 방법으로 리턴됨
MeshOutput VS_Mesh(VertexMesh input)
{
MeshOutput output;
SetMeshWorld(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
{
P_VP(P0, VS_Mesh, PS)
}
00_Renderfx
더보기
// 메쉬그리는거, 모델 그리는 거 다 이쪽으로 통합, 애니메이션도
struct VertexMesh
{
// 0번 슬롯
float4 Position : Position;
float2 Uv : Uv;
float3 Normal : Normal;
// 1번 슬롯
// Inst가 붙은 애들을 만든 Shader 클래스에서 1번으로 취급(이후에 설명)
// world 정보가 들어가 있다.
matrix Transform : InstTransform;
};
// VertexShader의 부분은 총 3가지로 통합해서 사용한다.
////////////////////////////////////////////////////////////////////////////
// 이 매크로로 정점 셰이더를 정의할 수 있다. 자주사용하고 변하지 않는 것들
// original position 사용하는 일들이 있다.(ex) 큐브맵)
#define VS_GENERATE \
output.oPosition = input.Position.xyz;\
\
output.Position = WorldPosition(input.Position);\
output.Position = ViewProjection(output.Position);\
\
output.Normal = WorldNormal(input.Normal);\
\
output.Uv = input.Uv;
////////////////////////////////////////////////////////////////////////////
// inout : 입력도 되고 출력도 되도록 파라미터를 사용
// 아무것도 안붙은 애들은 입력용이다.
void SetMeshWorld(inout matrix world, VertexMesh input)
{
world = input.Transform;
}
// 메쉬 뿐만 아니라, 모델, 애니메이션도 이 친구로 output 시킴
// 모든 정점 셰이더가 이 방법으로 리턴됨
MeshOutput VS_Mesh(VertexMesh input)
{
MeshOutput output;
SetMeshWorld(World, input);
VS_GENERATE
return output;
}
////////////////////////////////////////////////////////////////////////////
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;
}
////////////////////////////////////////////////////////////////////////////
#define MAX_MODEL_KEYFRAMES 500
#define MAX_MODEL_INSTANCE 500
struct AnimationFrame
{
int Clip;
uint CurrFrame;
uint NextFrame;
float Time;
float Running;
float3 Padding;
};
struct TweenFrame
{
float TakeTime;
float TweenTime;
float RunningTime;
float Padding;
AnimationFrame Curr;
AnimationFrame Next;
};
// 묶어서 전역변수로 쓰기 위해
cbuffer CB_TweenFrame
{
TweenFrame TweenFrames[MAX_MODEL_INSTANCE];
};
struct VertexOutput
{
// SV_Position -> position0번으로 취급됨
float4 Position : SV_Position;
float3 Normal : Normal;
float2 Uv : Uv;
};
// inout : 입력, 출력용으로 사용하겠다.
// in : 입력용으로 사용하겠다. out : 출력용으로 사용하겠다.
// Animation의 world를 계산함
// world는 입력도 하고 출력도 할거라서 inout
void SetTweenWorld(inout matrix world, VertexModel input)
{
// 각 항목을 for문으로 다루기 위해 배열로 저장
float indices[4] = { input.BlendIndices.x, input.BlendIndices.y, input.BlendIndices.z, input.BlendIndices.w };
float weights[4] = { input.BlendWeights.x, input.BlendWeights.y, input.BlendWeights.z, input.BlendWeights.w };
// 0번이 현재 동작 1번이 다음동작(Tweening 때문에)
// 이후 작업 편하게 하려고 변수 선언
int clip[2];
int currFrame[2];
int nextFrame[2];
float time[2];
clip[0] = TweenFrames[input.InstanceID].Curr.Clip;
currFrame[0] = TweenFrames[input.InstanceID].Curr.CurrFrame;
nextFrame[0] = TweenFrames[input.InstanceID].Curr.NextFrame;
time[0] = TweenFrames[input.InstanceID].Curr.Time;
clip[1] = TweenFrames[input.InstanceID].Next.Clip;
currFrame[1] = TweenFrames[input.InstanceID].Next.CurrFrame;
nextFrame[1] = TweenFrames[input.InstanceID].Next.NextFrame;
time[1] = TweenFrames[input.InstanceID].Next.Time;
// 행단위로 써놨었다. 읽어들임
float4 c0, c1, c2, c3;
float4 n0, n1, n2, n3;
matrix curr = 0, next = 0;
matrix currAnim = 0;
matrix nextAnim = 0;
// 최종행렬
matrix transform = 0;
// 현재 동작 구해줌
[unroll(4)]
for (int i = 0; i < 4; i++)
{
// 가중치가 4개였다.(스키닝을 하기위한)
// Sample은 확대/축소가 일어나므로, 데이터의 변형이 일어나서
// 사용할 수 없다. 그래서 Load을 사용 -> 행열로 원본데이터를 불러옴
// int4 ? 왜 int인가? -> 마지막 w는 밉맵의 번호이다, 우리는 맵맵을 한장으로 사용하므로 0(우리는 민맵 1로 하나만 사용하니까 0을 줌)
// 4개 픽셀 했었어서 *4 곱함(x는 그렇다.)
// + 0은 각 픽셀의 0행을 위해
// 본 번호(열), 키 프레임(행), clip(면)
c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, currFrame[0], clip[0], 0)); // 1행
c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, currFrame[0], clip[0], 0)); // 2행
c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, currFrame[0], clip[0], 0)); // 3행
c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, currFrame[0], clip[0], 0)); // 4행
curr = matrix(c0, c1, c2, c3);
n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, nextFrame[0], clip[0], 0)); // 1행
n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, nextFrame[0], clip[0], 0)); // 2행
n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, nextFrame[0], clip[0], 0)); // 3행
n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, nextFrame[0], clip[0], 0)); // 4행
next = matrix(n0, n1, n2, n3);
// 이전 프레임에서 현재 프레임까지의 갑시, desc.time은 0 ~ 1 범위이므로 lerp의 보간 값으로 그대로 대입한다.
// lerp도 0 ~ 1값으로 처리하기 때문이다.
// (현재 프레임 Matrix, 다음 프레임 Matrix, 시간값(0 ~ 1))
currAnim = lerp(curr, next, time[0]);
// 다음 동작 구해줌
// flatten : 제어문법(현재는 신경쓰지 말자)
// 다음이 -1 보다 크다면(clip이 있다는 뜻) 다음 동작을 플레이 시킨다.
[flatten]
if (clip[1] > -1)
{
c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, currFrame[1], clip[1], 0)); // 1행
c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, currFrame[1], clip[1], 0)); // 2행
c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, currFrame[1], clip[1], 0)); // 3행
c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, currFrame[1], clip[1], 0)); // 4행
curr = matrix(c0, c1, c2, c3);
n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, nextFrame[1], clip[1], 0)); // 1행
n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, nextFrame[1], clip[1], 0)); // 2행
n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, nextFrame[1], clip[1], 0)); // 3행
n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, nextFrame[1], clip[1], 0)); // 4행
next = matrix(n0, n1, n2, n3);
nextAnim = lerp(curr, next, time[1]);
// TweenTime : 0 ~ 1까지 정규화되어 있다.
currAnim = lerp(currAnim, nextAnim, TweenFrames[input.InstanceID].TweenTime);
}
// 최종행렬에 가중치만 누적시키면 된다.
// 곱한걸 계속 누적시키는 것(계속 영향을 받을 수 있도록)
// 초반에 설명한 weights[i] 0.5라면 0.5만큼 curr에 곱해줌(움직여줌)
// transform : 가중치, 프레임까지 결합된 행렬
transform += mul(weights[i], currAnim);
}
// 최종행렬을 world로 변환
// transform : 애니메이션이 이동할 행렬, input.Transform : 인스턴싱의 world
world = mul(transform, input.Transform);
}
struct BlendFrame
{
uint Mode;
float Alpha;
float2 Padding;
AnimationFrame Clip[3];
};
cbuffer CB_BlendFrame
{
// s는 인스턴싱 때문에 미리 붙여놓음
BlendFrame BlendFrames[MAX_MODEL_INSTANCE];
};
void SetBlendWorld(inout matrix world, VertexModel input)
{
// 각 항목을 for문으로 다루기 위해 배열로 저장
float indices[4] = { input.BlendIndices.x, input.BlendIndices.y, input.BlendIndices.z, input.BlendIndices.w };
float weights[4] = { input.BlendWeights.x, input.BlendWeights.y, input.BlendWeights.z, input.BlendWeights.w };
// 행단위로 써놨었다. 읽어들임
float4 c0, c1, c2, c3;
float4 n0, n1, n2, n3;
matrix curr = 0, next = 0;
matrix currAnim[3];
matrix anim = 0;
// 최종행렬
matrix transform = 0;
BlendFrame frame = BlendFrames[input.InstanceID];
// 현재 동작 구해줌
[unroll(4)]
for (int i = 0; i < 4; i++)
{
[unroll(3)]
for (int k = 0; k < 3; k++)
{
c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 1행
c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 2행
c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 3행
c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 4행
curr = matrix(c0, c1, c2, c3);
n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 1행
n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 2행
n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 3행
n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 4행
next = matrix(n0, n1, n2, n3);
currAnim[k] = lerp(curr, next, frame.Clip[k].Time);
}
// 0, 1, 2 구간이 떨어짐
int clipA = (int) frame.Alpha;
int clipB = clipA + 1;
float alpha = frame.Alpha;
if (alpha >= 1.0f)
{
// 1 ~ 2구간을 가야하니까 -1을 시킴
// lerp를 하기 위해 0 ~ 1구간을 만들어야 해서 -1을 함
alpha = frame.Alpha - 1.0f;
// 2를 넘었다면 플레이 구간이 1 ~ 2이다.
if (frame.Alpha >= 2.0f)
{
clipA = 1;
clipB = 2;
}
}
anim = lerp(currAnim[clipA], currAnim[clipB], alpha);
transform += mul(weights[i], anim);
}
world = mul(transform, input.Transform);
}
MeshOutput VS_Animation(VertexModel input)
{
MeshOutput output;
if (BlendFrames[input.InstanceID].Mode == 0)
SetTweenWorld(World, input);
else
SetBlendWorld(World, input);
VS_GENERATE
return output;
}
00_Light.fx
더보기
// 조명, material 색상에 관한걸 모아놈
// Light 정보
struct LightDesc
{
float4 Ambient;
float4 Specular;
float3 Direction;
float Padding;
float3 Position;
};
cbuffer CB_Light
{
LightDesc GlobalLight;
};
// Material 정보
Texture2D DiffuseMap;
Texture2D SpecularMap;
Texture2D NormalMap;
TextureCube SkyCubeMap;
struct MaterialDesc
{
float4 Ambient;
float4 Diffuse;
float4 Specular;
float4 Emissive;
};
cbuffer CB_Material
{
MaterialDesc Material;
};
결과
'DirectX11 3D > 기본 문법' 카테고리의 다른 글
<DirectX11 3D> 53 - Instancing(Animation) (0) | 2022.02.22 |
---|---|
<DirectX11 3D> 52 - Instancing(Model) (0) | 2022.02.22 |
<DirectX11 3D> 50 - Instancing(인스턴싱) (0) | 2022.02.21 |
<DirectX11 3D> 49 - Animation(Blending) (0) | 2022.02.21 |
<DirectX11 3D> 48 - Animation(FrameLerp, Tween) (0) | 2022.02.21 |