• 티스토리 홈
  • 프로필사진
    유니얼
  • 방명록
  • 공지사항
  • 태그
  • 블로그 관리
  • 글 작성
유니얼
  • 프로필사진
    유니얼
    • 분류 전체보기 (295)
      • Unity (17)
        • 게임 개발 (5)
      • Unreal (24)
        • 게임 개발 (20)
      • DirectX (36)
      • 코딩테스트 (91)
        • 프로그래머스 (25)
        • 백준 (66)
      • Google Workspace (1)
      • Programing (102)
        • C# (68)
        • C++ (24)
        • JavaScript (10)
      • 게임 서버 프로그래밍 (17)
      • Web (6)
        • 슈퍼코딩 (6)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
    등록된 댓글이 없습니다.
  • 최근 공지
    등록된 공지가 없습니다.
# Home
# 공지사항
#
# 태그
# 검색결과
# 방명록
  • [DirectX11] 인스턴싱(Instancing)과 드로우 콜(Draw Call)
    2024년 03월 12일
    • 유니얼
    • 작성자
    • 2024.03.12.:19
    728x90

    DirectX 11로 게임 엔진 아키텍처 만들기

    DirectX11과 게임 엔진 아키텍쳐를 공부하면서, 그 중에서도 Instancing과 드로우 콜(Draw Call) 최적화는 게임 개발에서 매우 중요한 주제입니다. 이러한 개념을 이해하고 적절히 활용하면, 더 많은 객체를 화면에 렌더링하면서도 성능을 유지할 수 있습니다. 이 글에서는 Instancing과 드로우 콜에 대해 설명하고, DirectX11에서 이를 구현하는 방법을 예제 코드와 함께 살펴보겠습니다.

     

    Result

    참고강의 링크:

    https://www.inflearn.com/course/directx11-%EA%B2%8C%EC%9E%84%EA%B0%9C%EB%B0%9C-%EB%8F%84%EC%95%BD%EB%B0%98/dashboard

     

    [게임 프로그래머 도약반] DirectX11 입문 강의 - 인프런

    게임 프로그래머 공부에 있어서 필수적인 DirectX 11 지식을 초보자들의 눈높이에 맞춰 설명하는 강의입니다., [사진][사진] [사진] 게임 개발자는 Unreal, Unity만 사용할 줄 알면 되는 거 아닌가요? 엔

    www.inflearn.com

    Instancing이란?

    Instancing은 하나의 렌더링 호출로 여러 개의 객체를 화면에 그리는 기술입니다. 이 방법을 사용하면, 각 객체마다 별도의 드로우 콜을 발생시키지 않아도 동일한 메쉬(geometry)를 공유하는 여러 인스턴스를 효율적으로 렌더링할 수 있습니다. 예를 들어, 게임 내에 같은 형태의 나무가 수백 개 있다고 할 때, 각각의 나무에 대해 개별적으로 렌더링 호출을 하지 않고, Instancing을 사용하여 단일 드로우 콜로 모든 나무를 렌더링할 수 있습니다.

    드로우 콜(Draw Call) 최적화

    드로우 콜은 GPU에게 그래픽스 렌더링을 요청하는 호출입니다. 드로우 콜 수가 많을수록 CPU와 GPU 사이의 통신 부담이 증가하여 게임의 성능이 저하될 수 있습니다. 따라서, 드로우 콜 수를 최소화하는 것은 게임 성능 최적화에서 중요한 과제입니다. Instancing은 이러한 최적화를 달성하기 위한 효과적인 방법 중 하나입니다.

    DirectX11에서의 Instancing 구현

    DirectX11에서 Instancing을 구현하기 위해, 주로 두 가지 주요 개념을 사용합니다: Instanced Buffer와 Instanced Data. Instanced Buffer는 각 인스턴스의 고유 데이터(예: 위치, 회전 등)를 저장하는 버퍼이며, Instanced Data는 각 인스턴스에 적용할 데이터입니다.

    #pragma once
    
    class VertexBuffer
    {
    public:
    	VertexBuffer();
    	~VertexBuffer();
    
    	ComPtr<ID3D11Buffer> GetComPtr() { return _vertexBuffer; }
    	uint32 GetStride() { return _stride; } // 정점 하나의 크기
    	uint32 GetOffset() { return _offset; } // 버퍼 내에서의 오프셋
    	uint32 GetCount() { return _count; } // 버퍼에 저장된 정점의 수
    	uint32 GetSlot() { return _slot; } // 이 버퍼가 바인드될 슬롯 번호
    
    	// 정점 데이터를 이용해 버퍼를 생성하는 함수
    	template<typename T>
    	void Create(const vector<T>& vertices, uint32 slot = 0, bool cpuWrite = false, bool gpuWrite = false)
    	{
    		_stride = sizeof(T);
    		_count = static_cast<uint32>(vertices.size());
    
    		_slot = slot;
    		_cpuWrite = cpuWrite;
    		_gpuWrite = gpuWrite;
    
    		D3D11_BUFFER_DESC desc;
    		ZeroMemory(&desc, sizeof(desc));
    		desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // 이 버퍼가 정점 버퍼로 사용됨을 명시
    		desc.ByteWidth = (uint32)(_stride * _count); // 버퍼의 전체 크기
    
    		// 버퍼의 용도 설정
    		if (cpuWrite == false && gpuWrite == false)
    		{
    			desc.Usage = D3D11_USAGE_IMMUTABLE; // 변경 불가, GPU만 읽기
    		}
    		else if (cpuWrite == true && gpuWrite == false)
    		{
    			desc.Usage = D3D11_USAGE_DYNAMIC; // CPU에서 쓰기 가능, GPU에서 읽기
    			desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    		}
    		else if (cpuWrite == false && gpuWrite == true)
    		{
    			desc.Usage = D3D11_USAGE_DEFAULT; // 기본 설정, GPU에서 읽고 쓰기
    		}
    		else
    		{
    			desc.Usage = D3D11_USAGE_STAGING; // CPU와 GPU 모두에서 읽고 쓰기 가능
    			desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
    		}
    
    		D3D11_SUBRESOURCE_DATA data;
    		ZeroMemory(&data, sizeof(data));
    		data.pSysMem = vertices.data(); // 정점 데이터의 포인터
    
    		// 버퍼 생성
    		HRESULT hr = DEVICE->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
    		CHECK(hr); // 성공 여부 확인
    	}
    
    	// 이 함수를 호출하면 버퍼가 입력 어셈블러 스테이지에 바인드됩니다.
    	void PushData()
    	{
    		DC->IASetVertexBuffers(_slot, 1, _vertexBuffer.GetAddressOf(), &_stride, &_offset);
    	}
    
    private:
    	ComPtr<ID3D11Buffer> _vertexBuffer; // DirectX 정점 버퍼
    
    	uint32 _stride = 0; // 정점 하나의 바이트 크기
    	uint32 _offset = 0; // 버퍼 내에서의 오프셋, 일반적으로 0
    	uint32 _count = 0; // 버퍼에 저장된 정점의 수
    
    	uint32 _slot = 0; // 이 버퍼가 바인드될 슬롯 번호
    	bool _cpuWrite = false; // CPU에서 버퍼에 쓰기 가능 여부
    	bool _gpuWrite = false; // GPU에서 버퍼에 쓰기 가능 여부
    };

    프로젝트 호출

    InstancingDemo.h

    #pragma once
    #include "IExecute.h"
    
    class InstancingDemo : public IExecute
    {
    public:
    	void Init() override;
    	void Update() override;
    	void Render() override;
    
    private:
    	shared_ptr<Shader> _shader;
    	shared_ptr<GameObject> _camera;
    	vector<shared_ptr<GameObject>> _objs;
    
    private:
    	// INSTANCING
    	shared_ptr<Mesh> _mesh;
    	shared_ptr<Material> _material;
    
    	vector<Matrix> _worlds;
    	shared_ptr<VertexBuffer> _instanceBuffer;
    };

    InstancingDemo.cpp

    #include "pch.h"
    #include "InstancingDemo.h"
    #include "GeometryHelper.h"
    #include "Camera.h"
    #include "GameObject.h"
    #include "CameraScript.h"
    #include "MeshRenderer.h"
    #include "Mesh.h"
    #include "Material.h"
    #include "Model.h"
    #include "ModelRenderer.h"
    #include "ModelAnimator.h"
    #include "Mesh.h"
    #include "Transform.h"
    #include "VertexBuffer.h"
    #include "IndexBuffer.h"
    
    void InstancingDemo::Init()
    {
    	RESOURCES->Init();
    	_shader = make_shared<Shader>(L"19. InstancingDemo.fx");
    
    	// Camera
    	_camera = make_shared<GameObject>();
    	_camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
    	_camera->AddComponent(make_shared<Camera>());
    	_camera->AddComponent(make_shared<CameraScript>());
    
    	// Material
    	{
    		shared_ptr<Material> material = make_shared<Material>();
    		material->SetShader(_shader);
    		auto texture = RESOURCES->Load<Texture>(L"UnityLogo", L"..\\Resources\\Textures\\UnityLogo.png");
    		material->SetDiffuseMap(texture);
    		MaterialDesc& desc = material->GetMaterialDesc();
    		desc.ambient = Vec4(1.f);
    		desc.diffuse = Vec4(1.f);
    		desc.specular = Vec4(1.f);
    		RESOURCES->Add(L"UnityLogo", material);
    		// INSTANCING
    		_material = material;
    	}
    
    	for (int32 i = 0; i < 10000; i++)
    	{
    		auto obj = make_shared<GameObject>();
    		obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 100, 0, rand() % 100));
    		obj->AddComponent(make_shared<MeshRenderer>());
    		{
    			obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"UnityLogo"));
    		}
    		{
    			auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
    			obj->GetMeshRenderer()->SetMesh(mesh);
    			// INSTANCING
    			_mesh = mesh;
    		}
    
    		_objs.push_back(obj);
    	}
    	
    	RENDER->Init(_shader);
    
    	// INSTANCING
    	_instanceBuffer = make_shared<VertexBuffer>();
    
    	for (auto& obj : _objs)
    	{
    		Matrix world = obj->GetTransform()->GetWorldMatrix();
    		_worlds.push_back(world);
    	}
    
    	_instanceBuffer->Create(_worlds, /*slot*/1);
    }
    
    void InstancingDemo::Update()
    {
    	_camera->Update();
    	RENDER->Update();
    
    	{
    		LightDesc lightDesc;
    		lightDesc.ambient = Vec4(0.4f);
    		lightDesc.diffuse = Vec4(1.f);
    		lightDesc.specular = Vec4(0.1f);
    		lightDesc.direction = Vec3(1.f, 0.f, 1.f);
    		RENDER->PushLightData(lightDesc);
    	}
    
    	/*for (auto& obj : _objs)
    	{
    		obj->Update();
    	}*/
    
    	_material->Update();
    
    	//auto world = GetTransform()->GetWorldMatrix();
    	//RENDER->PushTransformData(TransformDesc{ world });
    
    	_mesh->GetVertexBuffer()->PushData();
    	_instanceBuffer->PushData();
    	_mesh->GetIndexBuffer()->PushData();
    
    	//_shader->DrawIndexed(0, 0, _mesh->GetIndexBuffer()->GetCount(), 0, 0);
    	_shader->DrawIndexedInstanced(0, 0, _mesh->GetIndexBuffer()->GetCount(), _objs.size());
    }
    
    void InstancingDemo::Render()
    {
    
    }

    결론

    인스턴싱(Instancing)과 드로우 콜(Draw Call) 최적화는 게임 개발과 다양한 3D 애플리케이션에서 성능 향상을 위해 필수적인 기법입니다. 인스턴싱을 통해 같은 메시를 여러 번 렌더링할 때 발생하는 오버헤드를 줄이고, 드로우 콜의 수를 감소시킴으로써 CPU와 GPU 간의 통신 부하를 줄일 수 있습니다. 이러한 기법들을 적절히 활용하면, 대규모 장면을 더 효율적으로 렌더링하며, 전체적인 애플리케이션의 성능을 향상시킬 수 있습니다.

     

    반응형
    다음글
    다음 글이 없습니다.
    이전글
    이전 글이 없습니다.
    댓글
조회된 결과가 없습니다.
스킨 업데이트 안내
현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
목차
표시할 목차가 없습니다.
    • 안녕하세요
    • 감사해요
    • 잘있어요

    티스토리툴바