서의 공간
[T21.3] Box, Hill, Sphere, Cylinder, Skull, Wave 본문
[T21.3] Box, Hill, Sphere, Cylinder, Skull, Wave
홍서의 2020. 12. 1. 18:45공통 개념
1. 각 오브젝트의 클래스보다 더 중요한 것은, 그 오브젝트의 기하구조를 만드는 클래스이다. Geometry로 시작하는 클래스가 바로 이러한 클래스인데. 오브젝트 클래스는 결국 정점에 대해여 Bindable한 모든 것을 파이프라인에 바인드 하는 것이 하는 일이 거의 전부이다. 반면 Geometry~ 클래스는 실제 오브젝트의 구조를 결정짓고 정점의 인덱스 리스트까지 만들기 때문에 이 부분에 중점을 두고 살펴봐야 할 것이다. Box는 앞서 이미 보았으므로 생략했다.
2. Rasterizer Stage: 정점 파이프라인을 지나 출력된 프리미티브 데이터는 래스터라이저 스테이지에 보내져 래스터라이저에 의 해 렌더링 할 픽셀(텍셀)단위로 분해된다. 이때 래스터라이저에서는 다음과 같은 처리를 수행한다.
- 보이지 않는 프리미티브 제거(은면제거, 컬링 처리)
- 좌표의 뷰포트 변환
- 시저 테스트
- 깊이 바이어스 계산
- 프리미티브들을 렌더 타겟 상의 텍셀 단위로 변경
- 멀티 샘플링이나 필 모드의 설정에 따른 처리
- RS 스테이지의 동작은 [RasterizerState] 오브젝트에서 설정한다.
Hill
핵심 개념
MakeTesselated() 함수는 4개의 파라미터를 가진다. 깊이, 너비, 너비에서 정점의 개수, 깊이에서 정점의 개수 이 4개이다. 그리드의 중점은 시작점이 아니기 때문에 좌상 단부터 시작하여 정점의 위치와 인덱스를 계산해주어야 한다. 아래 그림을 살펴보면 \(\mathbf{v_{00}}\)의 x좌표는 전체 너비의 절반의 -X 방향이고, z좌표는 전체 깊이의 절반의 +Z방향이다. 두 번째 그림은 인덱스가 어떻게 계산되는지 그림이다. 삼각형을 이루는 세 개의 인덱스는 시계 방향인 것을 다시 한번 기억하자.
또 하나 살펴봐야 할 것은 언덕을 그리기 위해 높이를 결정하는 부분이다. 먼저 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가지 이다. 여기서 조각 수와 스택 수는 다음 그림을 참고한다. 두 번째 그림은 원기둥 정점의 인덱스를 계산하는 방법이 나와있다.
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 |