Height Map
지면을 까는 Grid를 지난 포스팅에 해봤는데 이제 이것을 산 처럼 어디는 내려가고 어디는 올라가는 형식의 높이가 있는 맵을 한번 테스트 해보자. 유니티에서는 Terrain 툴이 존재해서 이것을 할 수 있다.
이것을 이용할 것인데 이는 terrain의 높이를 8비트로 저장한 사진이다. 즉 0,0 부터 맵 끝까지 어느 지형의 높이를 0부터 255의 숫자로 나타낸 것이다. 이것을 우린 파싱해서 실제 맵에 적용해 줘야하는 것이다.
이걸 CPU에서 연산해서 가지고 있다가 필요할 때 마다 GPU에 넘겨주는 방식도 있고 GPU 자체가 들고있어서 필요할 때마다 사용하는 방식 등 여러가지가 있지만 전자로 해보겠다.
#include "pch.h"
#include "07. HeightMapDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
void HeightMapDemo::Init()
{
_shader = make_shared<Shader>(L"06. Terrain.fx");
// Texture
_heightMap = RESOURCES->Load<Texture>(L"Height", L"..\\Resources\\Textures\\Terrain\\height.png");
_texture = RESOURCES->Load<Texture>(L"Grass", L"..\\Resources\\Textures\\Terrain\\grass.jpg");
const int32 width = _heightMap->GetSize().x;
const int32 height = _heightMap->GetSize().y;
const DirectX::ScratchImage& info = _heightMap->GetInfo();
uint8* pixelBuffer = info.GetPixels();
// Object
_geometry = make_shared<Geometry<VertexTextureData>>();
GeometryHelper::CreateGrid(_geometry, width, height);
// CPU
{
vector<VertexTextureData>& v = const_cast<vector<VertexTextureData>&>(_geometry->GetVertices());
for (int32 z = 0; z < height; z++)
{
for (int32 x = 0; x < width; x++)
{
int32 idx = width * z + x;
uint8 height = pixelBuffer[idx] / 255.f * 25.f;
v[idx].position.y = height; // 높이 보정
}
}
}
_vertexBuffer = make_shared<VertexBuffer>();
_vertexBuffer->Create(_geometry->GetVertices());
_indexBuffer = make_shared<IndexBuffer>();
_indexBuffer->Create(_geometry->GetIndices());
// Camera
_camera = make_shared<GameObject>();
_camera->GetOrAddTransform();
_camera->AddComponent(make_shared<Camera>());
_camera->AddComponent(make_shared<CameraScript>());
_camera->GetTransform()->SetPosition(Vec3(0.f, 5.f, 0.f));
_camera->GetTransform()->SetRotation(Vec3(25.f, 0.f, 0.f));
}
void HeightMapDemo::Update()
{
_camera->Update();
}
void HeightMapDemo::Render()
{
_shader->GetMatrix("World")->SetMatrix((float*)&_world);
_shader->GetMatrix("View")->SetMatrix((float*)&Camera::S_MatView);
_shader->GetMatrix("Projection")->SetMatrix((float*)&Camera::S_MatProjection);
_shader->GetSRV("Texture0")->SetResource(_texture->GetComPtr().Get());
uint32 stride = _vertexBuffer->GetStride();
uint32 offset = _vertexBuffer->GetOffset();
DC->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
DC->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
_shader->DrawIndexed(0, 1, _indexBuffer->GetCount(), 0, 0);
}
언뜻보면 높이가 잘 보이지 않지만 실제로 보면 높이가 보인다.
Normal
앞으로 아주 중요한 역할을 하게될 Normal에 대해 알아보자.
물체의 어떤 정점에서 수직인 단위벡터를 Normal 벡터라고 한다. 이 Normal 벡터가 필요한 이유가 뭘까?
나중에는 다양한 형태의 조명 연산을 해줄텐데 그때 사용하게 될 것이다.
만약 어떤 그리드가 있다고 해보자. 그때 태양이 햇빛을 바닥으로 쏘는데 그리드의 일직선으로 내리쬐는 빛은 쎄게 먹고 거의 90도로 먹는 빛은 약하게 먹어야할것이다. 이 것을 구현하려면 어떻게 해야할까
우리가 그리드의 Normal 벡터를 할고 빛이 오는 벡터를 알고 있다면 충분히 구해낼 수 있다.
dot( -> -빛 벡터, ->Normal 벡터) 이 두개를 내적해주면 결국 두 벡터의 cos각 만 남게 된다.
cos의 값이 가장 큰 순간은 머리위에서 햇빛이 온다는 얘기고
cos의 값이 가장 작은 순간은 앞에서(90도)에서 햇빛이 온다는 얘기이다.
즉, cos 값에 따라 조명의 세기를 조절할 수 있다는 것이다.
이렇게 Geometry 를 수정할 수 있다.
void GeometryHelper::CreateCube(shared_ptr<Geometry<VertexTextureNormalData>> geometry)
{
float w2 = 0.5f;
float h2 = 0.5f;
float d2 = 0.5f;
vector<VertexTextureNormalData> vtx(24);
// 앞면
vtx[0] = VertexTextureNormalData{ (Vec3(-w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f)) };
vtx[1] = VertexTextureNormalData{ (Vec3(-w2, +h2, -d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f)) };
vtx[2] = VertexTextureNormalData{ (Vec3(+w2, +h2, -d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f)) };
vtx[3] = VertexTextureNormalData{ (Vec3(+w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f)) };
// 뒷면
vtx[4] = VertexTextureNormalData{(Vec3(-w2, -h2, +d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 0.0f, 1.0f))};
vtx[5] = VertexTextureNormalData{(Vec3(+w2, -h2, +d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 0.0f, 1.0f))};
vtx[6] = VertexTextureNormalData{(Vec3(+w2, +h2, +d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f))};
vtx[7] = VertexTextureNormalData{(Vec3(-w2, +h2, +d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f))};
// 윗면
vtx[8] = VertexTextureNormalData{ (Vec3(-w2, +h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 1.0f, 0.0f)) };
vtx[9] = VertexTextureNormalData{ (Vec3(-w2, +h2, +d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f)) };
vtx[10] = VertexTextureNormalData{ (Vec3(+w2, +h2, +d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f)) };
vtx[11] = VertexTextureNormalData{ (Vec3(+w2, +h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 1.0f, 0.0f)) };
// 아랫면
vtx[12] = VertexTextureNormalData{ (Vec3(-w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, -1.0f, 0.0f)) };
vtx[13] = VertexTextureNormalData{ (Vec3(+w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, -1.0f, 0.0f)) };
vtx[14] = VertexTextureNormalData{ (Vec3(+w2, -h2, +d2), Vec2(0.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f)) };
vtx[15] = VertexTextureNormalData{ (Vec3(-w2, -h2, +d2), Vec2(1.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f)) };
// 왼쪽면
vtx[16] = VertexTextureNormalData{ (Vec3(-w2, -h2, +d2), Vec2(0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f)) };
vtx[17] = VertexTextureNormalData{ (Vec3(-w2, +h2, +d2), Vec2(0.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f)) };
vtx[18] = VertexTextureNormalData{ (Vec3(-w2, +h2, -d2), Vec2(1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f)) };
vtx[19] = VertexTextureNormalData{ (Vec3(-w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f)) };
// 오른쪽면
vtx[20] = VertexTextureNormalData{ (Vec3(+w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f)) };
vtx[21] = VertexTextureNormalData{ (Vec3(+w2, +h2, -d2), Vec2(0.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f)) };
vtx[22] = VertexTextureNormalData{ (Vec3(+w2, +h2, +d2), Vec2(1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f)) };
vtx[23] = VertexTextureNormalData{ (Vec3(+w2, -h2, +d2), Vec2(1.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f)) };
geometry->SetVertices(vtx);
vector<uint32> idx(36);
// 앞면
idx[0] = 0; idx[1] = 1; idx[2] = 2;
idx[3] = 0; idx[4] = 2; idx[5] = 3;
// 뒷면
idx[6] = 4; idx[7] = 5; idx[8] = 6;
idx[9] = 4; idx[10] = 6; idx[11] = 7;
// 윗면
idx[12] = 8; idx[13] = 9; idx[14] = 10;
idx[15] = 8; idx[16] = 10; idx[17] = 11;
// 아랫면
idx[18] = 12; idx[19] = 13; idx[20] = 14;
idx[21] = 12; idx[22] = 14; idx[23] = 15;
// 왼쪽면
idx[24] = 16; idx[25] = 17; idx[26] = 18;
idx[27] = 16; idx[28] = 18; idx[29] = 19;
// 오른쪽면
idx[30] = 20; idx[31] = 21; idx[32] = 22;
idx[33] = 20; idx[34] = 22; idx[35] = 23;
geometry->SetIndices(idx);
}
void GeometryHelper::CreateGrid(shared_ptr<Geometry<VertexTextureNormalData>> geometry, int32 sizeX, int32 sizeZ)
{
vector<VertexTextureNormalData> vtx;
for (int32 z = 0; z < sizeZ + 1; z++)
{
for (int32 x = 0; x < sizeX + 1; x++)
{
VertexTextureNormalData v;
v.position = Vec3(static_cast<float>(x), 0, static_cast<float>(z));
v.uv = Vec2(static_cast<float>(x), static_cast<float>(sizeZ - z));
v.normal = Vec3(0.f, 1.f, 0.f);
vtx.push_back(v);
}
}
geometry->SetVertices(vtx);
vector<uint32> idx;
for (int32 z = 0; z < sizeZ; z++)
{
for (int32 x = 0; x < sizeX; x++)
{
// [0]
// | \
// [2] - [1]
idx.push_back((sizeX + 1) * (z + 1) + (x));
idx.push_back((sizeX + 1) * (z)+(x + 1));
idx.push_back((sizeX + 1) * (z)+(x));
// [1] - [2]
// \ |
// [0]
idx.push_back((sizeX + 1) * (z)+(x + 1));
idx.push_back((sizeX + 1) * (z + 1) + (x));
idx.push_back((sizeX + 1) * (z + 1) + (x + 1));
}
}
geometry->SetIndices(idx);
}
void GeometryHelper::CreateSphere(shared_ptr<Geometry<VertexTextureNormalData>> geometry)
{
float radius = 0.5f; // 구의 반지름
uint32 stackCount = 20; // 가로 분할
uint32 sliceCount = 20; // 세로 분할
vector<VertexTextureNormalData> vtx;
VertexTextureNormalData v;
// 북극
v.position = Vec3(0.0f, radius, 0.0f);
v.uv = Vec2(0.5f, 0.0f);
v.normal = v.position;
v.normal.Normalize();
vtx.push_back(v);
float stackAngle = XM_PI / stackCount;
float sliceAngle = XM_2PI / sliceCount;
float deltaU = 1.f / static_cast<float>(sliceCount);
float deltaV = 1.f / static_cast<float>(stackCount);
// 고리마다 돌면서 정점을 계산한다 (북극/남극 단일점은 고리가 X)
for (uint32 y = 1; y <= stackCount - 1; ++y)
{
float phi = y * stackAngle;
// 고리에 위치한 정점
for (uint32 x = 0; x <= sliceCount; ++x)
{
float theta = x * sliceAngle;
v.position.x = radius * sinf(phi) * cosf(theta);
v.position.y = radius * cosf(phi);
v.position.z = radius * sinf(phi) * sinf(theta);
v.uv = Vec2(deltaU * x, deltaV * y);
v.normal = v.position;
v.normal.Normalize();
vtx.push_back(v);
}
}
// 남극
v.position = Vec3(0.0f, -radius, 0.0f);
v.uv = Vec2(0.5f, 1.0f);
v.normal = v.position;
v.normal.Normalize();
vtx.push_back(v);
geometry->SetVertices(vtx);
vector<uint32> idx(36);
// 북극 인덱스
for (uint32 i = 0; i <= sliceCount; ++i)
{
// [0]
// | \
// [i+1]-[i+2]
idx.push_back(0);
idx.push_back(i + 2);
idx.push_back(i + 1);
}
// 몸통 인덱스
uint32 ringVertexCount = sliceCount + 1;
for (uint32 y = 0; y < stackCount - 2; ++y)
{
for (uint32 x = 0; x < sliceCount; ++x)
{
// [y, x]-[y, x+1]
// | /
// [y+1, x]
idx.push_back(1 + (y)*ringVertexCount + (x));
idx.push_back(1 + (y)*ringVertexCount + (x + 1));
idx.push_back(1 + (y + 1) * ringVertexCount + (x));
// [y, x+1]
// / |
// [y+1, x]-[y+1, x+1]
idx.push_back(1 + (y + 1) * ringVertexCount + (x));
idx.push_back(1 + (y)*ringVertexCount + (x + 1));
idx.push_back(1 + (y + 1) * ringVertexCount + (x + 1));
}
}
// 남극 인덱스
uint32 bottomIndex = static_cast<uint32>(vtx.size()) - 1;
uint32 lastRingStartIndex = bottomIndex - ringVertexCount;
for (uint32 i = 0; i < sliceCount; ++i)
{
// [last+i]-[last+i+1]
// | /
// [bottom]
idx.push_back(bottomIndex);
idx.push_back(lastRingStartIndex + i);
idx.push_back(lastRingStartIndex + i + 1);
}
geometry->SetIndices(idx);
}
VertexOutput VS(VertexInput input)
{
VertexOutput output;
output.position = mul(input.position, World);
output.position = mul(output.position, View);
output.position = mul(output.position, Projection);
output.uv = input.uv;
output.normal = mul(input.normal, (float3x3)World);
return output;
}
SamplerState Sampler0;
float4 PS(VertexOutput input) : SV_TARGET
{
float3 normal = normalize(input.normal);
float3 light = -LightDir;
//return float4(1, 1, 1, 1) * dot(light, normal);
return Texture0.Sample(Sampler0, input.uv) * dot(light, normal);
}
Mesh
Mesh를 이제 하나로 묶어서 관리하기 위해 작업해보자.
가령 geometry와 VertexBuffer, indexBuffer를 하나로 묶는 작업이다.
#pragma once
#include "ResourceBase.h"
#include "Geometry.h"
class Mesh : public ResourceBase
{
using Super = ResourceBase;
public:
Mesh();
virtual ~Mesh();
void CreateQuad();
void CreateCube();
void CreateGrid(int32 sizeX, int32 sizeZ);
void CreateSphere();
shared_ptr<VertexBuffer> GetVertexBuffer() { return _vertexBuffer; }
shared_ptr<IndexBuffer> GetIndexBuffer() { return _indexBuffer; }
private:
void CreateBuffers();
private:
// Mesh
shared_ptr<Geometry<VertexTextureNormalData>> _geometry;
shared_ptr<VertexBuffer> _vertexBuffer;
shared_ptr<IndexBuffer> _indexBuffer;
};
#include "pch.h"
#include "Mesh.h"
#include "GeometryHelper.h"
Mesh::Mesh() : Super(ResourceType::Mesh)
{
}
Mesh::~Mesh()
{
}
void Mesh::CreateQuad()
{
_geometry = make_shared<Geometry<VertexTextureNormalData>>();
GeometryHelper::CreateQuad(_geometry);
CreateBuffers();
}
void Mesh::CreateCube()
{
_geometry = make_shared<Geometry<VertexTextureNormalData>>();
GeometryHelper::CreateCube(_geometry);
CreateBuffers();
}
void Mesh::CreateGrid(int32 sizeX, int32 sizeZ)
{
_geometry = make_shared<Geometry<VertexTextureNormalData>>();
GeometryHelper::CreateGrid(_geometry, sizeX, sizeZ);
CreateBuffers();
}
void Mesh::CreateSphere()
{
_geometry = make_shared<Geometry<VertexTextureNormalData>>();
GeometryHelper::CreateSphere(_geometry);
CreateBuffers();
}
void Mesh::CreateBuffers()
{
_vertexBuffer = make_shared<VertexBuffer>();
_vertexBuffer->Create(_geometry->GetVertices());
_indexBuffer = make_shared<IndexBuffer>();
_indexBuffer->Create(_geometry->GetIndices());
}
MeshRenderer에서 이제 Update할 때 가지고 있는 Mesh와 텍스처를 Gpu에 넘겨줘 화면에 띄워준다.
#include "pch.h"
#include "09. MeshDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
void MeshDemo::Init()
{
// Camera
_camera = make_shared<GameObject>();
_camera->GetOrAddTransform();
_camera->AddComponent(make_shared<Camera>());
_camera->AddComponent(make_shared<CameraScript>());
// Object
_obj = make_shared<GameObject>();
_obj->GetOrAddTransform();
_obj->AddComponent(make_shared<MeshRenderer>());
{
auto shader = make_shared<Shader>(L"07. Normal.fx");
_obj->GetMeshRenderer()->SetShader(shader);
}
{
RESOURCES->Init();
auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
_obj->GetMeshRenderer()->SetMesh(mesh);
}
{
auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
_obj->GetMeshRenderer()->SetTexture(texture);
}
}
void MeshDemo::Update()
{
_camera->Update();
_obj->Update();
}
void MeshDemo::Render()
{
}
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] Lighting & Material - Material, Normal Mapping (0) | 2024.09.25 |
---|---|
[DirectX] Lighting & Material - Global Shader, Depth Stencil View, 조명 (0) | 2024.09.24 |
[DirectX] DirectX 11 3D 입문 - 카메라, 텍스처, Geometry (0) | 2024.09.12 |
[DirectX] DirectX 11 3D 입문 - 프로젝트 설정 및 간단한 실습 (0) | 2024.09.11 |
[DirectX] 엔진 구조로 제작하기 - Material, Animation, Data (0) | 2024.09.10 |