제13장 기본적인 지형 렌더링 2010-1학기 컴퓨터게임(DirectX)
기본적인 지형 렌더링 지형 렌더링 지형 메쉬: 삼각형 격자 격자 내 각 버텍스에 높이 부여하여 지형 표현 텍스처 추가하여 해변, 언덕, 눈 덮힌 산 등 표현 이 장의 내용 Terrain 클래스 구현 과정을 보여줌 전체 지형의 버텍스/인덱스 데이터를 보관하고 한 번에 렌더링 작은 지형의 경우에는 적용할 수 있으나 큰 지형에는 세부레벨조정(Level of Detail) 이나 추려내기 기능 필요 2010-1학기 컴퓨터게임(DirectX)
산이나 계곡과 같은 자연스러운 지형을 표현하기 위한 지형의 높이 정보 생성 방법 지형을 위한 버텍스와 삼각형 데이터 생성 방법 목표 산이나 계곡과 같은 자연스러운 지형을 표현하기 위한 지형의 높이 정보 생성 방법 지형을 위한 버텍스와 삼각형 데이터 생성 방법 지형에 조명과 텍스처 적용을 위한 테크닉 지형 표면에 카메라를 이식, 지형 위를 걷거나 뛰는 효과 구현 2010-1학기 컴퓨터게임(DirectX)
높 이 맵 지형의 언덕이나 계곡을 표현하는 데 이용되는 것이 높이맵(heightmap) 각각의 항목이 지형 격자 내의 특정 버텍스의 높이와 대응 각 요소가 지형 격자 내의 각 버텍스와 일대일 대응관계를 가지는 행렬로 생각 높이맵을 디스크에 보관할 때 각 높이에 한 바이트의 메모리를 할당 0 부터 255까지의 높이를 가짐 높이맵의 그래픽 표현 중 하나로 낮은 고도를 어두운 값으로, 높은 고도를 밝은 값으로 표현하는 그레이스케일 맵이 있다. 2010-1학기 컴퓨터게임(DirectX)
높이맵 만들기 높이맵은 절차적인 방법을 이용하거나 포토샵과 같은 이미지 편집기를 이용해 만들 수 있다. 이미지 편집기로 높이맵에 이용할 이미지를 만들 때 먼저 이미지 포맷으로 그레이 스케일을 지정 높이맵 작성을 마친 뒤, 8비트 RAW파일로 저장 경우에 따라서는 다른 파일 포맷으로 저장 가능 2010-1학기 컴퓨터게임(DirectX)
(그림13.3) 포토샵에서 만든 그레이스케일 이미지 2010-1학기 컴퓨터게임(DirectX)
2010-1학기 컴퓨터게임(DirectX)
RAW 파일 로딩하기 RAW파일은 일련의 연속된 바이트 블록으로 다음의 메서드를 이용해 손쉽게 블록을 읽기 가능 std::vector<int> _heightmap; bool Terrain::readRawFile(std::string fileName) { // 각 벡터를 위한 높이 std::vector<BYTE> in( _numVertices); std::ifstream inFile(fileName.c_str(), std::ios_base::binary); if(inFile == 0) return false; 2010-1학기 컴퓨터게임(DirectX)
in.size()); // 버퍼로 읽어들일 바이트 수 inFile.close(); // byte 벡터를 int 벡터로 복사 inFile.read( (char*) &in[0]; // 버퍼 in.size()); // 버퍼로 읽어들일 바이트 수 inFile.close(); // byte 벡터를 int 벡터로 복사 _heightmap.resize( _numVertices); for(int i = 0; i < in.size(); i++) _heightmap[i] = in[i]; return true; } 2010-1학기 컴퓨터게임(DirectX)
코드에서 바이트 벡터를 정수 벡터로 복사하고 있다. 이것은 이후에 높이값을 [0,255] 범위 밖으로 배율을 변경 할 수 있도록 하기 위한 것이다. 이 메서드의 유일한 제한 RAW파일에 포함된 바이트 수만큼 지형 내의 버텍스가 준비되어 있어야 함 2010-1학기 컴퓨터게임(DirectX)
Terrain 클래스는 높이맵 내의 항목에 접근하고 수정하기 위한 다음의 두 메서드를 제공 int Terrain::getHeightmapEntry(int row, int col) { return _heightmap[row*_numVertsPerRow+col]; } void Terrain::setHeightmapEntry(int row, int col, int value) _heightmap[row*_numVertsPerRow+col] = value; 2010-1학기 컴퓨터게임(DirectX)
지형 기하정보 생성하기 Z 시작=(-w/2,d/2) 셀 간격 X 셀/ 사각형 d=깊이 끝=(w/2, -d/2) W=너비 2010-1학기 컴퓨터게임(DirectX)
높이맵 요소들의 배율을 조정하는데 이용될 높이 스케일 값 예제에서 이 값들을 Terrain 클래스의 생성자에 전달 지형의 크기를 정의하는 요소 행당 버텍스 수 열당 버텍스 수 셀 간격 지형과 연결될 장치 높이맵 데이터를 포함하는 파일명 문자열 높이맵 요소들의 배율을 조정하는데 이용될 높이 스케일 값 예제에서 이 값들을 Terrain 클래스의 생성자에 전달 2010-1학기 컴퓨터게임(DirectX)
IDirect3DDevice9* device, std::string heightmapFileName, class Terrain { public: Terrain( IDirect3DDevice9* device, std::string heightmapFileName, int numVertsPerRow, int numVertsPerCol, int cellSpacing, // 셀 간의 간격 float heightScale); // 높이 배율을 조정하는 값 ….. // 메서드 생략 private: … //장치/버텍스 버퍼 생략 2010-1학기 컴퓨터게임(DirectX)
int _numVertsPerRow; int _numVertsPerCol; int _cellSpacing; int _numCellsPerRow; int _numCellsPerCol; int _width; int _depth; int _numVertices; int _numTriangles; float _heightScale; }; 전체 코드는 예제 참고 2010-1학기 컴퓨터게임(DirectX)
생성자에 전달된 값들을 이용해 다음과 같은 방법으로 다른 변수들의 값을 계산함. _numCellsPerRow = _numVertsPerRow – 1; _numCellsPerCol = _numVertsPerCol – 1; _width = _numCellsPerCol * _cellSpacing; _depth = _numCellsPerCol * _cellSpacing; _numVertices = _numvertsPerRow * _numvertsPerCol; _numTriangles = _numCellsPerRow * _numCellsPerCol * 2; 2010-1학기 컴퓨터게임(DirectX)
struct TerrainVertex{ TerrainVertex(){} 우리가 이용할 지형의 버텍스 구조체 struct TerrainVertex{ TerrainVertex(){} TerrainVertex(float x, float y, float z, float u, float v){ _x = x; _y = y; _z = z; _u = u; _v = v; } float _x, _y, _z; float _u, _v; static const DWORD FVF; }; const DWORD Terrain::TerrainVertex::FVF = D3DFVF_XYZ | D3DFVF_TEX1; 2010-1학기 컴퓨터게임(DirectX)
TerrainVertex 는 Terrain 클래스 내부의 중첩된 클래스임 2010-1학기 컴퓨터게임(DirectX)
버텍스 계산하기 삼각형 격자의 버텍스들을 계산하기 위해서는 시작점의 버텍스를 생성하기 시작, 끝점에 이를 때까지 셀 간격만큼 버텍스의 간격을 비우고 버텍스들을 만들면 된다. Y좌표는 읽어들인 높이맵 데이터 구조체 내의 대응되는 항목에서 간단하게 얻을 수 있다. 2010-1학기 컴퓨터게임(DirectX)
지형 버텍스와 텍스춰 버텍스간의 대응 텍스처 좌표 계산방법은 아래 그림을 참고 이 그림은 버텍스(i,j)에 해당하는 텍스처 좌표(u,v)를 얻는 간단한 시나리오를 보여줌 (1,0) (0,0) +U (0,1) (1,1) +V 2010-1학기 컴퓨터게임(DirectX)
u = j · uCoordIncreamentSize v = i · vCoordIncreamentSize 여기에서 1 numCellCols 1 numCellRows 2010-1학기 컴퓨터게임(DirectX)
bool Terrain::computeVertices(){ HRESULT hr = 0; 최종적으로 버텍스를 생성하는 코드 bool Terrain::computeVertices(){ HRESULT hr = 0; hr = _device->CreateVertexBuffer( _numVertices * sizeof(TerrainVertex), D3DUSAGE_WRITEONLY, TerrainVertex::FVF, D3DPOOL_MANAGED, &_vb, 0); if(FAILED(hr)) return false; int startX = -_width / 2; // 버텍스 생성을 시작할 좌표 int startZ = _depth / 2; 2010-1학기 컴퓨터게임(DirectX)
int endX = _width / 2; // 버텍스 생성을 마칠 좌표 int endZ = -_depth / 2; // 하나의 버텍스에서 다음 버텍스로 증가할 // 텍스처 좌표의 크기를 계산 float uCoordIncrementSize = 1.0f / (float)_numCellsPerRow; float vCoordIncrementSize = 1.0f / (float)_numCellsPerCol; TerrainVertex* v = 0; _vb->Lock(0, 0, (void**)&v, 0); int i = 0; 2010-1학기 컴퓨터게임(DirectX)
for(int z = startZ; z >= endZ; z -= _cellSpacing) { int j = 0; for(int x = startX; x <= endX; x += _cellSpacing) // 중첩된 루프 내의 위치에 따라 // 버텍스 버퍼와 높이맵으로의 올바른 // 인덱스를 계산한다. int index = i * _numVertsPerRow + j; 2010-1학기 컴퓨터게임(DirectX)
v[index] = TerrainVertex( (float)x, (float)_heightmap[index], (float)z, (float)j * uCoordIncrementSize, (float)i * vCoordIncrementSize); j++; // next column } i++; // next row _vb->Unlock(); return true; 2010-1학기 컴퓨터게임(DirectX)
인덱스 계산 – 삼각형 정의하기 삼각형 격자의 인덱스를 계산하기 위해서는 그림의 좌측 상단에서 시작 우측 하단으로 각각의 사각형을 구성하는 두 개의 삼각형들을 차례대로 계산 버텍스열 j (0,0) 버텍스열 j + 1 버텍스행 i A B C D ij번째 셀 버텍스행 i+1 (m,n) 2010-1학기 컴퓨터게임(DirectX)
사각형(i,j)를 찾기 위한 범용 공식 ∆ ABC = {i·numVertsPerRow+j i·numVertsPerRow+j+1 (i+1) ·numVertsPerRow+J} ∆ CBD = {(i+1)·numVertsPerRow+j i·numVertsPerRow+j+1 (i+1) ·numVertsPerRow+j+1} 2010-1학기 컴퓨터게임(DirectX)
인덱스를 계산하는 코드 Bool Terrain::computeIndices() { HRESULT hr = 0; hr = _device->CreateIndexBuffer( _numTriangles * 3 * sizeof(WORD), //삼각형당 3개의 인덱스 D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOLMANAGED, &_ib, 0); 2010-1학기 컴퓨터게임(DirectX)
_ib->Lock(0, 0, (void**)&indices, 0); If(FAILED(hr)) return false; WORD* indices = 0; _ib->Lock(0, 0, (void**)&indices, 0); // 하나의 사각형을 구성하는 두 개의 삼각형을 나타내기 위한 // 6개의 인덱스 그룹의 시작점 int baseIndex = 0; // 각 사각형의 삼각형을 계산하는 루프 for(int i = 0; i < _numCellsPerCol; i++) { 2010-1학기 컴퓨터게임(DirectX)
for(int j = 0; j < _numCellsPerRow; j++) { indices[baseIndex] = i * _numVertsPerRow + j; indices[baseIndex + 1] = i * _numVertsPerRow + j + 1; indices[baseIndex + 2] = (i+1) * _numVertsPerRow + j; indices[baseIndex + 3] = (i+1) * _numVertsPerRow + j; indices[baseIndex + 4] = i * _numVertsPerRow + j + 1; indices[baseIndex + 5] = (i+1) * _numVertsPerRow + j + 1; baseIndex += 6; } _ib->Unlock(); return true; 2010-1학기 컴퓨터게임(DirectX)
텍 스 처 링 Terrain 클래스는 지형에 텍스처를 입히는 두 가지 방법을 제공 1) 미리 만들어둔 텍스처 파일을 읽어들이고 이를 이용하는 방법 IDirect3DTexture9 인터페이스로의 포인터인 _tex데이터 멤버로 텍스처 파일을 읽어들임 bool loadTexture(std::string fileName); 2010-1학기 컴퓨터게임(DirectX)
bool loadTexture(std::string fileName); bool Terrain::loadTexture(std::string fileName) { HRESULT hr = 0; hr = D3DXCreateTextureFromFile( _device, fileName.c_str(), &_tex); if(FAILED(hr)) return false; return true; } 2010-1학기 컴퓨터게임(DirectX)
먼저 “빈” 텍스처를 만들고 미리 정의된 인자를 바탕으로 코드에서 각 텍셀의 컬러를 계산 예제에서는 지형의 높이를 인자로 이용 2) 절차적 방식 지형에 텍스처를 입히는 두 번째 방법 절차적으로 텍스처를 계산해 내는 것 먼저 “빈” 텍스처를 만들고 미리 정의된 인자를 바탕으로 코드에서 각 텍셀의 컬러를 계산 예제에서는 지형의 높이를 인자로 이용 2010-1학기 컴퓨터게임(DirectX)
Terrain:genTexture 메서드를 이용한 절차적 텍스처 D3DXCreateTexture를 이용해 빈 텍스처를 만들고 최상의 레벨(밉맵 레벨)을 잠근 다음, 각각의 텍셀을 대상으로 반복하여 컬러를 입힘 대응되는 사각형의 높이에 따라 텍셀의 컬러를 결정하여 각 텍셀을 대상으로 반복하여 컬러를 입힘 각각의 텍셀에 색상을 입힌 뒤에는 텍셀을 비추는 태양 빛의 각도에 따라 텍셀의 밝기를 조정 Terrain::lightTerrain 메서드에서 이루어짐 하위 밉맵 레벨의 텍셀을 계산하는 것으로 끝을 맺음 D3DXFilterTexture 함수가 담당 2010-1학기 컴퓨터게임(DirectX)
텍스처를 만들어내는 코드 bool Terrain::genTexture(D3DXVECTOR3* directionToLight) { HRESULT hr = 0; // 각각의 사각형 셀을 위한 텍셀 int texWidth = _numCellsPerRow; int texHeight = _numCellsPerCol; // 빈 텍스처를 만든다. hr = D3DXCreateTexture( _device, texWidth, texHeight, 0, // create a complete mipmap chain 0, // usage D3DFMT_X8R8G8B8,// 32 bit XRGB format D3DPOOL_MANAGED, &_tex); 2010-1학기 컴퓨터게임(DirectX)
D3DSURFACE_DESC textureDesc; if(FAILED(hr)) return false; D3DSURFACE_DESC textureDesc; _tex->GetLevelDesc(0 /*level*/, &textureDesc); //직접 32비트 픽셀을 이용해 텍스처를 채우므로 //올바른 포맷인지를 먼저 확인 if( textureDesc.Format != D3DFMT_X8R8G8B8 ) D3DLOCKED_RECT lockedRect; _tex->LockRect(0/*lock top surface*/, &lockedRect, 0 /* lock entire tex*/, 0/*flags*/); // 텍스처를 채운다. DWORD* imageData = (DWORD*)lockedRect.pBits; 2010-1학기 컴퓨터게임(DirectX)
for(int i = 0; i < texHeight; i++) { for(int j = 0; j < texWidth; j++) { D3DXCOLOR c; // 사각형의 우측 상단 높이를 구한다. float height = (float)getHeightmapEntry(i, j) / _heightScale; // 대응되는 사각형 높이에 따라 텍셀의 컬러를 지정한다. if( (height) < 42.5f ) c = d3d::BEACH_SAND; else if( (height) < 85.0f ) c = d3d::LIGHT_YELLOW_GREEN; else if( (height) < 127.5f ) c = d3d::PUREGREEN; else if( (height) < 170.0f ) c = d3d::DARK_YELLOW_GREEN; else if( (height) < 212.5f ) c = d3d::DARKBROWN; else c = d3d::WHITE; 2010-1학기 컴퓨터게임(DirectX)
imageData[i * lockedRect.Pitch / 4 + j] = (D3DCOLOR)c; } _tex->UnlockRect(0); // 지형을 밝힌다. if(!lightTerrain(directionToLight)) { ::MessageBox(0, "lightTerrain() - FAILED", 0, 0); return false; hr = D3DXFilterTexture( _tex, // 밉맵 레벨을 채울 텍스처 0, // 디폴트 팔레트 0, // 하위 레벨의 원본으로 최상위 레벨을 이용 D3DX_DEFAULT); // 디폴트 필터 2010-1학기 컴퓨터게임(DirectX)
::MessageBox(0, "D3DXFilterTexture() - FAILED", 0, 0); return false; } if(FAILED(hr)) { ::MessageBox(0, "D3DXFilterTexture() - FAILED", 0, 0); return false; } return true; 2010-1학기 컴퓨터게임(DirectX)
조 명 Terrain::genTexture 메서드는 지형에 조명을 추가, 사실감을 더하는 Terrain::lightTerrain 메서드 호출 Direct3D에게 지형의 음영만 계산하는 것을 맡기지 않고 지형의 조명을 계산하는 이유 버텍스 법선을 저장하지 않아도 되므로 메모리 절약 지형은 정적이며 조명을 움직이지도 않을 것이므로 조명을 미리 계산하여 지형에 실시간으로 조명을 적용하는데 따르는 처리 부담을 덜 수 있다. 약간의 수학 연습을 할 수 있으며 기본적인 조명의 개념과 Direct3D 함수들에 익숙해짐 2010-1학기 컴퓨터게임(DirectX)
조명의 개요 우리가 지형의 음영을 계산하는데 이용하는 조명 테크닉은 가장 기본적인 것 난반사광(Diffuse lighting) 빛으로의 방향을 지정하여 하나의 평행한 광원을 정의 이 방향은 평행 광원에서의 빛이 발산되는 방향과는 반대의 방향 예를 들어 lightRaysDirection=(0, -1, 0) 방향으로 하늘에서 아래로 빛이 발산되기를 원한다면, 평행 광원으로의 방향은 반대방향인 directionToLight=(0, 1, 0) 이 된다. 2010-1학기 컴퓨터게임(DirectX)
다음은 지형 내의 각각의 사각형에 대해 빛 벡터 L과 사각형 표면 법선 N간의 각도 계산 각도가 커지면 사각형 면이 점차 광원에서 멀어져 빛을 덜 받음 작아지면 사각형 면이 점차 광원으로 향하며 더욱 많은 빛을 받음 90도가 넘으면 표면은 더 이상 빛을 못 받음 L N L N 2010-1학기 컴퓨터게임(DirectX)
빛 벡터와 표면 법선과의 각도 관계를 이용해 표면이 받는 빛의 양을 결정하는 [0, 1] 범위의 음영 스칼라를 만들 수 있다. 큰 각도는 0에 가까운 스칼라로 표현되며 음영 스칼라와 컬러를 곱하면 0에 가까운 값으로 어두운 컬러가 된다. 작은 각도는 1에 가까운 스칼라로 표현되며 컬러와 곱하면 1에 가까운 밝은 값이 만들어진다. 2010-1학기 컴퓨터게임(DirectX)
사각형의 음영 계산하기 광원으로의 방향은 우리가 L 이라고 부르는 정규화된 벡터로 얻을 수 있다. L 과 사각형의 법선 벡터 N 간의 각도를 계산하기 위해서는 먼저 N을 찾아야 함 먼저 사각형과 공면인 0이 아니면서 평행이 아닌 두개의 벡터를 찾아야 함 2010-1학기 컴퓨터게임(DirectX)
다음은 이 두 개의 벡터 u와 v를 보여줌 a Y u = b - a b v = c - a Z c X 2010-1학기 컴퓨터게임(DirectX)
u = (cellSpacing, by – ay, 0) v = (0, cy – ay, -cellSpacing) u와 v가 있다면 사각형의 법선 N은 n = u x v로 얻을 수 있다. N은 정규화 되어야 한다. N = L과 N간의 각도를 찾기 위해서는 3-공간 내 두 단위 벡터의 내적이 두 벡터 간 각도의 코사인 이다. N |N| 2010-1학기 컴퓨터게임(DirectX)
float cosine = D3DXVec3Dot(&n, directionToLight); L · N = s 스칼라 s는 [-1, 1] 범위 내에 위치한다. [-1, 0) 내의 s값은 [그림 13.7]에서 빛을 받지 못하는 90도 이상의 L과 N간의 각도에 대응하므로, [-1,0) 내일 경우 s를 0으로 고정 float cosine = D3DXVec3Dot(&n, directionToLight); If(cosine < 0.0f) cosine = 0.0f; 2010-1학기 컴퓨터게임(DirectX)
이제 90도 이상의 각도에 대해 s를 고정하면 s는 [0,1] 범위의 스칼라가 된다. 0도에서 90도까지의 각도가 1에서 0값에 대응됨 각각의 사각형을 위한 음영 인수는 Terrrain::computeShade 에서 계산한다. 이 메서드는 사각형을 지정하는 열과 행, 그리고 평행 광원의 방향을 인자로 받는다. 2010-1학기 컴퓨터게임(DirectX)
float heightA = getHeightmapEntry(cellRow, cellCol); float Terrain::computeShade(int cellRow, int cellCol, D3DXVECTOR3* directionToLight) { // 사각형의 새 버텍스 높이를 얻는다. float heightA = getHeightmapEntry(cellRow, cellCol); float heightB = getHeightmapEntry(cellRow, cellCol+1); float heightC = getHeightmapEntry(cellRow+1, cellCol); // 사각형의 두 벡터 외적을 이용해 // 법선을 찾아낸다. D3DXVECTOR3 u(_cellSpacing, heightB - heightA, 0.0f); D3DXVECTOR3 v(0.0f, heightC - heightA, -_cellSpacing); 2010-1학기 컴퓨터게임(DirectX)
D3DXVec3Cross(&n, &u, &v); D3DXVec3Normalize(&n, &n); D3DXVECTOR3 n; D3DXVec3Cross(&n, &u, &v); D3DXVec3Normalize(&n, &n); float cosine = D3DXVec3Dot(&n, directionToLight); if(cosine < 0.0f) cosine = 0.0f; return cosine; } 2010-1학기 컴퓨터게임(DirectX)
지형에 음영 입히기 특정한 사각형의 음영을 계산할 수 있게 되었다면 지형의 모든 사각형을 계산하는 일은 매우 간단 각각 사각형을 대상으로 반복, 사각형의 음영값을 계산 사각형에 대응하는 텍셀 컬러에 이 음영을 적용 이 작업을 통해 빛을 적게 받는 사각형이 좀더 어두어짐 2010-1학기 컴퓨터게임(DirectX)
Terrain::lightTerrain 메서드의 가장 중요한 부분 DWORD* imageData = (DWORD*)lockedRect.pBits; for(int i = 0; i < textureDesc.Height; i++){ for(int j = 0; j < textureDesc.Width; j++){ int index = i * lockedRect.Pitch / 4 + j; // 셀의 현재 컬러를 얻는다. D3DXCOLOR c( imageData[index] ); // 셀에 음영을 적용한다. c *= computeShade(i, j, directionToLight);; // 음영을 입힌 컬러를 저장한다. imageData[index] = (D3DCOLOR)c; } 2010-1학기 컴퓨터게임(DirectX)
지형 위를 “걷기” 지형을 구성한 다음에는 마치 지형 위를 걷는 것과 같은 카메라 움직임을 구현할 필요가 있다. 이를 위해서는 현재 서있는 지점의 높이에 따라 카메라의 높이를 조정해야 한다. 가장 먼저 해야 할 일은 카메라 위치의 x와 z좌표를 이용해 현재의 셀을 찾아내는 것 이 작업은 Terrain::getHeight 함수가 담당 이 함수는 카메라의 x와 z좌표를 인자로 받고 카메라의 높이로 지정할 수 있는 높이를 리턴 2010-1학기 컴퓨터게임(DirectX)
float Terrain::getHeight(float x, float z){ // 지형의 시작점을 원점으로 이동하는 변환, xz 평면상에서 이동 x = ((float)_width / 2.0f) + x; z = ((float)_depth / 2.0f) - z; //셀 간격을 1로 만드는 변환, 변환의 배율을 낮춘다. x /= (float)_cellSpacing; z /= (float)_cellSpacing; 2010-1학기 컴퓨터게임(DirectX)
일단 지형의 시작점을 원점으로 이동하는 변환을 계산하고 수행 다음은 셀 간격 변수의 반전으로 배율을 변경 이 과정을 거치면 셀 간격은 1이 된다. 이어 양의 z축이 “아래”를 가리키도록 새로운 틀을 전환 +z 가 아래를 향하는 것으로 인식 2010-1학기 컴퓨터게임(DirectX)
다음은 이 과정을 그림으로 보여주고 있다. Z 시작 0 1 2 3 X P P 0 1 2 3 X Z 셀간격 1.0 0 1 2 3 X P P 0 1 2 3 X 셀간격 1.0 Z 2010-1학기 컴퓨터게임(DirectX)
다음과 같은 코드로 간단한 셀의 행과 열을 알아냄 float col = ::floorf(x); 변경된 좌표 시스템은 행렬의 순서와 동일 즉 좌측 상단이 원점, 열의 수가 오른쪽으로 증가 행의 수는 아래쪽으로 증가. 다음과 같은 코드로 간단한 셀의 행과 열을 알아냄 float col = ::floorf(x); float row = ::floorf(z); X 이하의 가장 큰 정수 2010-1학기 컴퓨터게임(DirectX)
이젠 현재 위치의 셀을 찾을 수 있으므로 셀을 구성하는 네 버텍스의 높이를 구성할 수 있다. float A = getHeightmapEntry(row, col); float B = getHeightmapEntry(row, col+1); float C = getHeightmapEntry(row+1, col); float D = getHeightmapEntry(row+1, col+1); 이젠 셀 내의 카메라가 위치한 특정 x, z좌표의 높이(y-좌표)를 얻는 과정이 남아있다. 2010-1학기 컴퓨터게임(DirectX)
Y v1 y v2 Z (x,z) v0 v3 X 2010-1학기 컴퓨터게임(DirectX)
높이를 구하기 위해서는 우리가 셀의 어떤 삼각형에 있는지를 먼저 알아야 한다. 우리가 있는 삼각형을 찾기 위해서는 왼쪽 상단의 버텍스가 원점에 오도록 현재의 셀을 이동 col과 row는 현재 우리가 있는 셀의 왼쪽 상단 버텍스 위치를 나타냄 x축의 -col이 되고 z축의 -row가 되도록 이동해야 함 2010-1학기 컴퓨터게임(DirectX)
x와 z좌표를 이동하면 다음과 같은 결과가 나옴 float dx = x - col; float dz = z - row; 다음은 이동한 뒤의 셀 상태를 보여줌 0 1 2 3 (0,0) 0 1 2 3 우리가 있는 셀 P P (1,1) 버텍스(2,1) 1.0 2010-1학기 컴퓨터게임(DirectX)
이제 dz<1.0 - dx이면 우리는 “위쪽”삼각형 v0v1v2에 있는것 2010-1학기 컴퓨터게임(DirectX)
“위쪽” 삼각형에서의 높이 계산 방법 위쪽 삼각형에서의 높이를 구하기 위해서는 먼저 삼각형의 옆면과 벡터 q = (qx, A, qz)의 종단점에서 시작하는 두 개의 벡터 u = (cellSpacing, B - A, 0)와 v = (0, C - A, -cellSpacing)을 구성해야 함 이어 dx로 u를 따라 선형적으로 보간하고 dz로 v를 선형적으로 보간한다. 2010-1학기 컴퓨터게임(DirectX)
보간 과정 (그림13.12) A) 삼각형의 인접하고 상반된 면의 두 벡터를 계산 B) u를 dx로 선형적 보간하고 v를 dz로 선형적으로 보간하면 높이가 구해짐 => (q + dx u + dz v) 의 y 좌표 Y q v u Y q v u Z dxu Z dzv y (x,z) (x,z) X X 2010-1학기 컴퓨터게임(DirectX)
우리가 관심을 가지는 것은 보간된 높이값 뿐이므로 y-성분만을 보간하고 나머지 성분들은 무시 가능 최종적으로 A + dx uy + dz vy를 통해 높이 구함 2010-1학기 컴퓨터게임(DirectX)
Terrain::getHeight를 완결하는 코드 if(dz < 1.0f - dx) // 위쪽 삼각형 ABC { float uy = B - A; // A->B float vy = C - A; // A->C height = A + d3d::Lerp(0.0f, uy, dx) + d3d::Lerp(0.0f, vy, dz); } else // 아래쪽 삼각형 DCB float uy = C - D; // D->C float vy = B - D; // D->B height = D + d3d::Lerp(0.0f, uy, 1.0f - dx) + d3d::Lerp(0.0f, vy, 1.0f - dz); return height; 2010-1학기 컴퓨터게임(DirectX)
Lerp 함수는 1D 선을 따르는 기본 선형 보간이며 다음과 같이 구현됨 float d3d::Lerp(float a, float b, float t) { return a – (a * t ) + ( b * t); } 2010-1학기 컴퓨터게임(DirectX)
예제 애플리케이션 : 지형 2010-1학기 컴퓨터게임(DirectX)
높이맵 데이터를 포함하는 RAW 파일을 이용해 지형을 생성하고 텍스처를 입히며 조명을 추가 화살표 키를 이용해 지형 위를 걷는 동작 먼저 지형과 카메라 초당 프레임 수를 기록하기 위한 전역 변수 추가. Terrain* TheTerrain = 0; Camera TheCamera(Camera::LANDOBJECT); FPSCounter* FPS = 0; 2010-1학기 컴퓨터게임(DirectX)
D3DXVECTOR3 lightDirection(0.0f, 1.0f, 0.0f); bool Setup() { D3DXVECTOR3 lightDirection(0.0f, 1.0f, 0.0f); TheTerrain = new Terrain(Device, "coastMountain64.raw", 64, 64, 10, 0.5f); TheTerrain->genTexture(&lightDirection); FPS = new FPSCounter(Device); Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR); 2010-1학기 컴퓨터게임(DirectX)
D3DXMatrixPerspectiveFovLH( &proj, D3DX_PI * 0.25f, // 45 - degree D3DXMATRIX proj; D3DXMatrixPerspectiveFovLH( &proj, D3DX_PI * 0.25f, // 45 - degree (float)Width / (float)Height, 1.0f, 1000.0f); Device->SetTransform(D3DTS_PROJECTION, &proj); return true; } void Cleanup() { d3d::Delete<Terrain*>(TheTerrain); d3d::Delete<FPSCounter*>(FPS); 2010-1학기 컴퓨터게임(DirectX)
bool Display(float timeDelta) { if( Device ) if( ::GetAsyncKeyState(VK_UP) & 0x8000f ) TheCamera.walk(100.0f * timeDelta); if( ::GetAsyncKeyState(VK_DOWN) & 0x8000f ) TheCamera.walk(-100.0f * timeDelta); if( ::GetAsyncKeyState(VK_LEFT) & 0x8000f ) TheCamera.yaw(-1.0f * timeDelta); if( ::GetAsyncKeyState(VK_RIGHT) & 0x8000f ) TheCamera.yaw(1.0f * timeDelta); 2010-1학기 컴퓨터게임(DirectX)
if( ::GetAsyncKeyState('N') & 0x8000f ) TheCamera.strafe(-100.0f * timeDelta); if( ::GetAsyncKeyState('M') & 0x8000f ) TheCamera.strafe(100.0f * timeDelta); if( ::GetAsyncKeyState('W') & 0x8000f ) TheCamera.pitch(1.0f * timeDelta); if( ::GetAsyncKeyState('S') & 0x8000f ) TheCamera.pitch(-1.0f * timeDelta); 2010-1학기 컴퓨터게임(DirectX)
TheCamera.getPosition(&pos); D3DXVECTOR3 pos; TheCamera.getPosition(&pos); float height = TheTerrain->getHeight( pos.x, pos.z ); pos.y = height + 5.0f; TheCamera.setPosition(&pos); D3DXMATRIX V; TheCamera.getViewMatrix(&V); Device->SetTransform(D3DTS_VIEW, &V); Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff000000, 1.0f, 0); Device->BeginScene(); 2010-1학기 컴퓨터게임(DirectX)
D3DXMatrixIdentity(&I); if( TheTerrain ) TheTerrain->draw(&I, false); if( FPS ) FPS->render(0xffffffff, timeDelta); Device->EndScene(); Device->Present(0, 0, 0, 0); } return true; 2010-1학기 컴퓨터게임(DirectX)