본문 바로가기

DirectX11 3D/기본 문법

<DirectX11 3D> 83 - Billboard

 

 

필요한 개념


Billboard 설명

Billboard

게임 같은거에 바닥에 풀이 대량으로 깔려있다. 그 풀이 모델이 아니라 하나의 판에다가 텍스처 하나를 반투명을 넣어서 출력해준 것이다.
복잡한 모델이라면 바닥에 풀을 많이 깔 수 없다. 아주 간단한 사각형 판을 깔고 텍스처 하나를 바르는것. 이런것이 여러개

카메라가 움직이면 면이 카메라 방향을 보게 된다.

면 하나를 깔아서 풀 텍스쳐를 입히고 그 면이 우리가 바라보는 카메라 방향으로 보게하는것이 BillBoard이다.

하나만 하면 어색해서 Terrain에 여러가지 풀을 섞어서 깐다.

각각 한 면씩 렌더링하면 DP Call이 많아진다.
인스턴싱을 사용할 수도 있으나, 별도의 슬롯이 필요하므로
간단히 정점 버퍼 하나로 각면의 값을 한번에 넣어서 면을 생성한다.

우리 면하나당 정점 4개 인덱스가 6개 필요했다. 면의 필요한 개수를 예를 들어 면이 100개다 하면 정점 개당 4개니까 400개의 정점, 인덱스 600개가 된다.
하나하나 그리지 말고 한번에 DrawIndexed() 시키고 정점에 위치값은 모두 동일하게 주고 UV를 줘서 면처럼 만든다. 한번에 100개의 면을 그리도록 한다.

정점의 위치는 4개 동일하게 주고 Shader에서 이것을 이용해서 면작업을 한다.

정점의 수 : 최대 빌보드 개수 * 4
인덱스의 수 : 최대 빌보드 개수 * 6

빌보드는 카메라를 향하게 하므로 면의 전방 방향을 카메라를 향하도록 만들어야 한다.

 

 

 

Billboard.h


더보기
#pragma once


// 최대 빌보드 개수(면 최대 개수)
#define MAX_BILLBOARD_COUNT 10000

class Billborad : public Renderer
{
public:
	// 텍스쳐 파일 경로
	Billborad(wstring file);
	~Billborad();

	void Update();
	void Render();

	// 외부에서 위치 추가할 수 있도록
	void Add(Vector3& position, Vector2& scale);

private:
	struct VertexBillboard
	{
		Vector3 Position;
		Vector2 Uv;
		Vector2 Scale;
	};

private:
	VertexBillboard* vertices;
	UINT* indices;

	// 최대 그릴 개수
	UINT drawCount = 0;
	UINT prevCount = 0;

	Texture* texture;
	ID3DX11EffectShaderResourceVariable* sDiffuseMap;
};

 

 

 

 

 

 

 

Billboard.cpp


더보기
#include "Framework.h"
#include "Billboard.h"

Billborad::Billborad(wstring file) :
	Renderer(L"83_Billboard.fx")
{
	// Count는 따로 저장할 필요없다, drawCount로 그릴거여서
	vertexCount = MAX_BILLBOARD_COUNT * 4;
	vertices = new VertexBillboard[vertexCount];

	// CpuWrite = true, 면이 추가되면 계속 정점 버퍼에 값을 써줘야 하기 때문이다.
	vertexBuffer = new VertexBuffer(vertices, vertexCount, sizeof(VertexBillboard), 0, true);


	indexCount = MAX_BILLBOARD_COUNT * 6;
	indices = new UINT[indexCount];

	// 인덱스는 최대 그릴 개수만큼 미리 전부 계산해놓음
	for (UINT i = 0; i < MAX_BILLBOARD_COUNT; i++)
	{
		indices[i * 6 + 0] = i * 4 + 0;
		indices[i * 6 + 1] = i * 4 + 1;
		indices[i * 6 + 2] = i * 4 + 2;
		indices[i * 6 + 3] = i * 4 + 2;
		indices[i * 6 + 4] = i * 4 + 1;
		indices[i * 6 + 5] = i * 4 + 3;
	}

	indexBuffer = new IndexBuffer(indices, indexCount);

	texture = new Texture(file);
	sDiffuseMap = shader->AsSRV("DiffuseMap");
}

Billborad::~Billborad()
{
	SafeDeleteArray(vertices);
	SafeDeleteArray(indices);

	SafeDelete(texture);
}

