2D 공간을 3D 공간으로 변환한다. 가까운면(근면)부터 거리가 먼면(원면)까지 방향을 구한 다음, 그것을 이어주는 선을 TrytoIntercept에 넣어주면 해당 삼각형의 위치가 나온다. 마우스의 위치를 2D로 변환한 좌표를 활용하여 삼각형을 그려 3D 좌표로 변환한다. 

 

 

 


 

 

Unprojection - 2D 좌표를 3D 좌표로 변환

 

Shader
   
Framework
  Environment
  Terrain.h .cpp
  Viewer
  Viewport.h .cpp
UnitTest
  Terrain
  GetRaycastDemo.h .cpp 생성
  Main.h .cpp

 

 


 

Terrain

 

Terrain.h

더보기
#pragma once
class Terrain
{
public:
typedef VertexNormal TerrainVertex;
public:
Terrain(Shader* shader, wstring heightFile);
~Terrain();
void Update();
void Render();
void Pass(UINT val) { pass = val; }
float GetHeight(Vector3& position);
float GetVerticalRaycast(Vector3& position);
Vector3 GetRaycastPosition(); //반직선을 쏴서 선택한다.
private:
void CreateVertexData();
void CreateIndexData();
void CreateNormalData();
void CreateBuffer();
private:
UINT pass = 0;
Shader* shader;
Texture* heightMap;
UINT width, height;
UINT vertexCount;
TerrainVertex* vertices;
ID3D11Buffer* vertexBuffer;
UINT indexCount;
UINT* indices;
ID3D11Buffer* indexBuffer;
Matrix world;
};

Vector3 GetRaycastPosition()

  • 반직선을 쏴서 선택한다.

 

 

Terrain.cpp

