서의 공간

[T21.3] Box, Hill, Sphere, Cylinder, Skull, Wave 본문

Graphics API/DirectX 11 - Chili

[T21.3] Box, Hill, Sphere, Cylinder, Skull, Wave

홍서의 2020. 12. 1. 18:45
[영상 21.3.1] 여러가지 오브젝트 그리기

공통 개념

1. 각 오브젝트의 클래스보다 더 중요한 것은, 그 오브젝트의 기하구조를 만드는 클래스이다. Geometry로 시작하는 클래스가 바로 이러한 클래스인데. 오브젝트 클래스는 결국 정점에 대해여 Bindable한 모든 것을 파이프라인에 바인드 하는 것이 하는 일이 거의 전부이다. 반면 Geometry~ 클래스는 실제 오브젝트의 구조를 결정짓고 정점의 인덱스 리스트까지 만들기 때문에 이 부분에 중점을 두고 살펴봐야 할 것이다. Box는 앞서 이미 보았으므로 생략했다.

2. Rasterizer Stage: 정점 파이프라인을 지나 출력된 프리미티브 데이터는 래스터라이저 스테이지에 보내져 래스터라이저에 의 해 렌더링 할 픽셀(텍셀)단위로 분해된다. 이때 래스터라이저에서는 다음과 같은 처리를 수행한다.

  • 보이지 않는 프리미티브 제거(은면제거, 컬링 처리)
  • 좌표의 뷰포트 변환
  • 시저 테스트
  • 깊이 바이어스 계산
  • 프리미티브들을 렌더 타겟 상의 텍셀 단위로 변경
  • 멀티 샘플링이나 필 모드의 설정에 따른 처리
  • RS 스테이지의 동작은 [RasterizerState] 오브젝트에서 설정한다.

Hill

더보기

핵심 개념

MakeTesselated() 함수는 4개의 파라미터를 가진다. 깊이, 너비, 너비에서 정점의 개수, 깊이에서 정점의 개수 이 4개이다. 그리드의 중점은 시작점이 아니기 때문에 좌상 단부터 시작하여 정점의 위치와 인덱스를 계산해주어야 한다. 아래 그림을 살펴보면 \(\mathbf{v_{00}}\)의 x좌표는 전체 너비의 절반의 -X 방향이고, z좌표는 전체 깊이의 절반의 +Z방향이다. 두 번째 그림은 인덱스가 어떻게 계산되는지 그림이다. 삼각형을 이루는 세 개의 인덱스는 시계 방향인 것을 다시 한번 기억하자.

[그림 21.3.1] 정점의 계산
[그림 21.3.2] 인덱스 계산

또 하나 살펴봐야 할 것은 언덕을 그리기 위해 높이를 결정하는 부분이다. 먼저 HillEx.cpp에서 정점버퍼를 만들기 전에 vertices의 높이를 새롭게 계산하는 부분을 살펴보자. GetHeight() 함수이고 파라미터로 x, z 값을 받는다. 그리고 그 높이 값으로 각 정점의 색상을 결정한다.

 

GeometryGrid.h

#pragma once
#include "IndexedTriangleList.h"
#include "ChiliMath.h"

class GeometryGrid
{
public:
	template<class V>
	static IndexedTriangleList<V> MakeTesselated(float width, float depth, int m, int n)
	{
		namespace dx = DirectX;

		int vertexCount = m * n;
		int faceCount = (m - 1) * (n - 1) * 2;

		float halfWidth = 0.5f * width;
		float halfDepth = 0.5f * depth;

		float d_x = width / (n - 1);
		float d_z = depth / (m - 1);

		std::vector<V> vertices(vertexCount);
		for (int i = 0; i < m; ++i)
		{
			float z = halfDepth - i * d_z;
			for (int j = 0; j < n; ++j)
			{
				float x = -halfWidth + j * d_x;
				vertices[i * n + j].pos = dx::XMFLOAT3(x, 0.0f, z);
			}
		}

		std::vector<unsigned short> indices;
		indices.resize(faceCount * 3);
		int k = 0;
		for (int i = 0; i < m - 1; ++i)
		{
			for (int j = 0; j < n - 1; ++j)
			{
				indices[k] = i * n + j;
				indices[k + 1] = i * n + j + 1;
				indices[k + 2] = (i + 1) * n + j;
				indices[k + 3] = (i + 1) * n + j;
				indices[k + 4] = i * n + j + 1;
				indices[k + 5] = (i + 1) * n + j + 1;
				k += 6;
			}
		}

		return{ std::move(vertices), std::move(indices) };
	}
};

HillEx.h

#pragma once
#include "DrawableBase.h"

