MeshRenderer

MeshRenderer의 역할과 구조

MeshRenderer는 3D 게임 오브젝트를 렌더링하기 위한 핵심 컴포넌트입니다. Component 클래스를 상속받아 구현되었으며, 다음과 같은 주요 기능을 담당합니다:

  1. 3D 모델의 형태(Mesh)와 재질(Material) 정보 관리

  2. 렌더링 파이프라인 설정

  3. 렌더링 패스(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;
};
  • 이 템플릿 구현의 핵심 특징:

  1. 타입 독립성: 템플릿 T를 통해 어떤 정점 구조체도 저장 가능

  2. 데이터 추가 방식:

    1. 단일 정점/인덱스 추가 (AddVertex, AddIndex)

    2. 벡터로 일괄 추가 (AddVertices, AddIndices)

    3. 전체 설정 (SetVertices, SetIndices)

  3. 벡터 기반 저장: 동적으로 크기가 변할 수 있는 메시 데이터 처리에 적합

  • 다양한 정점 구조체 타입

    • 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는:

  1. 구의 모든 정점과 인덱스 데이터를 저장

  2. 데이터를 Buffer 클래스에 전달하여 GPU 메모리에 업로드할 준비

Buffer 클래스 - GPU 메모리 관리의 핵심

  • Buffer 클래스 구조와 역할

    • Buffer 클래스는 CPU 메모리의 데이터를 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):

        1. 생성 후 변경 불가능한 버퍼

        2. CPU에서 초기화 후 GPU에서 읽기만 가능

      • D3D11_USAGE_DYNAMIC (cpuWrite=true, gpuWrite=false):

        1. CPU에서 자주 업데이트하는 버퍼

        2. GPU는 읽기만 가능

      • D3D11_USAGE_DEFAULT (cpuWrite=false, gpuWrite=true):

        1. GPU에서 쓰기 가능

        2. 주로 GPU-GPU 복사 작업에 사용

      • D3D11_USAGE_STAGING (cpuWrite=true, gpuWrite=true):

        1. 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 클래스는 다음과 같은 핵심 요소들을 관리합니다:

  1. 셰이더 참조: 객체를 어떻게 렌더링할지 결정하는 셰이더 프로그램

  2. 텍스처 컬렉션: 객체의 표면 질감을 정의하는 다양한 텍스처 맵

  3. 재질 속성: 주변광(ambient), 확산광(diffuse), 반사광(specular) 등 재질의 광학적 특성

  4. 상수 버퍼: 이러한 속성을 셰이더에 전달하기 위한 버퍼

  • 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 클래스의 주요 기능:

  1. 이미지 로딩: 다양한 형식(PNG, JPG, DDS 등)의 이미지 파일 로드

  2. 리소스 뷰 생성: 셰이더에서 접근할 수 있는 ShaderResourceView 생성

  3. 텍스처 속성 관리: 크기, 형식, 밉맵 레벨 등 설정

// 텍스처 로딩 및 생성
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 클래스의 주요 기능:

  1. 셰이더 로딩 및 컴파일: HLSL 파일에서 셰이더 코드 로드 및 컴파일

  2. DirectX 셰이더 객체 생성: 컴파일된 셰이더로 DirectX 셰이더 객체 생성

  3. 리소스 바인딩 관리: 상수 버퍼, 텍스처 등 리소스를 셰이더에 바인딩

  • 셰이더 로딩과 컴파일 과정:

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; }
};
  • 셰이더 슬롯의 작동 방식:

  1. 셰이더 코드에서는 레지스터 바인딩을 통해 리소스에 접근합니다:

   // HLSL 셰이더 코드
   cbuffer TransformBuffer : register(b0)
   {
       matrix World;
       matrix View;
       matrix Projection;
   }
   
   Texture2D DiffuseMap : register(t0);
  1. 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;
	    }
	}
   }
  1. 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의 주요 기능:

  1. 렌더링 상태 설정: 렌더링에 필요한 셰이더, 메시, 텍스처 등 설정 (MeshRenderer가 가지고 있는 데이터를사용)

  2. 렌더링 실행: 설정된 상태로 렌더링 수행

  3. 다양한 렌더링 기법 지원: 기본 렌더링, 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 렌더링 시스템의 기반을 이룹니다.

렌더링 프로세스 전체 흐름

  1. Material 초기화:

    1. 셰이더 설정

    2. 텍스처 설정

    3. 재질 속성 설정

    4. 상수 버퍼 생성 및 데이터 업로드

  2. RenderPass 설정:

    1. 메시, 셰이더, 텍스처 참조 설정

    2. 렌더링 상태 구성

  3. 렌더링 실행:

    1. RenderManager가 모든 렌더링 가능 오브젝트 수집

    2. 각 오브젝트의 모든 RenderPass 순차적으로 실행

    3. 각 RenderPass에서:

      1. 셰이더 설정

      2. 상수 버퍼 바인딩 (변환 행렬, 재질 속성)

      3. 텍스처 바인딩

      4. 정점 및 인덱스 버퍼 설정

      5. 그리기 호출

Last updated