더보기
#include "Framework.h"
#include "Terrain.h"
Terrain::Terrain(Shader * shader, wstring heightFile)
: shader(shader)
{
heightMap = new Texture(heightFile);
CreateVertexData();
CreateIndexData();
CreateNormalData();
CreateBuffer();
D3DXMatrixIdentity(&world);
}
Terrain::~Terrain()
{
SafeDelete(heightMap);
SafeDeleteArray(vertices);
SafeDeleteArray(indices);
SafeRelease(vertexBuffer);
SafeRelease(indexBuffer);
}
void Terrain::Update()
{
static Vector3 direction = Vector3(-1, -1, 1);
ImGui::SliderFloat3("Direction", direction, -1, 1);
shader->AsVector("Direction")->SetFloatVector(direction);
shader->AsMatrix("World")->SetMatrix(world);
shader->AsMatrix("View")->SetMatrix(Context::Get()->View());
shader->AsMatrix("Projection")->SetMatrix(Context::Get()->Projection());
}
void Terrain::Render()
{
//for (int i = 0; i < vertexCount; i++)
//{
// Vector3 start = vertices[i].Position;
// Vector3 end = vertices[i].Position + vertices[i].Normal * 2;
// DebugLine::Get()->RenderLine(start, end, Color(0, 1, 0, 1));
//}
UINT stride = sizeof(TerrainVertex);
UINT offset = 0;
D3D::GetDC()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
D3D::GetDC()->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
D3D::GetDC()->IASetIndexBuffer(indexBuffer, DXGI_FORMAT_R32_UINT, 0);
shader->DrawIndexed(0, pass, indexCount);
}
float Terrain::GetHeight(Vector3 & position)
{
UINT x = (UINT)position.x;
UINT z = (UINT)position.z;
if (x < 0 || x > width) return FLT_MIN;
if (z < 0 || z > height) return FLT_MIN;
UINT index[4];
index[0] = width * z + x;
index[1] = width * (z + 1) + x;
index[2] = width * z + x + 1;
index[3] = width * (z + 1) + x + 1;
Vector3 v[4];
for (int i = 0; i < 4; i++)
v[i] = vertices[index[i]].Position;
float ddx = (position.x - v[0].x) / 1.0f;
float ddz = (position.z - v[0].z) / 1.0f;
Vector3 result;
if(ddx + ddz <= 1.0f)
result = v[0] + (v[2] - v[0]) * ddx + (v[1] - v[0]) * ddz;
else
{
ddx = 1.0f - ddx;
ddz = 1.0f - ddz;
result = v[3] + (v[1] - v[3]) * ddx + (v[2] - v[3]) * ddz;
}
return result.y;
}
float Terrain::GetVerticalRaycast(Vector3 & position)
{
UINT x = (UINT)position.x;
UINT z = (UINT)position.z;
if (x < 0 || x > width) return FLT_MIN;
if (z < 0 || z > height) return FLT_MIN;
UINT index[4];
index[0] = width * z + x;
index[1] = width * (z + 1) + x;
index[2] = width * z + x + 1;
index[3] = width * (z + 1) + x + 1;
Vector3 p[4];
for (int i = 0; i < 4; i++)
p[i] = vertices[index[i]].Position;
Vector3 start(position.x, 50, position.z);
Vector3 direction(0, -1, 0);
float u, v, distance;
Vector3 result(-1, FLT_MIN, -1);
if (D3DXIntersectTri(&p[0], &p[1], &p[2], &start, &direction, &u, &v, &distance) == TRUE)
result = p[0] + (p[1] - p[0]) * u + (p[2] - p[0]) * v;
if (D3DXIntersectTri(&p[3], &p[1], &p[2], &start, &direction, &u, &v, &distance) == TRUE)
result = p[3] + (p[1] - p[3]) * u + (p[2] - p[3]) * v;
return result.y;
}
Vector3 Terrain::GetRaycastPosition()
{
Matrix V = Context::Get()->View();
Matrix P = Context::Get()->Projection();
Viewport* vp = Context::Get()->GetViewport();
Vector3 mouse = Mouse::Get()->GetPosition();
//방향 = 원면에서의 마우스 위치 - 근면에서의 마우스 위치
//direction = far - near
Vector3 n, f; //near, far
mouse.z = 0.0f;
vp->Unproject(&n, mouse, world, V, P);//근면의 마우스 위치
mouse.z = 1.0f;
vp->Unproject(&f, mouse, world, V, P);//원면의 마우스 위치
Vector3 start = n;
Vector3 direction = f - n; //far - near
for (UINT z = 0; z < height - 1; z++)
{
for (UINT x = 0; x < width - 1; x++)
{
UINT index[4];
index[0] = width * z + x;
index[1] = width * (z + 1) + x;
index[2] = width * z + x + 1;
index[3] = width * (z + 1) + x + 1;
Vector3 p[4];
for (int i = 0; i < 4; i++)
p[i] = vertices[index[i]].Position;
float u, v, distance;
if (D3DXIntersectTri(&p[0], &p[1], &p[2], &start, &direction, &u, &v, &distance) == TRUE)
return p[0] + (p[1] - p[0]) * u + (p[2] - p[0]) * v;
if (D3DXIntersectTri(&p[3], &p[1], &p[2], &start, &direction, &u, &v, &distance) == TRUE)
return p[3] + (p[1] - p[3]) * u + (p[2] - p[3]) * v;
}
}
return Vector3(-1, FLT_MIN, -1);
}
void Terrain::CreateVertexData()
{
vector<Color> heights;
heightMap->ReadPixel(DXGI_FORMAT_R8G8B8A8_UNORM, &heights);
width = heightMap->GetWidth();
height = heightMap->GetHeight();
vertexCount = width * height;
vertices = new TerrainVertex[vertexCount];
for (UINT z = 0; z < height; z++)
{
for (UINT x = 0; x < width; x++)
{
UINT index = width * z + x;
UINT pixel = width * (height - 1 - z) + x;
vertices[index].Position.x = (float)x;
vertices[index].Position.y = heights[pixel].r * 255.0f / 10.0f;
vertices[index].Position.z = (float)z;
}
}
}
void Terrain::CreateIndexData()
{
indexCount = (width - 1) * (height - 1) * 6;
indices = new UINT[indexCount];
UINT index = 0;
for (UINT y = 0; y < height - 1; y++)
{
for (UINT x = 0; x < width - 1; x++)
{
indices[index + 0] = width * y + x;
indices[index + 1] = width * (y + 1) + x;
indices[index + 2] = width * y + x + 1;
indices[index + 3] = width * y + x + 1;
indices[index + 4] = width * (y + 1) + x;
indices[index + 5] = width * (y + 1) + x + 1;
index += 6;
}
}
}
void Terrain::CreateNormalData()
{
for (UINT i = 0; i < indexCount / 3; i++)
{
UINT index0 = indices[i * 3 + 0];
UINT index1 = indices[i * 3 + 1];
UINT index2 = indices[i * 3 + 2];
TerrainVertex v0 = vertices[index0];
TerrainVertex v1 = vertices[index1];
TerrainVertex v2 = vertices[index2];
Vector3 a = v1.Position - v0.Position;
Vector3 b = v2.Position - v0.Position;
Vector3 normal;
D3DXVec3Cross(&normal, &a, &b);
vertices[index0].Normal += normal;
vertices[index1].Normal += normal;
vertices[index2].Normal += normal;
}
for (UINT i = 0; i < vertexCount; i++)
D3DXVec3Normalize(&vertices[i].Normal, &vertices[i].Normal);
}
void Terrain::CreateBuffer()
{
//Create Vertex Buffer
{
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC));
desc.ByteWidth = sizeof(TerrainVertex) * vertexCount;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = vertices;
Check(D3D::GetDevice()->CreateBuffer(&desc, &subResource, &vertexBuffer));
}
//Create Index Buffer
{
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC));
desc.ByteWidth = sizeof(UINT) * indexCount;
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = indices;
Check(D3D::GetDevice()->CreateBuffer(&desc, &subResource, &indexBuffer));
}
}