class HillEx : public DrawableBase<HillEx>
{
public:
	HillEx(Graphics& gfx, std::mt19937& rng,
		std::uniform_real_distribution<float>& adist,
		std::uniform_real_distribution<float>& ddist,
		std::uniform_real_distribution<float>& odist,
		std::uniform_real_distribution<float>& rdist);
	void Update(float dt) noexcept override;
	DirectX::XMMATRIX GetTransformXM() const noexcept override;
	float GetHeight(float x, float z) const;
private:
	float r;
	float roll = 0.0f;
	float pitch = 0.0f;
	float yaw = 0.0f;
	float theta;
	float phi;
	float chi;

	float droll;
	float dpitch;
	float dyaw;
	float dtheta;
	float dphi;
	float dchi;
};

HillEx.cpp

#include "HillEx.h"
#include "BindableBase.h"
#include "GraphicsThrowMacros.h"
#include "GeometryGrid.h"

HillEx::HillEx(Graphics & gfx,
	std::mt19937 & rng,
	std::uniform_real_distribution<float>& adist,
	std::uniform_real_distribution<float>& ddist,
	std::uniform_real_distribution<float>& odist,
	std::uniform_real_distribution<float>& rdist)
	:
	r(0),
	droll(ddist(rng)),
	dpitch(ddist(rng)),
	dyaw(ddist(rng)),
	dphi(odist(rng)),
	dtheta(odist(rng)),
	dchi(odist(rng)),
	chi(0),
	theta(0),
	phi(0)
{
	namespace dx = DirectX;

	if (!IsStaticInitialized())
	{
		struct Vertex
		{
			dx::XMFLOAT3 pos;
			dx::XMFLOAT4 color;
		};
		auto model = GeometryGrid::MakeTesselated<Vertex>(160.0f, 160.0f, 50, 50);
		
		for (int i = 0; i < model.vertices.size(); ++i) 
		{
			dx::XMFLOAT3 p = model.vertices[i].pos;

			p.y = GetHeight(p.x, p.z);

			model.vertices[i].pos = p;

			if (p.y < -10.0f)
			{
				model.vertices[i].color = dx::XMFLOAT4(1.0f, 0.96f, 0.62f, 1.0f);
			}
			else if (p.y < 5.0f)
			{
				model.vertices[i].color = dx::XMFLOAT4(0.48f, 0.77f, 0.46f, 1.0f);
			}
			else if (p.y < 12.0f)
			{
				model.vertices[i].color = dx::XMFLOAT4(0.1f, 0.48f, 0.19f, 1.0f);
			}
			else if (p.y < 20.0f)
			{
				model.vertices[i].color = dx::XMFLOAT4(1.0f, 0.96f, 0.62f, 1.0f);
			}
			else
			{
				model.vertices[i].color = dx::XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
			}
		}

		AddStaticBind(std::make_unique<VertexBuffer>(gfx, model.vertices));

		auto pvs = std::make_unique<VertexShader>(gfx, L"VertexShader.cso");
		auto pvsbc = pvs->GetBytecode();
		AddStaticBind(std::move(pvs));

		AddStaticBind(std::make_unique<PixelShader>(gfx, L"PixelShader.cso"));
		
		AddStaticIndexBuffer(std::make_unique<IndexBuffer>(gfx, model.indices));

		const std::vector<D3D11_INPUT_ELEMENT_DESC> ied = 
		{
			{"Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
			{"Color",    0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
		};
		AddStaticBind(std::make_unique<InputLayout>(gfx, ied, pvsbc));

		AddStaticBind(std::make_unique<Topology>(gfx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST));
	}
	else
	{
		SetIndexFromStatic();
	}

	AddBind(std::make_unique<TransformCbuf>(gfx, *this));
}

void HillEx::Update(float dt) noexcept
{}

DirectX::XMMATRIX HillEx::GetTransformXM() const noexcept
{
	namespace dx = DirectX;
	return dx::XMMatrixRotationRollPitchYaw(pitch, yaw, roll) *
		dx::XMMatrixTranslation(r, 0.0f, 0.0f) *
		dx::XMMatrixRotationRollPitchYaw(theta, phi, chi);
}

float HillEx::GetHeight(float x, float z) const
{
	return 0.3f * (z * sinf(0.1f * x) + x * cosf(0.1f * z));
}

Cylinder

더보기

핵심 개념

원기둥을 그리기 위해 필요한 파라미터는 아랫면 반지름, 윗면 반지름, 높이, 조각 수, 스택 수 5가지 이다. 여기서 조각 수와 스택 수는 다음 그림을 참고한다. 두 번째 그림은 원기둥 정점의 인덱스를 계산하는 방법이 나와있다.

[그림 21.3.3] 원기둥의 조각과 스택
[그림 21.3.4] 정점의 인덱스 계산

 GeometryCylinder.h

#pragma once
#include "IndexedTriangleList.h"
#include "ChiliMath.h"

class GeometryCylinder
{
public:
	template<class V>
	static IndexedTriangleList<V> MakeTesselated(float bottomRadius,
		float topRadius, float height, unsigned int sliceCount,
		unsigned int stackCount)
	{
		namespace dx = DirectX;

		// 고리에서 한 조각의 중심각의 크기
		float dTheta = 2.0f * PI / sliceCount;

		// 옆면 만들기
		std::vector<V> vertices;
		// 더미 하나의 높이
		float stackHeight = height / stackCount;
		// 한 단계 위로 올라갈 때의 반지름의 변화량 
		float radiusStep = (topRadius - bottomRadius) / stackCount;
		// 고리의 개수
		unsigned int ringCount = stackCount + 1;
		// 최하단 고리에서부터 최상단 고리까지 훑으면서 각 고리의 정점들을 계산
		for (unsigned int i = 0; i < ringCount; ++i)
		{
			// 현재 정점의 y좌표
			// -0.5f * height는 시작 y좌표임(0번째 최하단 고리부터 시작)
			float y = -0.5f * height + i * stackHeight;
			// 단계별 고리의 반지름 길이
			float r = bottomRadius + i * radiusStep;
			// 현재 고리의 정점들 계산
			for (unsigned int j = 0; j <= sliceCount; ++j)
			{
				// 극좌표로 계산한 (x, z), 어차피 y는 윗 단계 for문에 고정됨
				float c = cosf(j * dTheta);
				float s = sinf(j * dTheta);
				
				// vertex구조체의 속성을 모른다. pos 말고는.
				// push_back()대신 이렇게 pos 속성에만 값을 설정한다.
				vertices.emplace_back();
				vertices.back().pos = dx::XMFLOAT3(r * c, y, r * s);
			}
		}
		// 옆면 인덱스 리스트
		unsigned int ringVertexCount = sliceCount + 1;
		std::vector<unsigned short> indices;
		for (unsigned int i = 0; i < stackCount; ++i) 
		{
			for (unsigned int j = 0; j < sliceCount; ++j)
			{
				indices.emplace_back(i * ringVertexCount + j);
				indices.emplace_back((i + 1) * ringVertexCount + j);
				indices.emplace_back((i + 1) * ringVertexCount + j + 1);

				indices.emplace_back(i * ringVertexCount + j);
				indices.emplace_back((i + 1) * ringVertexCount + j + 1);
				indices.emplace_back(i * ringVertexCount + j + 1);
			}
		}

		// 윗면 만들기
		unsigned int baseIndex = (unsigned int)vertices.size();
		float uy = 0.5f * height;
		// 고리 정점들을 복제해 마개 정점들을 만든다.
		for (unsigned int i = 0; i <= sliceCount; ++i)
		{
			float x = topRadius * cosf(i * dTheta);
			float z = topRadius * sinf(i * dTheta);

			vertices.emplace_back();
			vertices.back().pos = dx::XMFLOAT3(x, uy, z);
		}
		// 위 마개의 중심 정점
		vertices.emplace_back();
		vertices.back().pos = dx::XMFLOAT3(0.0f, uy, 0.0f);
		// 위 마개의 인덱스
		unsigned int centerIndex = (unsigned int)vertices.size() - 1;
		// 윗면 인덱스 리스트
		for (unsigned int i = 0; i < sliceCount; ++i)
		{
			indices.emplace_back(centerIndex);
			indices.emplace_back(baseIndex + i + 1);
			indices.emplace_back(baseIndex + i);
		}

		// 아랫면 만들기
		baseIndex = (unsigned int)vertices.size();
		float by = -0.5f * height;
		for (unsigned int i = 0; i <= sliceCount; ++i)
		{
			float x = bottomRadius * cosf(i * dTheta);
			float z = bottomRadius * sinf(i * dTheta);

			vertices.emplace_back();
			vertices.back().pos = dx::XMFLOAT3(x, by, z);
		}
		vertices.emplace_back();
		vertices.back().pos = dx::XMFLOAT3(0.0f, by, 0.0f);
		centerIndex = (unsigned int)vertices.size() - 1;
		for (unsigned int i = 0; i < sliceCount; ++i)
		{
			//인덱스 순서가 윗면과 다르다, 바닥을 향하는 방향이 앞 방향이어야 함
			indices.emplace_back(centerIndex);
			indices.emplace_back(baseIndex + i);
			indices.emplace_back(baseIndex + i + 1);
		}
		return { std::move(vertices), std::move(indices) };
	}
};

Sphere

더보기

핵심 개념

구의 구조를 결정하기 위해 필요한 파라미터는 반지름, 슬라이스 수, 스택 수이다. 

 

GeometrySphere.h

#pragma once
#include "IndexedTriangleList.h"
#include "ChiliMath.h"

class GeometrySphere
{
public:
	template<class V>
	static IndexedTriangleList<V> MakeTesselated(float radius, 
		unsigned int sliceCount, unsigned int stackCount)
	{
		namespace dx = DirectX;

		//////// 정점 구조 만들기
		std::vector<V> vertices;
		// topVertex 추가
		vertices.emplace_back();
		vertices.back().pos = dx::XMFLOAT3(0.0f, +radius, 0.0f);

		float phiStep = PI / stackCount;
		float thetaStep = 2.0f * PI / sliceCount;

		for (unsigned int i = 1; i <= stackCount - 1; ++i)
		{
			float phi = i * phiStep;
			// 고리의 정점들
			for (unsigned int j = 0; j <= sliceCount; ++j)
			{
				float theta = j * thetaStep;
				// 구면 좌표계에서 카테시안 좌표계로
				dx::XMFLOAT3 v(radius * sinf(phi) * cosf(theta),
					radius * cosf(phi),
					radius * sinf(phi) * sinf(theta));

				vertices.emplace_back();
				vertices.back().pos = v;
			}
		}
		// bottomVertex 추가
		vertices.emplace_back();
		vertices.back().pos = dx::XMFLOAT3(0.0f, -radius, 0.0f);

		///////// 인덱스 리스트 만들기
		// 구의 top 스택 계산
		std::vector<unsigned short> indices;
		for (unsigned int i = 1; i <= sliceCount; ++i)
		{
			indices.push_back(0);
			indices.push_back(i + 1);
			indices.push_back(i);
		}
		// 중간 스택들 계산
		unsigned int baseIndex = 1;
		unsigned int ringVertexCount = sliceCount + 1;
		for (unsigned int i = 0; i < stackCount - 2; ++i)
		{
			for (unsigned int j = 0; j < sliceCount; ++j)
			{
				indices.push_back(baseIndex + i * ringVertexCount + j);
				indices.push_back(baseIndex + i * ringVertexCount + j + 1);
				indices.push_back(baseIndex + (i + 1) * ringVertexCount + j);

				indices.push_back(baseIndex + (i + 1) * ringVertexCount + j);
				indices.push_back(baseIndex + i * ringVertexCount + j + 1);
				indices.push_back(baseIndex + (i + 1) * ringVertexCount + j + 1);
			}
		}
		// bottom 스택 계산
		unsigned int southPoleIndex = (unsigned int)vertices.size() - 1;
		baseIndex = southPoleIndex - ringVertexCount;
		for (unsigned int i = 0; i < sliceCount; ++i)
		{
			indices.push_back(southPoleIndex);
			indices.push_back(baseIndex + i);
			indices.push_back(baseIndex + i + 1);
		}
		return { std::move(vertices), std::move(indices) };
	}
};

Skull

더보기

핵심 개념

Skull은 txt 파일에 저장되어 있는 모델의 정보를 불러와, vertices와 indices를 구한다. 정점은 31076개이고 삼각형은 60339개이다.

 

GeometrySkull.h

#pragma once
#include "IndexedTriangleList.h"
#include "ChiliMath.h"
#include <Windows.h>
#include <fstream>

class GeometrySkull
{
public:
	template<class V>
	static IndexedTriangleList<V> Make()
	{
		namespace dx = DirectX;

		std::ifstream fin("Models/skull.txt");

		if (!fin)
		{
			MessageBox(0, "Models/skull.txt not found", 0, 0);
			return{};
		}

		UINT vcount = 0;
		UINT tcount = 0;
		std::string ignore;

		fin >> ignore >> vcount;
		fin >> ignore >> tcount;
		fin >> ignore >> ignore >> ignore >> ignore;

		float nx, ny, nz;
		dx::XMFLOAT4 white(1.0f, 1.0f, 1.0f, 1.0f);

		std::vector<V> vertices(vcount);
		for (UINT i = 0; i < vcount; ++i)
		{
			fin >> vertices[i].pos.x >> vertices[i].pos.y >> vertices[i].pos.z;
			vertices[i].color = white;
			fin >> nx >> ny >> nz;
		}

		fin >> ignore;
		fin >> ignore;
		fin >> ignore;

		UINT mSkullIndexCount = 3 * tcount;
		std::vector<unsigned short> indices(mSkullIndexCount);
		for (UINT i = 0; i < tcount; ++i)
		{
			fin >> indices[i * 3 + 0] >> indices[i * 3 + 1] >> indices[i * 3 + 2];
		}

		fin.close();

		return { std::move(vertices), std::move(indices) };
	}
};

Wave

더보기

추가 예정

'Graphics API > DirectX 11 - Chili' 카테고리의 다른 글

[T24.1] Lighting  (0) 2020.12.05
[T24.0] Lighting  (0) 2020.12.05
[T21.2] Drawble System  (0) 2020.11.30
[T21.1] Bindable System  (0) 2020.11.29
[T20] Solid cube, Depth and stencil  (0) 2020.11.29
Comments