DirectX 11에서 빌보드는 항상 카메라를 향하고 있는 2D 이미지로, 3D 개체의 환영을 만든다. 빌보드는 폭발, 연기 또는 화재와 같은 입자 효과 등에 사용된다. 빌보드를 만들려면 이미지를 3D 공간에 배치하고 법선이 카메라의 보기 방향과 수직이 되도록 정렬한다. 이렇게 하면 위치나 방향에 관계없이 이미지가 항상 카메라를 향하게 된다. 그러면 빌보드가 적절한 크기로 조정되고 텍스처 쿼드로 렌더링된다.

 

목차

     

     


     

     

     

    Billboard

     

    DirectX 11에서 빌보드는 항상 카메라를 향하고 있는 2D 이미지로, 3D 개체의 환영을 만든다. 빌보드는 폭발, 연기 또는 화재와 같은 입자 효과 등에 사용된다.

     

    빌보드를 만들려면 이미지를 3D 공간에 배치하고 법선이 카메라의 보기 방향과 수직이 되도록 정렬한다. 이렇게 하면 위치나 방향에 관계없이 이미지가 항상 카메라를 향하게 된다. 그러면 빌보드가 적절한 크기로 조정되고 텍스처 쿼드로 렌더링된다.

     

    사실적인 입자 효과를 얻기 위해 광고판을 투명도, 혼합 및 음영 처리와 같은 다른 기술과 결합할 수 있다. 예를 들어 연기 입자는 알파 채널이 있는 투명 텍스처를 사용하여 희미하고 불규칙한 연기 모양을 시뮬레이션할 수 있다. 또한 블렌딩을 사용하여 입자를 뒤에 있는 장면과 혼합할 수 있으며 음영을 사용하여 입자에 하이라이트와 그림자를 만들 수 있다.

     

    빌보드는 실시간으로 동적인 입자 효과를 만들기에 효과적인 방법이다.

     

     

     

    면에 필요한 개수를 구한 후 Draw Index → 인덱스를 정점으로 사용 →  Uv를 이용하여 면처럼 만든다.

     

    Shader
      Billboard.fx 생성
    Framework
      Objects
      Billboard.h .cpp 생성
    UnitTest
      Objects
      BillboardDemo.h. cpp 생성

     

    tangent는 u방향에 Mapping 시키는게 정석적인 방법이다.

     


     

     

    Billboard.fx

     

    Billboard.fx

    더보기
    #include "00_Global.fx"
    #include "00_Light.fx"
    
    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;
        
        float4 position = WorldPosition(input.Position);
        
        float3 up = float3(0, 1, 0);
        //float3 forward = float3(0, 0, 1);
        float3 forward = position.xyz - ViewPosition(); //카메라를 바라보는 방향
        float3 right = normalize(cross(up, forward)); //(1, 0, 0)
        
        position.xyz += (input.Uv.x - 0.5f) * right * input.Scale.x;
        position.xyz += (1.0f - input.Uv.y - 0.5f) * up * input.Scale.y; //2D 3D좌표는 뒤집혀있다.
        position.w = 1.0f;
        
        output.Position = ViewProjection(position);
        output.Uv = input.Uv;
    
        return output;
    }
    
    float4 PS(VertexOutput input) : SV_Target
    {
        float4 diffuse = DiffuseMap.Sample(LinearSampler, input.Uv);
        
        //clip(diffuse.a - 0.3f);
        if (diffuse.a < 0.3)
            discard;
        
        return diffuse;
    }
    
    technique11 T0
    {
        P_VP(P0, VS, PS)
    }

     

     


     

    Billboard

     

    Billboard.h

    더보기
    #pragma once
    #define MAX_BILLBOARD_COUNT 100000
    
    class Billboard : public Renderer
    {
    public:
    	Billboard(wstring file);
    	~Billboard();
    
    	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; //drawCount는 그릴 개수
    	UINT prevCount = 0;
    
    	Texture* texture;
    	ID3DX11EffectShaderResourceVariable* sDiffuseMap;
    };

     

     

    Billboard.cpp

    더보기
    #include "Framework.h"
    #include "Billboard.h"
    
    Billboard::Billboard(wstring file)
    	: Renderer(L"83_Billboard.fxo")
    { 
    	vertexCount = MAX_BILLBOARD_COUNT * 4; 
    	vertices = new VertexBillboard[vertexCount]; //vertices 동적할당
    	
    	vertexBuffer = new VertexBuffer(vertices, vertexCount, sizeof(VertexBillboard), 0, true);
    
    
    	indexCount = MAX_BILLBOARD_COUNT * 6;
    	indices = new UINT[indexCount]; //indices 동적할당
    
    	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"); //DiffuseMap 불러옴
    }
    
    Billboard::~Billboard()
    {
    	SafeDeleteArray(vertices);
    	SafeDeleteArray(indices);
    
    	SafeDelete(texture);
    }
    
    void Billboard::Update()
    {
    	Super::Update();
    }
    
    void Billboard::Render()
    {
    	if (drawCount != prevCount)
    	{
    		prevCount = drawCount;
    
    		D3D11_MAPPED_SUBRESOURCE subResource;
    		D3D::GetDC()->Map(vertexBuffer->Buffer(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
    		{
    			memcpy(subResource.pData, vertices, sizeof(VertexBillboard) * vertexBuffer->Count());
    		}
    		D3D::GetDC()->Unmap(vertexBuffer->Buffer(), 0);
    	}
    
    	Super::Render();
    
    
    	sDiffuseMap->SetResource(texture->SRV());
    	shader->DrawIndexed(0, Pass(), drawCount * 6);
    }
    
    void Billboard::Add(Vector3 & position, Vector2 & scale)
    {
    	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);
    	
    	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

     

    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;
    
    	Billboard* 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);
    	((Freedom *)Context::Get()->GetCamera())->Speed(50, 2);
    
    
    	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 Billboard(L"Terrain/grass_14.tga");
    
    	for (UINT i = 0; i < 1200; i++)
    	{
    		Vector2 scale = Math::RandomVec2(1, 3);
    		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(0.15f, 0.15f, 0.15f, 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(0.15f, 0.15f, 0.15f, 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(0.15f, 0.15f, 0.15f, 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);
    }

     

     

     


     

     

    실행화면