void Billborad::Update()
{
	Super::Update();
}

void Billborad::Render()
{
	if (drawCount != prevCount)
	{
		prevCount = drawCount;

		// 여기서 Add시에 밀어넣어주는 거 보다
		// Render에서 DrawCount와 다를시에 밀어주는게 더 좋긴 하다.
		D3D11_MAPPED_SUBRESOURCE subResource;
		D3D::GetDC()->Map(vertexBuffer->Buffer(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
		{
			// memcpy_s가 정확하다.
			// 면 하나당 4개의 정점이 필요
			memcpy(subResource.pData, vertices, sizeof(VertexBillboard) * vertexCount);
		}
		D3D::GetDC()->Unmap(vertexBuffer->Buffer(), 0);
	}

	Super::Render();

	sDiffuseMap->SetResource(texture->SRV());

	// 그릴 개수 : drawCount * 6개
	shader->DrawIndexed(0, Pass(), drawCount * 6);
}

void Billborad::Add(Vector3& position, Vector2& scale)
{
	// drawCount는 0부터 시작할 것이다.
	// 0부터 쭉 넣으면 됨

	// 4개를 넣는다.(정점 4개는 위치가 같다.)
	// Shader에서 전개 시켜줄 것이다.

	vertices[drawCount * 4 + 0].Position = position;
	vertices[drawCount * 4 + 1].Position = position;
	vertices[drawCount * 4 + 2].Position = position;
	vertices[drawCount * 4 + 3].Position = position;

	vertices[drawCount * 4 + 0].Uv = Vector2(0, 1);
	vertices[drawCount * 4 + 1].Uv = Vector2(0, 0);
	vertices[drawCount * 4 + 2].Uv = Vector2(1, 1);
	vertices[drawCount * 4 + 3].Uv = Vector2(1, 0);


	// Scale도 4개 전부 동일하게 준다.
	// 그래야 Shader에서 크기 늘려줄때 가능, 전부 동일한 값을 가져야함
	vertices[drawCount * 4 + 0].Scale = scale;
	vertices[drawCount * 4 + 1].Scale = scale;
	vertices[drawCount * 4 + 2].Scale = scale;
	vertices[drawCount * 4 + 3].Scale = scale;

	drawCount++;
}

 

 

 

 

 

 

 

BillboardDemo.h


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

class BillboardDemo : 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 Billboards();
	void Mesh();
	void Airplane();
	void Kachujin();
	void KachujinCollider();
	void KachujinWeapon();
	void PointLighting();
	void SpotLighting();

	void Pass(UINT mesh, UINT model, UINT anim);

private:
	Shader* shader;

	Billborad* billboard;

	CubeSky* sky;

	Material* floor;
	Material* stone;
	Material* brick;
	Material* wall;

	MeshRender* cube;
	MeshRender* cylinder;
	MeshRender* sphere;
	MeshRender* grid;

	ModelRender* airplane = NULL;


	ModelAnimator* kachujin = NULL;
	Transform* colliderInitTransforms;
	ColliderObject** colliders;

	ModelRender* weapon = NULL;
	Transform* weaponInitTransform;

	vector<MeshRender*> meshes;
	vector<ModelRender*> models;
	vector<ModelAnimator*> animators;
};

 

 

 

 

 

 

 

BillboardDemo.cpp


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

void BillboardDemo::Initialize()
{
	Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
	Context::Get()->GetCamera()->Position(1, 36, -85);

	shader = new Shader(L"82_NormalMapping.fxo");

	sky = new CubeSky(L"Environment/GrassCube1024.dds");

	Billboards();

	Mesh();
	Airplane();

	Kachujin();
	KachujinCollider();
	KachujinWeapon();

	PointLighting();
	SpotLighting();
}

void BillboardDemo::Update()
{
	static UINT selected = 3;
	ImGui::InputInt("NormalMap Selected", (int*)&selected);
	selected %= 4;

	shader->AsScalar("Selected")->SetInt(selected);

	sky->Update();

	cube->Update();
	grid->Update();
	cylinder->Update();
	sphere->Update();

	airplane->Update();
	kachujin->Update();

	Matrix worlds[MAX_MODEL_TRANSFORMS];
	for (UINT i = 0; i < kachujin->GetTransformCount(); i++)
	{
		kachujin->GetAttachTransform(i, worlds);

		weapon->GetTransform(i)->World(weaponInitTransform->World() * worlds[40]);
	}

	weapon->UpdateTransforms();
	weapon->Update();

	billboard->Update();
}

void BillboardDemo::Render()
{
	sky->Render();


	Pass(0, 1, 2);

	wall->Render();
	sphere->Render();

	brick->Render();
	cylinder->Render();

	stone->Render();
	cube->Render();

	floor->Render();
	grid->Render();

	airplane->Render();

	kachujin->Render();
	weapon->Render();

	billboard->Render();
}

void BillboardDemo::Billboards()
{
	billboard = new Billborad(L"Terrain/grass_14.tga");
	
	for (UINT i = 0; i < 1200; i++)
	{
		Vector2 scale = Math::RandomVec2(1, 3);
		// 5(밑에 판 크기가 -5 ~ 5 -> 10) * 12(grid 크기) = 60
		Vector2 position = Math::RandomVec2(-60, 60);

		billboard->Add(Vector3(position.x, scale.y * 0.5f, position.y), scale);
	}

}

void BillboardDemo::Mesh()
{
	//Create Material
	{
		floor = new Material(shader);
		floor->DiffuseMap("Floor.png");
		floor->Specular(1, 1, 1, 20);
		floor->SpecularMap("Floor_Specular.png");
		floor->NormalMap("Floor_Normal.png");

		stone = new Material(shader);
		stone->DiffuseMap("Stones.png");
		stone->Specular(1, 1, 1, 20);
		stone->SpecularMap("Stones_Specular.png");
		stone->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
		stone->NormalMap("Stones_Normal.png");


		brick = new Material(shader);
		brick->DiffuseMap("Bricks.png");
		brick->Specular(1, 0.3f, 0.3f, 20);
		brick->SpecularMap("Bricks_Specular.png");
		brick->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
		brick->NormalMap("Bricks_Normal.png");

		wall = new Material(shader);
		wall->DiffuseMap("Wall.png");
		wall->Specular(1, 1, 1, 20);
		wall->SpecularMap("Wall_Specular.png");
		wall->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
		wall->NormalMap("Wall_Normal.png");
	}

	//Create Mesh
	{
		Transform* transform = NULL;

		cube = new MeshRender(shader, new MeshCube());
		transform = cube->AddTransform();
		transform->Position(0, 5, 0);
		transform->Scale(20, 10, 20);

		grid = new MeshRender(shader, new MeshGrid(5, 5));
		transform = grid->AddTransform();
		transform->Position(0, 0, 0);
		transform->Scale(12, 1, 12);

		cylinder = new MeshRender(shader, new MeshCylinder(0.5f, 3.0f, 20, 20));
		sphere = new MeshRender(shader, new MeshSphere(0.5f, 20, 20));
		for (UINT i = 0; i < 5; i++)
		{
			transform = cylinder->AddTransform();
			transform->Position(-30, 6, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);

			transform = cylinder->AddTransform();
			transform->Position(30, 6, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);


			transform = sphere->AddTransform();
			transform->Position(-30, 15.5f, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);

			transform = sphere->AddTransform();
			transform->Position(30, 15.5f, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);
		}
	}

	sphere->UpdateTransforms();
	cylinder->UpdateTransforms();
	cube->UpdateTransforms();
	grid->UpdateTransforms();

	meshes.push_back(sphere);
	meshes.push_back(cylinder);
	meshes.push_back(cube);
	meshes.push_back(grid);
}

void BillboardDemo::Airplane()
{
	airplane = new ModelRender(shader);
	airplane->ReadMesh(L"B787/Airplane");
	airplane->ReadMaterial(L"B787/Airplane");

	Transform* transform = airplane->AddTransform();
	transform->Position(2.0f, 9.91f, 2.0f);
	transform->Scale(0.004f, 0.004f, 0.004f);
	airplane->UpdateTransforms();

	models.push_back(airplane);
}

void BillboardDemo::Kachujin()
{
	kachujin = new ModelAnimator(shader);
	kachujin->ReadMesh(L"Kachujin/Mesh");
	kachujin->ReadMaterial(L"Kachujin/Mesh");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Idle");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Walk");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Run");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Slash");
	kachujin->ReadClip(L"Kachujin/Salsa Dancing");


	Transform* transform = NULL;

	transform = kachujin->AddTransform();
	transform->Position(0, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(0, 0, 1.0f);

	transform = kachujin->AddTransform();
	transform->Position(-15, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(1, 1, 1.0f);

	transform = kachujin->AddTransform();
	transform->Position(-30, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(2, 2, 0.75f);

	transform = kachujin->AddTransform();
	transform->Position(15, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayBlendMode(3, 0, 1, 2);
	kachujin->SetBlendAlpha(3, 1.5f);

	transform = kachujin->AddTransform();
	transform->Position(30, 0, -32.5f);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(4, 4, 0.75f);

	kachujin->UpdateTransforms();

	animators.push_back(kachujin);
}

void BillboardDemo::KachujinCollider()
{
	UINT count = kachujin->GetTransformCount();
	colliders = new  ColliderObject * [count];

	colliderInitTransforms = new Transform();
	colliderInitTransforms->Position(-2.9f, 1.45f, -50.0f);
	colliderInitTransforms->Scale(5, 5, 75);

	for (UINT i = 0; i < count; i++)
	{
		colliders[i] = new ColliderObject();

		//colliders[i]->Init = new Transform();
		//colliders[i]->Init->Position(0, 0, 0);
		//colliders[i]->Init->Scale(10, 30, 10);

		colliders[i]->Transform = new Transform();
		//colliders[i]->Collider = new Collider(colliders[i]->Transform, colliders[i]->Init);
		colliders[i]->Collider = new Collider(colliders[i]->Transform, colliderInitTransforms);
	}
}

void BillboardDemo::KachujinWeapon()
{
	weapon = new ModelRender(shader);
	weapon->ReadMesh(L"Weapon/Sword");
	weapon->ReadMaterial(L"Weapon/Sword");

	UINT count = kachujin->GetTransformCount();
	for (UINT i = 0; i < count; i++)
		weapon->AddTransform();

	weapon->UpdateTransforms();
	models.push_back(weapon);


	weaponInitTransform = new Transform();
	weaponInitTransform->Position(-2.9f, 1.45f, -6.45f);
	weaponInitTransform->Scale(0.5f, 0.5f, 0.75f);
	weaponInitTransform->Rotation(0, 0, 1);
}

void BillboardDemo::PointLighting()
{
	PointLight light;
	light =
	{
		Color(0.0f, 0.0f, 0.0f, 1.0f), //Ambient
		Color(0.0f, 0.0f, 1.0f, 1.0f), //Diffuse
		Color(0.0f, 0.0f, 0.7f, 1.0f), //Specular
		Color(0.0f, 0.0f, 0.7f, 1.0f), //Emissive
		Vector3(-30, 10, -30), 5.0f, 0.9f // 위치, 범위, 강도
	};
	Lighting::Get()->AddPointLight(light);

	light =
	{
		Color(0.0f, 0.0f, 0.0f, 1.0f),
		Color(1.0f, 0.0f, 0.0f, 1.0f),
		Color(0.6f, 0.2f, 0.0f, 1.0f),
		Color(0.6f, 0.3f, 0.0f, 1.0f),
		Vector3(15, 10, -30), 10.0f, 0.3f
	};
	Lighting::Get()->AddPointLight(light);

	light =
	{
		Color(0.0f, 0.0f, 0.0f, 1.0f), //Ambient
		Color(0.0f, 1.0f, 0.0f, 1.0f), //Diffuse
		Color(0.0f, 0.7f, 0.0f, 1.0f), //Specular
		Color(0.0f, 0.7f, 0.0f, 1.0f), //Emissive
		Vector3(-5, 1, -17.5f), 5.0f, 0.9f
	};
	Lighting::Get()->AddPointLight(light);

	light =
	{
		Color(0.0f, 0.0f, 0.0f, 1.0f),
		Color(0.0f, 0.0f, 1.0f, 1.0f),
		Color(0.0f, 0.0f, 0.7f, 1.0f),
		Color(0.0f, 0.0f, 0.7f, 1.0f),
		Vector3(-10, 1, -17.5f), 5.0f, 0.9f
	};
	Lighting::Get()->AddPointLight(light);
}

void BillboardDemo::SpotLighting()
{
	SpotLight light;
	light =
	{
		Color(0.3f, 1.0f, 0.0f, 1.0f),
		Color(0.7f, 1.0f, 0.0f, 1.0f),
		Color(0.3f, 1.0f, 0.0f, 1.0f),
		Color(0.3f, 1.0f, 0.0f, 1.0f),
		Vector3(-15, 20, -30), 25.0f,
		Vector3(0, -1, 0), 30.0f, 0.4f
	};
	Lighting::Get()->AddSpotLight(light);

	light =
	{
		Color(1.0f, 0.2f, 0.9f, 1.0f),
		Color(1.0f, 0.2f, 0.9f, 1.0f),
		Color(1.0f, 0.2f, 0.9f, 1.0f),
		Color(1.0f, 0.2f, 0.9f, 1.0f),
		Vector3(0, 20, -30), 30.0f,
		Vector3(0, -1, 0), 40.0f, 0.55f
	};
	Lighting::Get()->AddSpotLight(light);
}

void BillboardDemo::Pass(UINT mesh, UINT model, UINT anim)
{
	for (MeshRender* temp : meshes)
		temp->Pass(mesh);

	for (ModelRender* temp : models)
		temp->Pass(model);

	for (ModelAnimator* temp : animators)
		temp->Pass(anim);
}

 

 

 

 

 

83_Billboard.fx


더보기
#include "00_Global.fx"
#include "00_Light.fx"

// Render 정보를 직접 써서 사용할것이다.

struct VertexInput
{
    float4 Position : Position;
    float2 Uv : Uv;
    float2 Scale : Scale; // 면의 크기
};

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

VertexOutput VS(VertexInput input)
{
    VertexOutput output;
    
    // 방향을 만든다.
    // Position에는 정점 6개가 같은것이 들어올 것이다.(-0.5, +0.5 이런게 아니라 다 같은 위치)
    float4 position = WorldPosition(input.Position);

    // 방향을 만들고 UV를 이용해 전개 시켜나갈것이다.
    float3 up = float3(0, 1, 0);
    
    // 전방방향이 카메라를 향하도록 한다.
    // float3 forward = float3(0, 0, 1);
    // 위치가 카메라를 바라보는 방향이됨
    float3 forward =  position.xyz - ViewPosition();
    
    // 외적을 하든, float3(1, 0, 0)을 주든 동일
    float3 right = normalize(cross(up, forward));
    
    
    // 밑에 과정수행시 world 정점의 위치가 사방으로 퍼지게 된다.
    // 0번 정점일때는 왼쪽 하단으로 가고, 1번 정점일때는 왼쪽 상단으로 간다. 2번 정점일 때는 우측 하단으로 가고... 
    
    // 이걸 가지고 Position을 전개
    // 위치는 전부 동일하게 줄것이고(정점), Uv는 이제까지 해오던 것처럼 그대로 준다.(0번 정점이 0,0 1번이 0,1) 
    // right가 1, 0, 0이고 예를 들어 input Uv.x가 0이라면 -0.5 * 1 = -0.5가 됨(즉, 왼쪽 방향의 정점을 만들 수 있게 된다.), 
    // scale을 곱해주면 -0.5 ~ -2까지 간다던가 크기를 늘릴 수 있다.
    position.xyz += (input.Uv.x - 0.5f) * right * input.Scale.x;
    // v는 뒤집어줘야한다.(뒤집어져 있어서) -> 2D, 3D 좌표는 뒤집어져 있다.
    position.xyz += (1.0f - input.Uv.y - 0.5f) * up * input.Scale.y;
    // w에는 1이 들어가있지만 넣어준다.
    position.w = 1.0f;
    
    
    // View, Projection 변환
    output.Position = ViewProjection(position);
    output.Uv = input.Uv;
    
    return output;
}

float4 PS(VertexOutput input) : SV_Target
{
    float4 diffuse = DiffuseMap.Sample(LinearSampler, input.Uv);
    
    // 클리핑(Cliping)이란? 화면의 픽셀을 OM단께로 넘기지 않고 픽셀 쉐이더에서 파기해서
    // 해당 픽셀을 그리지 않는 방식
    // 0이하의 값은 나타나지 않음
    // 디자이너가 알파값을 투명값으로 0으로 주는 경우도 있지만 오차값으로 주는 경우도 있어서
    // 0.3이하일시 파기
    // 1. clip(diffuse.a - 0.3);
    
    // 2. discard(만나면 파기)
    if (diffuse.a < 0.3)
        discard;
    
    // Vertex에서도 Vertex를 파기시키는 방법이 존재 -> Culling
    
    return diffuse;
}

technique11 T0
{
    P_VP(P0, VS, PS)
}

 

 

 

 

 

 

 

 

 

 

 

결과


풀들이 내가 바라보는 방향(카메라 방향)으로 회전 되는 것을 볼 수 있다.