Vector3 Terrain::GetRaycastPosition()

  • 반직선을 쏴서 선택한다.
  • 방향 = 원면에서의 마우스 위치 - 근면에서의 마우스 위치
    • direction = far - near
    • Vector3 n, f;

 

 


 

Viewport

 

Viewport.h

더보기
#pragma once
class Viewport
{
public:
Viewport(float width, float height, float x = 0, float y = 0, float minDepth = 0, float maxDepth = 1);
~Viewport();
void RSSetViewport();
void Set(float width, float height, float x = 0, float y = 0, float minDepth = 0, float maxDepth = 1);
float GetWidth() { return width; }
float GetHeight() { return height; }
void Project(Vector3* pOut, Vector3& source, Matrix& W, Matrix& V, Matrix& P);
void Unproject(Vector3* pOut, Vector3& source, Matrix& W, Matrix& V, Matrix& P);
private:
float x, y;
float width, height;
float minDepth, maxDepth;
D3D11_VIEWPORT viewport;
};

 

 

Viewport.cpp

더보기
#include "Framework.h"
#include "Viewport.h"
Viewport::Viewport(float width, float height, float x, float y, float minDepth, float maxDepth)
{
Set(width, height, x, y, minDepth, maxDepth);
}
Viewport::~Viewport()
{
}
void Viewport::RSSetViewport()
{
D3D::GetDC()->RSSetViewports(1, &viewport);
}
void Viewport::Set(float width, float height, float x, float y, float minDepth, float maxDepth)
{
viewport.TopLeftX = this->x = x;
viewport.TopLeftY = this->y = y;
viewport.Width = this->width = width;
viewport.Height = this->height = height;
viewport.MinDepth = this->minDepth = minDepth;
viewport.MaxDepth = this->maxDepth = maxDepth;
RSSetViewport();
}
void Viewport::Project(Vector3 * pOut, Vector3 & source, Matrix & W, Matrix & V, Matrix & P)
{
// W -> V -> P -> Vp
Vector3 position = source;
Matrix wvp = W * V * P;
D3DXVec3TransformCoord(pOut, &position, &wvp);
pOut->x = ((pOut->x + 1.0f) * 0.5f) * width + x;
pOut->y = ((-pOut->y + 1.0f) * 0.5f) * height + y;
pOut->z = (pOut->z * (maxDepth - minDepth)) + minDepth;
}
void Viewport::Unproject(Vector3 * pOut, Vector3 & source, Matrix & W, Matrix & V, Matrix & P)
{
// Viewport(Vp) -> Projection(P) -> View(V) -> World(W)
Vector3 position = source;
//화면공간에서 3D(NDC)공간으로 변환 //위의 Projection에서 한 좌표변환을 되돌려준다고 생각하면 된다.
pOut->x = ((position.x - x) / width) * 2.0f - 1.0f;
pOut->y = (((position.y - y) / height) * 2.0f - 1.0f) * -1.0f;
pOut->z = ((position.z - minDepth) / (maxDepth - minDepth));
Matrix wvp = W * V * P;
D3DXMatrixInverse(&wvp, NULL, &wvp); //역행렬
D3DXVec3TransformCoord(pOut, pOut, &wvp);
}

void Viewport::Unproject

  •  Viewport(Vp) -> Projection(P) -> View(V) -> World(W) 과정을 거친다.
  • 화면공간에서 3D(NDC)공간으로 변환한다.
  • Projection에서 한 좌표변환을 되돌려준다고 생각하면 된다.

 


 

GetRaycastDemo

 

GetRaycastDemo.h

더보기
#pragma once
#include "Systems/IExecute.h"
class GetRaycastDemo : 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:
Shader* shader;
Terrain* terrain;
};

 

 

GetRaycastDemo.cpp

더보기
#include "stdafx.h"
#include "GetRaycastDemo.h"
void GetRaycastDemo::Initialize()
{
Context::Get()->GetCamera()->RotationDegree(12, 0, 0);
Context::Get()->GetCamera()->Position(35, 10, -55);
((Freedom *)Context::Get()->GetCamera())->Speed(20);
shader = new Shader(L"19_Terrain.fx");
terrain = new Terrain(shader, L"Terrain/Gray256.png");
terrain->Pass(1);
}
void GetRaycastDemo::Destroy()
{
SafeDelete(shader);
SafeDelete(terrain);
}
void GetRaycastDemo::Update()
{
Vector3 position = terrain->GetRaycastPosition();
string str = "";
str += to_string(position.x) + ", ";
str += to_string(position.y) + ", ";
str += to_string(position.z) + ", ";
Gui::Get()->RenderText(Vector2(10, 60), Color(1, 0, 0, 1), str);
terrain->Update();
}
void GetRaycastDemo::Render()
{
terrain->Render();
}

 

 


 

실행화면