MeshRenderer
MeshRenderer의 역할과 구조
MeshRenderer는 3D 게임 오브젝트를 렌더링하기 위한 핵심 컴포넌트입니다. Component 클래스를 상속받아 구현되었으며, 다음과 같은 주요 기능을 담당합니다:
3D 모델의 형태(Mesh)와 재질(Material) 정보 관리
렌더링 파이프라인 설정
렌더링 패스(RenderPass) 관리
class MeshRenderer : public Component
{
private:
// Mesh
shared_ptr<Mesh> _mesh;
shared_ptr<Material> _material;
shared_ptr<Model> _model;
RasterizerStateInfo _rasterzerStates;
vector<shared_ptr<RenderPass>> _renderPasses;
bool _isUseEnvironmentMap = false;
};
Mesh 클래스
Mesh 클래스는 3D 객체의 형태 데이터를 저장하고 관리합니다.
주요 구성 요소
class Mesh : public ResourceBase
{
public:
void CreateQuad_NormalTangent();
void CreateGrid_NormalTangent(int32 sizeX, int32 sizeZ);
void CreateCube_NormalTangent();
void CreateSphere_NormalTangent();
void CreateCylinder_NormalTangent();
private:
shared_ptr<Geometry<VertexTextureNormalTangentBlendData>> _geometry;
shared_ptr<Geometry<VertexTerrain>> _geometryForTerrain;
shared_ptr<Buffer> _buffer;
// 추가 지형 관련 데이터...
};
Geometry 클래스 - 정점 데이터 관리의 핵심
Geometry 클래스는 템플릿을 사용하여 다양한 정점 타입을 지원하는 유연한 구조로 설계되었습니다:
template <typename T>
class Geometry
{
public:
Geometry() {}
~Geometry() {}
void SetVertices(const vector<T>& vertices) { _vertices = vertices; }
void SetIndices(const vector<uint32>& indices) { _indices = indices; }
void AddVertex(const T& vertex) { _vertices.push_back(vertex); }
void AddVertices(const vector<T>& vertices) { _vertices.insert(_vertices.end(), vertices.begin(), vertices.end()); }
void AddIndex(uint32 index) { _indices.push_back(index); }
void AddIndices(const vector<uint32>& indices) { _indices.insert(_indices.end(), indices.begin(), indices.end()); }
vector<T>& GetVertices() { return _vertices; }
vector<uint32>& GetIndices() { return _indices; }
private:
vector<T> _vertices;
vector<uint32> _indices;
};
이 템플릿 구현의 핵심 특징:
타입 독립성: 템플릿 T를 통해 어떤 정점 구조체도 저장 가능
데이터 추가 방식:
단일 정점/인덱스 추가 (AddVertex, AddIndex)
벡터로 일괄 추가 (AddVertices, AddIndices)
전체 설정 (SetVertices, SetIndices)
벡터 기반 저장: 동적으로 크기가 변할 수 있는 메시 데이터 처리에 적합
다양한 정점 구조체 타입
Mesh 클래스에서는 Geometry 템플릿을 다양한 정점 타입으로 인스턴스화하여 사용합니다:
shared_ptr<Geometry<VertexTextureNormalTangentBlendData>> _geometry;
shared_ptr<Geometry<VertexTerrain>> _geometryForTerrain;
주요 정점 구조체 타입 및 용도:
struct VertexTextureData
{
Vec3 position = { 0.f, 0.f, 0.f };
Vec2 uv = { 0.f, 0.f };
static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};
struct VertexTextureNormalData
{
Vec3 position = { 0.f, 0.f, 0.f };
Vec2 uv = { 0.f, 0.f };
Vec3 normal = { 0.f, 0.f, 0.f };
VertexTextureNormalData() {};
VertexTextureNormalData(const Vec3& pos, const Vec2& texCoord, const Vec3& norm)
: position(pos), uv(texCoord), normal(norm) {}
static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};
struct VertexTextureNormalTangentData
{
Vec3 position = { 0, 0, 0 };
Vec2 uv = { 0, 0 };
Vec3 normal = { 0, 0, 0 };
Vec3 tangent = { 0, 0, 0 };
VertexTextureNormalTangentData() {};
VertexTextureNormalTangentData(const Vec3& pos, const Vec2& texCoord, const Vec3& norm, const Vec3& tan)
: position(pos), uv(texCoord), normal(norm), tangent(tan) {}
static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};
struct VertexTerrain
{
Vec3 position;
Vec2 uv;
Vec2 BoundsY;
static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};
struct VertexParticle
{
Vec3 position;
Vec3 velocity;
Vec2 size;
float age;
uint32 type;
static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};
Geometry를 활용한 메시 생성 과정
예를 들어, 구(Sphere) 메시를 생성하는 과정을 살펴보겠습니다:
void Mesh::CreateSphere_NormalTangent()
{
_geometry = make_shared<Geometry<VertexTextureNormalTangentBlendData>>();
float radius = 0.5f;
uint32 stackCount = 20;
uint32 sliceCount = 20;
vector<VertexTextureNormalTangentBlendData> vertices;
vector<uint32> indices;
// 북극점 정점
VertexTextureNormalTangentBlendData v;
v.position = Vec3(0.0f, radius, 0.0f);
v.uv = Vec2(0.5f, 0.0f);
v.normal = v.position;
v.normal.Normalize();
v.tangent = Vec3(1.0f, 0.0f, 0.0f);
vertices.push_back(v);
// 구의 각 층(stack)과 조각(slice)마다 정점 생성
float stackAngle = XM_PI / stackCount;
float sliceAngle = XM_2PI / sliceCount;
float deltaU = 1.f / static_cast<float>(sliceCount);
float deltaV = 1.f / static_cast<float>(stackCount);
// 정점 생성 로직...
// 인덱스 생성 로직...
// Geometry에 정점과 인덱스 데이터 설정
_geometry->SetVertices(vertices);
_geometry->SetIndices(indices);
// Buffer 생성
_buffer = make_shared<Buffer>();
_buffer->CreateBuffer(BufferType::VERTEX_BUFFER, vertices, 0, false, false);
_buffer->CreateBuffer(BufferType::INDEX_BUFFER, indices, 0, false, false);
}
이 과정에서 Geometry는:
구의 모든 정점과 인덱스 데이터를 저장
데이터를 Buffer 클래스에 전달하여 GPU 메모리에 업로드할 준비
Buffer 클래스 - GPU 메모리 관리의 핵심
class Buffer
{
public:
Buffer();
~Buffer();
template <typename T>
void CreateBuffer(BufferType type, vector<T> source, uint32 slot = 0, bool cpuWrite = false, bool gpuWrite = false);
template <typename T>
void CreateConstantBuffer();
template <typename T>
void CopyData(const T& data);
uint32 GetStride() { return _stride; }
uint32 GetOffset() { return _offset; }
uint32 GetSlot() { return _slot; }
ComPtr<ID3D11Buffer> GetVertexBuffer() { return _vertexBuffer; }
ComPtr<ID3D11Buffer> GetIndexBuffer() { return _indexBuffer; }
ComPtr<ID3D11Buffer> GetConstantBuffer() { return _constantBuffer; }
private:
ComPtr<ID3D11Buffer> _vertexBuffer;
ComPtr<ID3D11Buffer> _indexBuffer;
ComPtr<ID3D11Buffer> _constantBuffer = nullptr;
uint32 _stride = 0;
uint32 _offset = 0;
uint32 _slot = 0;
};
버퍼 생성 매커니즘
가장 핵심적인 메서드인 CreateBuffer 템플릿 함수를 자세히 살펴보겠습니다:
template <typename T>
void Buffer::CreateBuffer(BufferType type, vector<T> source, uint32 slot = 0, bool cpuWrite = false, bool gpuWrite = false)
{
_slot = slot;
if (type == BufferType::VERTEX_BUFFER)
{
_stride = sizeof(T);
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = (uint32)(sizeof(T) * static_cast<uint32>(source.size()));
// CPU/GPU 접근 권한에 따른 Usage 설정
if (cpuWrite == false && gpuWrite == false)
{
desc.Usage = D3D11_USAGE_IMMUTABLE; // CPU Read, GPU Read
}
else if (cpuWrite == true && gpuWrite == false)
{
desc.Usage = D3D11_USAGE_DYNAMIC; // CPU Write, GPU Read
desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
}
else if (cpuWrite == false && gpuWrite == true) // CPU Read, GPU Write
{
desc.Usage = D3D11_USAGE_DEFAULT;
}
else
{
desc.Usage = D3D11_USAGE_STAGING;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
}
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = source.data();
HRESULT hr = Graphics::GetInstance().GetDevice()->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
CHECK(hr);
}
else if (type == BufferType::INDEX_BUFFER)
{
// 인덱스 버퍼 생성 로직...
}
}
CPU/GPU 메모리 접근 제어 시스템
Buffer 클래스에서 가장 중요한 특징 중 하나는 CPU와 GPU 간의 메모리 접근 권한을 세밀하게 제어하는 기능입니다:
D3D11_USAGE_IMMUTABLE (cpuWrite=false, gpuWrite=false):
생성 후 변경 불가능한 버퍼
CPU에서 초기화 후 GPU에서 읽기만 가능
D3D11_USAGE_DYNAMIC (cpuWrite=true, gpuWrite=false):
CPU에서 자주 업데이트하는 버퍼
GPU는 읽기만 가능
D3D11_USAGE_DEFAULT (cpuWrite=false, gpuWrite=true):
GPU에서 쓰기 가능
주로 GPU-GPU 복사 작업에 사용
D3D11_USAGE_STAGING (cpuWrite=true, gpuWrite=true):
CPU-GPU 간 데이터 전송에 사용
상수버퍼 관리
template <typename T>
void Buffer::CreateConstantBuffer()
{
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
desc.ByteWidth = sizeof(T);
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
HRESULT hr = Graphics::GetInstance().GetDevice()->CreateBuffer(&desc, nullptr, _constantBuffer.GetAddressOf());
CHECK(hr);
}
template <typename T>
void Buffer::CopyData(const T& data)
{
D3D11_MAPPED_SUBRESOURCE subResource;
ZeroMemory(&subResource, sizeof(subResource));
Graphics::GetInstance().GetDeviceContext()->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
::memcpy(subResource.pData, &data, sizeof(data));
Graphics::GetInstance().GetDeviceContext()->Unmap(_constantBuffer.Get(), 0);
}
위 방식으로 생성된 상수 버퍼는 다음과 같은 데이터를 GPU에 전달하는데 사용됩니다:
변환 행렬 (월드, 뷰, 투영)
재질 속성 (ambient, diffuse, specular)
조명 데이터
애니메이션 키프레임 정보
카메라 정보
Material 클래스 - 텍스처와 셰이더 관리
Material 클래스는 3D 객체의 시각적 특성을 정의하고 관리하는 핵심 리소스입니다. 이 클래스는 객체가 빛을 어떻게 반사하고, 어떤 표면 질감을 가지며, 어떤 시각적 효과를 적용할지 결정합니다.
class Material : public ResourceBase
{
private:
// 셰이더와 텍스처 참조
shared_ptr<Shader> _shader;
shared_ptr<Texture> _texture;
shared_ptr<Texture> _normalMap;
shared_ptr<Texture> _diffuseMap;
shared_ptr<Texture> _specularMap;
// 재질 속성 관리
shared_ptr<Buffer> _materialBuffer;
MaterialDesc _materialDesc;
// 환경 매핑과 큐브맵 관련
ComPtr<ID3D11ShaderResourceView> _cubeMapSRV;
wstring _materialName = L"None";
};
Material 클래스는 다음과 같은 핵심 요소들을 관리합니다:
셰이더 참조: 객체를 어떻게 렌더링할지 결정하는 셰이더 프로그램
텍스처 컬렉션: 객체의 표면 질감을 정의하는 다양한 텍스처 맵
재질 속성: 주변광(ambient), 확산광(diffuse), 반사광(specular) 등 재질의 광학적 특성
상수 버퍼: 이러한 속성을 셰이더에 전달하기 위한 버퍼
Material 클래스의 주요 메서드들:
// 셰이더 및 텍스처 설정
void SetShader(shared_ptr<Shader> shader) { _shader = shader; }
void SetTexture(shared_ptr<Texture> texture) { _texture = texture; }
void SetNormalMap(shared_ptr<Texture> normal) { _normalMap = normal; }
void SetDiffuseMap(shared_ptr<Texture> diffuse) { _diffuseMap = diffuse; }
void SetSpecularMap(shared_ptr<Texture> specular) { _specularMap = specular; }
// 재질 속성 설정 및 GPU 전송
void SetMaterialDesc(MaterialDesc materialDesc) { _materialDesc = materialDesc; }
void PushMaterialDesc(); // 상수 버퍼를 통해 재질 속성을 GPU에 전송
// 특수 텍스처 생성
void CreateCubeMapTexture(shared_ptr<Texture> textureArray[6]);
void CreateEnvironmentMapTexture(shared_ptr<GameObject> gameObject);
Material 객체는 렌더링 과정에서 셰이더와 텍스처를 바인딩하고, 재질 속성을 상수 버퍼를 통해 GPU에 전달합니다. 이 과정에서 PushMaterialDesc() 메서드가 핵심 역할을 합니다:
void Material::PushMaterialDesc()
{
// 아직 상수 버퍼가 없다면 생성
if (_materialBuffer == nullptr)
{
_materialBuffer = make_shared<Buffer>();
_materialBuffer->CreateConstantBuffer<MaterialDesc>();
}
// 최신 재질 속성을 상수 버퍼에 복사
_materialBuffer->CopyData(_materialDesc);
}
Texture 클래스 - 시각적 이미지 데이터 관리
Material이 참조하는 Texture 클래스는 이미지 데이터를 로드하고 GPU에서 사용할 수 있는 형태로 관리합니다.
class Texture : public ResourceBase
{
private:
// DirectX 텍스처 리소스와 뷰
ComPtr<ID3D11Texture2D> _texture;
ComPtr<ID3D11ShaderResourceView> _srv;
// 텍스처 메타데이터
D3D11_TEXTURE2D_DESC _desc;
ScratchImage _image;
};
Texture 클래스의 주요 기능:
이미지 로딩: 다양한 형식(PNG, JPG, DDS 등)의 이미지 파일 로드
리소스 뷰 생성: 셰이더에서 접근할 수 있는 ShaderResourceView 생성
텍스처 속성 관리: 크기, 형식, 밉맵 레벨 등 설정
// 텍스처 로딩 및 생성
void Load(const wstring& path);
void Create(DXGI_FORMAT format, uint32 width, uint32 height, uint32 bindFlag);
void CreateFromTexture(ComPtr<ID3D11Texture2D> texture);
// 텍스처 정보 접근
ComPtr<ID3D11ShaderResourceView> GetShaderResourceView() { return _srv; }
ComPtr<ID3D11Texture2D> GetTexture() { return _texture; }
텍스처 로딩 과정의 예:
void Texture::CreateTexture(const wstring& path)
{
// 파일 확장자 얻기
wstring ext = fs::path(path).extension();
if (ext == L".dds" || ext == L".DDS")
{
LoadTextureFromDDS(path);
return;
}
DirectX::TexMetadata md;
DirectX::ScratchImage img;
HRESULT hr = ::LoadFromWICFile(path.c_str(), WIC_FLAGS_FORCE_RGB, &md, img);
CHECK(hr);
// Generate Mipmaps
DirectX::ScratchImage mipChain;
hr = DirectX::GenerateMipMaps(
img.GetImages(), img.GetImageCount(), img.GetMetadata(),
DirectX::TEX_FILTER_DEFAULT, 0, mipChain);
CHECK(hr);
// Create shader resource view with mipmaps
hr = ::CreateShaderResourceView(DEVICE.Get(),
mipChain.GetImages(),
mipChain.GetImageCount(),
mipChain.GetMetadata(),
_shaderResourceView.GetAddressOf());
CHECK(hr);
// 텍스처 포맷 확인
DXGI_FORMAT format = md.format;
_size.x = md.width;
_size.y = md.height;
}
ComPtr<ID3D11ShaderResourceView> Texture::LoadTextureFromDDS(const wstring& path)
{
// 파일 확장자 얻기
wstring ext = fs::path(path).extension();
DirectX::TexMetadata md;
DirectX::ScratchImage img;
HRESULT hr;
if (ext == L".dds" || ext == L".DDS")
hr = ::LoadFromDDSFile(path.c_str(), DDS_FLAGS_NONE, &md, img);
else if (ext == L".tga" || ext == L".TGA")
hr = ::LoadFromTGAFile(path.c_str(), &md, img);
else // png, jpg, jpeg, bmp
hr = ::LoadFromWICFile(path.c_str(), WIC_FLAGS_NONE, &md, img);
CHECK(hr);
hr = ::CreateShaderResourceView(DEVICE.Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView.GetAddressOf());
_size.x = md.width;
_size.y = md.height;
CHECK(hr);
return _shaderResourceView;
}
Material 클래스는 여러 Texture 객체를 참조하며, 각 텍스처는 다른 시각적 속성(base color, normal map, specular map 등)을 담당합니다. 이 텍스처들은 셰이더에 바인딩되어 최종 렌더링 결과에 영향을 줍니다.
Shader 클래스 - GPU 프로그래밍 인터페이스
Material이 참조하는 또 다른 핵심 구성요소인 Shader 클래스는 GPU에서 실행되는 프로그램을 관리합니다
class Shader : public ResourceBase
{
private:
// 다양한 셰이더 타입
ComPtr<ID3D11VertexShader> _vertexShader; // 정점 처리
ComPtr<ID3D11PixelShader> _pixelShader; // 픽셀 처리
ComPtr<ID3D11GeometryShader> _geometryShader; // 기하 처리
ComPtr<ID3D11HullShader> _hullShader; // 테셀레이션 제어
ComPtr<ID3D11DomainShader> _domainShader; // 테셀레이션 평가
ComPtr<ID3D11ComputeShader> _computeShader; // 범용 계산
// 셰이더 바이트코드
ComPtr<ID3DBlob> _vsBlob, _psBlob, _gsBlob, _hsBlob, _dsBlob, _csBlob;
// 입력 레이아웃 및 셰이더 슬롯
shared_ptr<InputLayout> _inputLayout;
shared_ptr<ShaderSlot> _shaderSlot;
};
Shader 클래스의 주요 기능:
셰이더 로딩 및 컴파일: HLSL 파일에서 셰이더 코드 로드 및 컴파일
DirectX 셰이더 객체 생성: 컴파일된 셰이더로 DirectX 셰이더 객체 생성
리소스 바인딩 관리: 상수 버퍼, 텍스처 등 리소스를 셰이더에 바인딩
셰이더 로딩과 컴파일 과정:
void Shader::LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob)
{
ComPtr<ID3DBlob> errorBlob;
const uint32 compileFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
HRESULT hr = ::D3DCompileFromFile(
path.c_str(),
nullptr,
D3D_COMPILE_STANDARD_FILE_INCLUDE,
name.c_str(),
version.c_str(),
compileFlag,
0,
blob.GetAddressOf(),
&errorBlob);
if (FAILED(hr)) {
if (errorBlob) {
// 에러 메시지를 받아오고, OutputDebugStringA를 사용해 디버그 출력
std::string errorMessage(static_cast<char*>(errorBlob->GetBufferPointer()), errorBlob->GetBufferSize());
OutputDebugStringA(errorMessage.c_str()); // 디버그 콘솔로 메시지 출력
}
CHECK(hr); // 에러 체크 (예: 프로그램 종료 또는 에러 처리)
}
}
void Shader::CreateShader(ShaderType type, const wstring& shaderPath, InputLayoutType inputType)
{
// 셰이더 타입에 따라 다른 처리
switch (type)
{
case ShaderType::VERTEX_SHADER:
LoadShaderFromFile(shaderPath, "VS_Main", "vs_5_0", _vsBlob);
DEVICE->CreateVertexShader(_vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf());
// 입력 레이아웃 생성
_inputLayout = make_shared<InputLayout>();
_inputLayout->CreateInputLayout(inputType, _vsBlob);
break;
case ShaderType::PIXEL_SHADER:
LoadShaderFromFile(shaderPath, "PS_Main", "ps_5_0", _psBlob);
DEVICE->CreatePixelShader(_psBlob->GetBufferPointer(), _psBlob->GetBufferSize(), nullptr, _pixelShader.GetAddressOf());
break;
// 다른 셰이더 타입에 대한 처리...
}
// 셰이더 슬롯 초기화
_shaderSlot = make_shared<ShaderSlot>();
}
리소스 바인딩 메서드는 셰이더 슬롯을 사용하여 상수 버퍼나 텍스처를 셰이더의 특정 레지스터에 바인딩합니다:
void Shader::PushConstantBufferToShader(ShaderType type, const wstring& name, UINT numBuffers, shared_ptr<Buffer> buffer)
{
// 셰이더 슬롯에서 해당 이름의 레지스터 번호 조회
int slot = _shaderSlot->GetSlotNumber(name);
if (slot == -1)
{
slot = _shaderSlot->GetMaxSlotNumber() + 1;
_shaderSlot->SetSlot(name, slot);
}
// 셰이더 타입에 따라 적절한 바인딩 함수 호출
switch (type)
{
case ShaderType::VERTEX_SHADER:
CONTEXT->VSSetConstantBuffers(slot, numBuffers, buffer->GetConstantBuffer().GetAddressOf());
break;
case ShaderType::PIXEL_SHADER:
CONTEXT->PSSetConstantBuffers(slot, numBuffers, buffer->GetConstantBuffer().GetAddressOf());
break;
// 다른 셰이더 타입에 대한 처리...
}
}
Material 클래스는 Shader 객체를 설정하고 이를 통해 상수 버퍼와 텍스처를 셰이더에 바인딩합니다. 이 과정에서 셰이더 슬롯이 중요한 역할을 합니다.
ShaderSlot 클래스 - 셰이더 리소스 바인딩 관리
ShaderSlot 클래스는 셰이더 리소스(상수 버퍼, 텍스처 등)가 어떤 레지스터 슬롯에 바인딩될지 관리합니다.
class ShaderSlot
{
private:
// 리소스 이름과 슬롯 번호의 매핑을 관리하는 맵
vector<map<wstring, int>> slots;
int maxSlotNumber = -1;
public:
// 슬롯 설정 및 조회
void SetSlot(wstring name, int slot);
int GetSlotNumber(wstring name);
int GetMaxSlotNumber() { return maxSlotNumber; }
};
셰이더 슬롯의 작동 방식:
셰이더 코드에서는 레지스터 바인딩을 통해 리소스에 접근합니다:
// HLSL 셰이더 코드
cbuffer TransformBuffer : register(b0)
{
matrix World;
matrix View;
matrix Projection;
}
Texture2D DiffuseMap : register(t0);
ShaderSlot 클래스는 이러한 리소스 이름(예: "TransformBuffer", "DiffuseMap")과 레지스터 슬롯(예: b0, t0)의 매핑을 관리합니다:
void ShaderSlot::SetSlot(wstring name, int slot)
{
for (int i = 0; i < slots.size(); i++)
{
auto it = slots[i].find(name);
if (it != slots[i].end())
{
printf("That slot is already set up");
return;
}
}
map<wstring, int> map;
map[name] = slot;
slots.push_back(map);
if (slot > maxSlotNumber) {
maxSlotNumber = slot;
}
}
int ShaderSlot::GetSlotNumber(wstring name)
{
for (int i = 0; i < slots.size(); i++)
{
auto it = slots[i].find(name);
if (it != slots[i].end())
{
return it->second;
}
}
}
Shader 클래스의 리소스 바인딩 메서드는 ShaderSlot을 통해 이름으로 슬롯을 찾고 해당 슬롯에 리소스를 바인딩합니다:
void Shader::PushShaderResourceToShader(ShaderType type, const wstring& name, UINT numViews, ComPtr<ID3D11ShaderResourceView> shaderResourceViews)
{
UINT slot = _shaderSlot->GetSlotNumber(name);
if (type == ShaderType::VERTEX_SHADER)
DEVICECONTEXT->VSSetShaderResources(slot, numViews, shaderResourceViews.GetAddressOf());
else if (type == ShaderType::DOMAIN_SHADER)
DEVICECONTEXT->DSSetShaderResources(slot, numViews, shaderResourceViews.GetAddressOf());
else if (type == ShaderType::GEOMETRY_SHADER)
DEVICECONTEXT->GSSetShaderResources(slot, numViews, shaderResourceViews.GetAddressOf());
else
DEVICECONTEXT->PSSetShaderResources(slot, numViews, shaderResourceViews.GetAddressOf());
}
RenderPass 클래스 - 렌더링 단계 관리
RenderPass 클래스는 특정 렌더링 기법이나 셰이더 효과를 구현하는 단일 렌더링 단계를 나타냅니다. MeshRenderer는 여러 RenderPass를 가질 수 있으며, 각 RenderPass는 다른 렌더링 기법을 적용합니다.
class RenderPass
{
private:
// 렌더링에 필요한 참조들
weak_ptr<Transform> _transform;
weak_ptr<MeshRenderer> _meshRenderer;
shared_ptr<Shader> _shader;
shared_ptr<Mesh> _mesh;
shared_ptr<Texture> _texture;
// 렌더링 상태 정보
RenderPriority _priority = RenderPriority::NORMAL;
D3D11_PRIMITIVE_TOPOLOGY _topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
public:
// 렌더 패스 설정
void SetTransform(shared_ptr<Transform> transform) { _transform = transform; }
void SetMeshRenderer(shared_ptr<MeshRenderer> meshRenderer) { _meshRenderer = meshRenderer; }
void SetShader(shared_ptr<Shader> shader) { _shader = shader; }
void SetMesh(shared_ptr<Mesh> mesh) { _mesh = mesh; }
void SetTexture(shared_ptr<Texture> texture) { _texture = texture; }
// 렌더링 메서드
void Render(bool isEnv = false);
void DefaultRender(bool isEnv = false);
void TerrainRender();
void SkyBoxRender();
void ParticleSystemRender();
// ... 기타 렌더링 메서드 ...
};
RenderPass의 주요 기능:
렌더링 상태 설정: 렌더링에 필요한 셰이더, 메시, 텍스처 등 설정 (MeshRenderer가 가지고 있는 데이터를사용)
렌더링 실행: 설정된 상태로 렌더링 수행
다양한 렌더링 기법 지원: 기본 렌더링, Terrain, Skybox, ParticleSystem 등 특수 렌더링 구현
기본 렌더링 과정 예시:
void RenderPass::DefaultRender(bool isEnv)
{
// 셰이더 및 입력 레이아웃 설정
auto inputLayout = _shader->GetInputLayout();
CONTEXT->IASetInputLayout(inputLayout->GetInputLayout().Get());
CONTEXT->VSSetShader(_shader->GetVertexShader().Get(), nullptr, 0);
CONTEXT->PSSetShader(_shader->GetPixelShader().Get(), nullptr, 0);
// 변환 행렬 상수 버퍼 설정
auto transform = _transform.lock();
_shader->PushConstantBufferToShader(ShaderType::VERTEX_SHADER, L"TransformBuffer", 1, transform->GetTransformBuffer());
// 재질 상수 버퍼 설정
auto meshRenderer = _meshRenderer.lock();
auto materialBuffer = meshRenderer->GetMaterialBuffer();
_shader->PushConstantBufferToShader(ShaderType::PIXEL_SHADER, L"MaterialBuffer", 1, materialBuffer);
// 텍스처 설정
if (_texture != nullptr)
_shader->PushShaderResourceToShader(ShaderType::PIXEL_SHADER, L"DiffuseMap", 1, _texture->GetShaderResourceView());
// 메시 버퍼 및 토폴로지 설정
auto buffer = _mesh->GetBuffer();
UINT stride = buffer->GetStride();
UINT offset = buffer->GetOffset();
CONTEXT->IASetVertexBuffers(0, 1, buffer->GetVertexBuffer().GetAddressOf(), &stride, &offset);
CONTEXT->IASetIndexBuffer(buffer->GetIndexBuffer().Get(), DXGI_FORMAT_R32_UINT, 0);
CONTEXT->IASetPrimitiveTopology(_topology);
// 그리기 호출
auto indexCount = _mesh->GetGeometry()->GetIndices().size();
CONTEXT->DrawIndexed(indexCount, 0, 0);
}
MeshRenderer 클래스는 여러 RenderPass를 관리하며, RenderManager는 이 RenderPass들을 적절한 순서로 실행합니다:
// MeshRenderer.h
private:
vector<shared_ptr<RenderPass>> _renderPasses;
// RenderManager.cpp
void RenderManager::DrawRenderableObject(bool isEnv)
{
// 모든 렌더링 가능 오브젝트 순회
for (const shared_ptr<GameObject>& gameObject : _renderObjects)
{
shared_ptr<MeshRenderer> meshRenderer = gameObject->GetComponent<MeshRenderer>();
// 모든 렌더 패스 실행
for (int i = 0; i < meshRenderer->GetRenderPasses().size(); i++)
{
shared_ptr<RenderPass> renderPass = meshRenderer->GetRenderPasses()[i];
renderPass->Render(isEnv);
}
}
// 인스턴싱 렌더링 등 추가 처리
// ...
}
이러한 방식으로, Material은 셰이더, 텍스처, 상수 버퍼를 관리하고, Shader는 ShaderSlot을 통해 리소스를 바인딩하며, RenderPass는 이 모든 요소를 활용하여 실제 렌더링을 수행합니다. 이 구성 요소들의 조화로운 상호 작용이 3D 렌더링 시스템의 기반을 이룹니다.
렌더링 프로세스 전체 흐름
Material 초기화:
셰이더 설정
텍스처 설정
재질 속성 설정
상수 버퍼 생성 및 데이터 업로드
RenderPass 설정:
메시, 셰이더, 텍스처 참조 설정
렌더링 상태 구성
렌더링 실행:
RenderManager가 모든 렌더링 가능 오브젝트 수집
각 오브젝트의 모든 RenderPass 순차적으로 실행
각 RenderPass에서:
셰이더 설정
상수 버퍼 바인딩 (변환 행렬, 재질 속성)
텍스처 바인딩
정점 및 인덱스 버퍼 설정
그리기 호출
Last updated