수업


✅ 주제

  • Shader 내 전역 변수들의 중복을 제거하고 Global.fx로 공통 정의를 모듈화하며, RenderManager를 통해 ConstantBuffer 데이터 전달을 구조화하는 통합 시스템을 구축하는 방법을 학습한다.

✅ 개념

  1. Shader 전역 변수의 문제점

    • 기존 방식은 Shader 코드 상단에 World, View, Projection, Texture0, LightDir 등 개별 요소를 직접 정의하고 CPU 코드에서 각각 세팅했다.
    • 이 방식은 테스트 단계에서는 편리하지만, 실전 개발에서는 중복이 많고 데이터 갱신 효율이 낮으며 구조적 관리가 어렵다.
    • 특히 하나의 값만 변경해도 전체 데이터를 다시 세팅해야 하는 문제가 있음.
  2. ConstantBuffer 사용의 이점

    • cbuffer를 이용해 관련된 데이터를 하나의 구조체로 묶어 GPU에 전달할 수 있다.
    • 이를 통해 원하는 범위의 데이터만 선택적으로 효율적으로 업데이트 가능하다.
    • World는 오브젝트 개별 상태이므로 TransformBuffer에, View, Projection은 전역 상태이므로 GlobalBuffer에 분리하여 관리한다.
  3. Global.fx 파일로 공통 정의 모듈화

    • HLSL에서도 C++처럼 #include를 활용하여 공통 헤더를 가져올 수 있다.
    • 이를 통해 자주 사용하는 구조체, 상수버퍼, 샘플러, 상태 정의, Pass 매크로 등을 공통화하여 모든 Shader 파일에서 쉽게 재사용 가능하게 만든다.
  4. RenderManager를 통한 데이터 전달 구조화

    • MeshRenderer 내에서 직접 Shader 값을 세팅하는 방식은 유지보수와 확장에 비효율적이다.
    • RenderManager를 통해 Shader에 전달해야 하는 데이터를 전담 관리하게 하여 역할 분리를 명확히 한다.
    • 추후 조명, 머티리얼, 애니메이션 등의 연동도 RenderManager를 통해 확장 가능하다.
  5. 카메라 좌표의 정확한 계산

    • 회전이 포함된 카메라의 월드 위치는 단순한 카메라 변환 행렬로는 정확히 얻기 어렵다.
    • 정확한 카메라 위치를 위해 월드 행렬의 역행렬을 사용해 보정한다.

✅ 용어정리

용어설명
cbufferGPU에 데이터(행렬 등)를 전달하기 위한 Constant Buffer 구조체
GlobalBufferView, Projection, VP(View * Projection)을 담은 전역 상수버퍼
TransformBuffer오브젝트 개별 World 행렬을 담는 상수버퍼
RenderManagerGlobalBuffer/TransformBuffer 데이터를 Shader에 전달하는 관리자 클래스
#includeShader 코드에서 외부 HLSL 파일을 포함시키는 전처리기 명령어
ID3DX11EffectConstantBufferConstantBuffer를 HLSL Shader와 연결하는 Effect11 객체
PASS_VPVertexShader, PixelShader를 간결하게 정의하기 위한 HLSL 매크로
RasterizerState렌더링 방식 설정 (예: 와이어프레임)
SamplerState텍스처 샘플링 방식 지정 (보간, 반복 등)
mul()행렬과 벡터의 곱 연산 함수 (HLSL 내장)

✅ 코드 분석

🔷 1. Global.fx - 공통 Shader 정의 헤더

#ifndef _GLOBAL_FX_
#define _GLOBAL_FX_

cbuffer GlobalBuffer
{
    matrix V;
    matrix P;
    matrix VP;
};

cbuffer TransformBuffer
{
    matrix W;
};

struct Vertex { float4 position : POSITION; };
struct VertexTexture { float4 position : POSITION; float2 uv : TEXCOORD; };
struct VertexColor { float4 Position : POSITION; float4 Color : COLOR; };
struct VertexTextureNormal { float4 position : POSITION; float2 uv : TEXCOORD; float3 normal : NORMAL; };

struct VertexOutput
{
    float4 position : SV_POSITION;
    float2 uv : TEXCOORD;
    float3 normal : NORMAL;
};

SamplerState LinearSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};

SamplerState PointSampler
{
    Filter = MIN_MAG_MIP_POINT;
    AddressU = Wrap;
    AddressV = Wrap;
};

RasterizerState FillModeWireFrame
{
    FillMode = Wireframe;
};

#define PASS_VP(name, vs, ps)                       \
pass name                                           \
{                                                   \
	SetVertexShader(CompileShader(vs_5_0, vs()));   \
	SetPixelShader(CompileShader(ps_5_0, ps()));    \
}

#endif
  • 공통으로 사용되는 구조체, 상태 정의, 버퍼를 포함.
  • 중복을 방지하기 위해 #ifndef _GLOBAL_FX_ 사용.
  • Pass 매크로를 통해 쉐이더 technique 구성도 효율화.

🔷 2. GlobalTest.fx - Global.fx를 포함하는 실습 Shader

#include "00. Global.fx"

Texture2D Texture0;

VertexOutput VS(VertexTextureNormal input)
{
    VertexOutput output;
    output.position = mul(input.position, W);
    output.position = mul(output.position, VP);
    output.uv = input.uv;
    output.normal = mul(input.normal, (float3x3)W);
    return output;
}

