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();
    }

     

     


     

    실행화면