DirectX 11에서 인스턴스화(instancing)는 장면에서 동일하거나 유사한 객체의 많은 수를 효율적으로 렌더링하는 데 사용되는 기술이다. 인스턴스(instance)를 사용하면 각 개체를 개별적으로 그리는 대신 변환, 텍스처 또는 기타 속성이 다른 단일 개체를 여러 번 그릴 수 있다.

 

목차

 

 


 

 

 

Instancing

 

DirectX 11에서 인스턴스화(instancing)는 장면에서 동일하거나 유사한 객체의 많은 수를 효율적으로 렌더링하는 데 사용되는 기술이다. 인스턴스(instance)를 사용하면 각 개체를 개별적으로 그리는 대신 변환, 텍스처 또는 기타 속성이 다른 단일 개체를 여러 번 그릴 수 있다.

인스턴스화(Instancing) 기법은 단일 지오메트리 버퍼를 사용하여 객체의 지오메트리를 정의하고, 개별 버퍼를 사용하여 객체의 각 인스턴스에 대한 인스턴스 데이터를 저장함으로써 작동합니다. 인스턴스 데이터 버퍼에는 일반적으로 각 인스턴스의 위치, 회전 및 축척에 대한 정보뿐만 아니라 셰이더로 전달해야 하는 다른 모든 인스턴스별 데이터가 포함됩니다.

인스턴스화된 개체를 그리기 위해 그래픽 파이프라인은 지오메트리 버퍼와 인스턴스 데이터 버퍼를 모두 읽습니다. 각 인스턴스에 대해 파이프라인은 인스턴스별 데이터를 사용하여 객체의 지오메트리를 객체 공간에서 월드 공간으로 변환하는 변환 행렬을 계산합니다. 그런 다음 이 변환 행렬은 정점 셰이더로 전달되며, 이 셰이더를 사용하여 객체의 정점을 스크린 공간으로 변환합니다.

인스턴스(instance)의 가장 큰 장점은 GPU에 발행해야 하는 드로우 호출 수를 줄여 렌더링 성능을 크게 향상시킬 수 있다는 점이다. 그래픽 드라이버가 하드웨어 가속을 사용하여 인스턴스 작업을 수행할 수 있기 때문에 각 개체에 대해 별도의 추첨 호출을 실행하는 것보다 훨씬 빠릅니다.

인스턴스(instance)는 기하학적 구조는 같지만 속성이 다른 많은 수의 입자, 잎 또는 기타 유사한 객체를 렌더링하는 데 특히 유용합니다. 또한 나무 숲이나 많은 건물이 있는 도시 경관과 같은 더 작은 하위 객체로 구성된 복잡한 객체를 효율적으로 렌더링하는 데도 사용할 수 있다.

 

In DirectX 11, instancing is a technique used to efficiently render large numbers of identical or similar objects in a scene. Instead of drawing each object separately, instancing allows you to draw a single object multiple times with different transformations, textures, or other attributes.

The instancing technique works by using a single geometry buffer to define the geometry of the object, and a separate buffer to store the instance data for each instance of the object. The instance data buffer typically contains information about the position, rotation, and scale of each instance, as well as any other per-instance data that needs to be passed to the shader.

To draw the instanced object, the graphics pipeline reads both the geometry buffer and the instance data buffer. For each instance, the pipeline uses the per-instance data to compute a transformation matrix that transforms the object's geometry from object space to world space. This transformation matrix is then passed to the vertex shader, which uses it to transform the object's vertices to screen space.

The main advantage of instancing is that it reduces the number of draw calls that need to be issued to the GPU, which can significantly improve rendering performance. This is because the graphics driver can use hardware acceleration to perform the instancing operation, which is much faster than issuing separate draw calls for each object.

Instancing is particularly useful for rendering large numbers of particles, foliage, or other similar objects that have the same geometry but different properties. It can also be used to efficiently render complex objects that consist of many smaller sub-objects, such as a forest of trees or a cityscape with many buildings.

 

 

Shader
  50_Instancing
UnitTest
  Instancing
  InstancingDemo.h .cpp
  Main.h .cpp

 


Instancing.fx

 

Instancing.fx

더보기
#include "00_Global.fx"

float3 Direction = float3(-1, -1, +1);

struct VertexInput
{
    float4 Position : Position;
    float2 Uv : Uv;
    float3 Normal : Normal;

    matrix Transform : Inst1;
};

struct VertexOutput
{
    float4 Position : SV_Position;
    float2 Uv : Uv;
    float3 Normal : Normal;
};

VertexOutput VS(VertexInput input)
{
    VertexOutput output;

    World = input.Transform;

    output.Position = WorldPosition(input.Position);
    output.Position = ViewProjection(output.Position);
    
    output.Normal = WorldNormal(input.Normal);

    output.Uv = input.Uv;

    return output;
}

float4 PS(VertexOutput input) : SV_Target
{
    float3 normal = normalize(input.Normal);
    float3 light = -Direction;

    return DiffuseMap.Sample(LinearSampler, input.Uv) * dot(light, normal);
}

technique11 T0
{
    P_VP(P0, VS, PS)
    P_RS_VP(P1, FillMode_WireFrame, VS, PS)

}
  • 일전에 만든 25_Mesh.fx를 복사하여 만들었다. 추가된 내용은 다음과 같다.
    • float3 Direction = float3(-1, -1, +1);을 추가하여 light의 방향을 하드코딩 해주었다.
    • struct VertexInput{}를 추가하였다.

 