float4 PS(VertexOutput input) : SV_TARGET
{
    return Texture0.Sample(LinearSampler, input.uv);
}

technique11 T0
{
    PASS_VP(P0, VS, PS)
};
  • VS는 World 변환과 VP(View * Projection)을 곱해 최종 위치를 계산.
  • PS는 단순 텍스처 샘플링만 수행함.
  • technique 구성은 PASS_VP 매크로로 간결하게 처리.

🔷 3. RenderManager.h / RenderManager.cpp

구조체 정의 및 클래스 선언

struct GlobalDesc { Matrix V, P, VP; };
struct TransformDesc { Matrix W; };

class RenderManager
{
	DECLARE_SINGLE(RenderManager);

public:
	void Init(shared_ptr<Shader> shader);
	void PushGlobalData(const Matrix& view, const Matrix& projection);
	void PushTransformData(const TransformDesc& desc);
	void Update(); // 프레임 단위로 View, Projection 세팅

private:
	shared_ptr<Shader> _shader;
	GlobalDesc _globalDesc;
	TransformDesc _transformDesc;

	shared_ptr<ConstantBuffer<GlobalDesc>> _globalBuffer;
	ComPtr<ID3DX11EffectConstantBuffer> _globalEffectBuffer;

	shared_ptr<ConstantBuffer<TransformDesc>> _transformBuffer;
	ComPtr<ID3DX11EffectConstantBuffer> _transformEffectBuffer;
};

Init 구현

void RenderManager::Init(shared_ptr<Shader> shader)
{
	_shader = shader;

	_globalBuffer = make_shared<ConstantBuffer<GlobalDesc>>();
	_globalBuffer->Create();
	_globalEffectBuffer = _shader->GetConstantBuffer("GlobalBuffer");

	_transformBuffer = make_shared<ConstantBuffer<TransformDesc>>();
	_transformBuffer->Create();
	_transformEffectBuffer = _shader->GetConstantBuffer("TransformBuffer");
}

데이터 푸시 구현

void RenderManager::PushGlobalData(const Matrix& view, const Matrix& projection)
{
	_globalDesc.V = view;
	_globalDesc.P = projection;
	_globalDesc.VP = view * projection;
	_globalBuffer->CopyData(_globalDesc);
	_globalEffectBuffer->SetConstantBuffer(_globalBuffer->GetComPtr().Get());
}

void RenderManager::PushTransformData(const TransformDesc& desc)
{
	_transformDesc = desc;
	_transformBuffer->CopyData(_transformDesc);
	_transformEffectBuffer->SetConstantBuffer(_transformBuffer->GetComPtr().Get());
}

🔷 4. MeshRenderer.cpp 수정

void MeshRenderer::Update()
{
	if (_mesh == nullptr || _texture == nullptr || _shader == nullptr)
		return;

	_shader->GetSRV("Texture0")->SetResource(_texture->GetComPtr().Get());

	auto world = GetTransform()->GetWorldMatrix();
	RENDER->PushTransformData(TransformDesc{ world });

	uint32 stride = _mesh->GetVertexBuffer()->GetStride();
	uint32 offset = _mesh->GetVertexBuffer()->GetOffset();

	DC->IASetVertexBuffers(0, 1, _mesh->GetVertexBuffer()->GetComPtr().GetAddressOf(), &stride, &offset);
	DC->IASetIndexBuffer(_mesh->GetIndexBuffer()->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);

	_shader->DrawIndexed(0, 0, _mesh->GetIndexBuffer()->GetCount(), 0, 0);
}
  • World 행렬 설정을 RenderManager로 위임.

🔷 5. GlobalTestDemo.cpp - 카메라/오브젝트/Shader 초기화

void GlobalTestDemo::Init()
{
	_shader = make_shared<Shader>(L"08. GlobalTest.fx");

	_camera = make_shared<GameObject>();
	_camera->GetOrAddTransform();
	_camera->AddComponent(make_shared<Camera>());
	_camera->AddComponent(make_shared<CameraScript>());

	_obj = make_shared<GameObject>();
	_obj->GetOrAddTransform();
	_obj->AddComponent(make_shared<MeshRenderer>());
	_obj->GetMeshRenderer()->SetShader(_shader);

	RESOURCES->Init();
	auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
	_obj->GetMeshRenderer()->SetMesh(mesh);

	auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
	_obj->GetMeshRenderer()->SetTexture(texture);

	RENDER->Init(_shader); // RenderManager에 Shader 연결
}
void GlobalTestDemo::Update()
{
	_camera->Update();
	RENDER->Update(); // View/Projection 갱신
	_obj->Update();   // World 갱신
}

✅ 핵심

  • 전역 변수로 관리되던 Shader 데이터를 cbuffer로 구조화해 GPU에 효율적으로 전달한다.
  • Global.fx를 통해 모든 공통 Shader 정의를 모듈화하고 중복을 제거한다.
  • RenderManager를 통해 View/Projection/World 행렬을 Shader에 자동 전달하는 시스템을 구축한다.
  • MeshRendererGlobalTestDemo에서는 역할만 수행하고 세부 데이터 세팅은 RenderManager가 전담한다.
  • 이 구조는 이후 조명, 머티리얼, 애니메이션 등 확장 작업에도 동일하게 적용할 수 있으며, 유지보수와 확장성에 탁월하다.

profile
李家네_공부방

0개의 댓글