InstancingDemo

 

InstancingDemo.h

더보기
#pragma once
#include "Systems/IExecute.h"

class InstancingDemo : 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:
	void CreateMesh();

private:
	Shader* shader;
	Material* material;

	vector<Mesh::MeshVertex> vertices;
	vector<UINT> indices;

	VertexBuffer* vertexBuffer; //0
	VertexBuffer* instanceBuffer; //1

	IndexBuffer* indexBuffer;

	PerFrame* perFrame;

	Transform* transforms[1000];
	Matrix worlds[1000];
};

 

 

InstancingDemo.cpp

더보기
#include "stdafx.h"
#include "InstancingDemo.h"

void InstancingDemo::Initialize()
{
	Context::Get()->GetCamera()->RotationDegree(0, 0, 0);
	Context::Get()->GetCamera()->Position(0, 0, -17);

	shader = new Shader(L"50_Instancing.fx");


	perFrame = new PerFrame(shader);//프레임 별로 밀어준다.

	material = new Material(shader);
	material->DiffuseMap(L"Box.png");

	for (UINT i = 0; i < 1000; i++)
	{
		transforms[i] = new Transform(shader);

		//transforms[i] = new Transform();

		transforms[i]->Position(Math::RandomVec3(-30, 30));
		transforms[i]->Scale(Math::RandomVec3(1.0f, 2.5f));
		transforms[i]->RotationDegree(Math::RandomVec3(-180, 180));

		worlds[i] = transforms[i]->World();
	}

	CreateMesh();

	instanceBuffer = new VertexBuffer(worlds, 1000, sizeof(Matrix), 1);
}

void InstancingDemo::Update()
{
	perFrame->Update();

	for (UINT i = 0; i < 1000; i++)
		transforms[i]->Update();
}

void InstancingDemo::Render()
{
	perFrame->Render();

	material->Render();

	vertexBuffer->Render();
	instanceBuffer->Render();

	indexBuffer->Render();

	D3D::GetDC()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	for (UINT i = 0; i < 1000; i++)
	{
		transforms[i]->Render();

		shader->DrawIndexed(0, 0, indices.size());
	}

	shader->DrawIndexedInstanced(0, 0, indices.size(), 1000);
}

void InstancingDemo::CreateMesh()
{
	float w, h, d;
	w = h = d = 0.5f;

	//Front
	vertices.push_back(Mesh::MeshVertex(-w, -h, -d, 0, 1, 0, 0, -1));
	vertices.push_back(Mesh::MeshVertex(-w, +h, -d, 0, 0, 0, 0, -1));
	vertices.push_back(Mesh::MeshVertex(+w, +h, -d, 1, 0, 0, 0, -1));
	vertices.push_back(Mesh::MeshVertex(+w, -h, -d, 1, 1, 0, 0, -1));

	//Back
	vertices.push_back(Mesh::MeshVertex(-w, -h, +d, 1, 1, 0, 0, 1));
	vertices.push_back(Mesh::MeshVertex(+w, -h, +d, 0, 1, 0, 0, 1));
	vertices.push_back(Mesh::MeshVertex(+w, +h, +d, 0, 0, 0, 0, 1));
	vertices.push_back(Mesh::MeshVertex(-w, +h, +d, 1, 0, 0, 0, 1));

	//Top
	vertices.push_back(Mesh::MeshVertex(-w, +h, -d, 0, 1, 0, 1, 0));
	vertices.push_back(Mesh::MeshVertex(-w, +h, +d, 0, 0, 0, 1, 0));
	vertices.push_back(Mesh::MeshVertex(+w, +h, +d, 1, 0, 0, 1, 0));
	vertices.push_back(Mesh::MeshVertex(+w, +h, -d, 1, 1, 0, 1, 0));

	//Bottom
	vertices.push_back(Mesh::MeshVertex(-w, -h, -d, 1, 1, 0, -1, 0));
	vertices.push_back(Mesh::MeshVertex(+w, -h, -d, 0, 1, 0, -1, 0));
	vertices.push_back(Mesh::MeshVertex(+w, -h, +d, 0, 0, 0, -1, 0));
	vertices.push_back(Mesh::MeshVertex(-w, -h, +d, 1, 0, 0, -1, 0));

	//Left
	vertices.push_back(Mesh::MeshVertex(-w, -h, +d, 0, 1, -1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(-w, +h, +d, 0, 0, -1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(-w, +h, -d, 1, 0, -1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(-w, -h, -d, 1, 1, -1, 0, 0));

	//Right
	vertices.push_back(Mesh::MeshVertex(+w, -h, -d, 0, 1, 1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(+w, +h, -d, 0, 0, 1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(+w, +h, +d, 1, 0, 1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(+w, -h, +d, 1, 1, 1, 0, 0));


	indices =
	{
		0, 1, 2, 0, 2, 3,
		4, 5, 6, 4, 6, 7,
		8, 9, 10, 8, 10, 11,
		12, 13, 14, 12, 14, 15,
		16, 17, 18, 16, 18, 19,
		20, 21, 22, 20, 22, 23
	};

	vertexBuffer = new VertexBuffer(&vertices[0], vertices.size(), sizeof(Mesh::MeshVertex));
	indexBuffer = new IndexBuffer(&indices[0], indices.size());
}

 

실행화면