Terrain

Terrain 생성 과정 요약

  • 데이터 준비 단계

    • 하이트맵 로드: RAW파일로 되어있는 HeightMap에 저장된 높이값(0~255)을 샘플링하여 실제 높이값(0~heightScale)으로 변환하여 메모리에 로드

    • 하이트맵 스무딩: 각 높이값을 주변 8개 픽셀과 평균하여 부드럽게 변환 (갑작스러운 높이 변화 방지)

    • 패치 구성: HeightMap(2049x2049)을 64셀 단위로 나누어 32x32개의 패치로 분할하고, 이를 제어하기 위한 33x33개의 패치 제어점을 생성하여 버텍스 버퍼로 만듦

    • 패치 바운딩 정보 계산: 32x32개의 패치 각각에 포함된 모든 높이값의 최소/최대값을 계산하여 저장 (프러스텀 컬링과 LOD 계산에 사용)

    • 하이트맵 SRV 생성: 하이트맵 데이터를 16비트 float 형식의 텍스처로 변환하여 GPU에서 샘플링할 수 있도록 준비

  • 렌더링 파이프라인 단계

    • VS(버텍스 셰이더) 단계:

      • 패치 제어점(33x33개)을 월드 좌표계로 변환

      • 하이트맵에서 각 제어점의 정확한 높이 샘플링하여 적용

      • 각 제어점의 UV 좌표와 해당 패치의 높이 범위 정보를 다음 단계로 전달

    • HS(헐 셰이더) 단계:

      • 패치 바운딩 박스(AABB)가 시야(프러스텀) 밖이면 테셀레이션 계수를 0으로 설정하여 컬링

      • 카메라와의 거리에 따라 테셀레이션 수준 결정 (가까운 패치는 높은 수준, 먼 패치는 낮은 수준)

      • 이웃 패치 간 균열 방지를 위해 공유 엣지의 테셀레이션 수준 일관되게 유지

    • 테셀레이터 단계 (하드웨어 고정 기능):

      • HS에서 계산된 테셀레이션 인자에 따라 패치를 매개변수 공간(uv)에서 더 작은 삼각형으로 분할

      • 새로 생성된 각 정점의 uv 좌표만 계산하여 DS로 전달

    • DS(도메인 셰이더) 단계:

      • 테셀레이터가 생성한 각 정점의 uv 좌표를 사용하여 바이리니어 보간으로 3D 위치 계산

      • 계산된 위치의 UV 좌표로 하이트맵을 샘플링하여 정확한 높이(y값) 적용

      • 타일링된 텍스처 좌표 계산 및 최종 정점 위치를 클립 공간으로 변환

    • PS(픽셀 셰이더) 단계:

      • 하이트맵의 인접한 높이값 차이를 사용해 각 픽셀의 법선 벡터 계산

      • 5개 레이어 텍스처(잔디, 흙, 돌 등)를 블렌드맵에 따라 혼합

      • 계산된 법선과 라이트 정보를 바탕으로 조명 및 그림자 효과 적용

  • 추가 핵심 요소

    • 하이트맵 해상도: 2049x2049 크기로, 모든 지형 높이 정보를 상세히 저장

    • 패치 제어점: 33x33=1089개로, 지형 전체의 "뼈대" 역할

    • 테셀레이션 수준: 최대 64x64까지 가능하여, 하나의 패치가 최대 4096개의 삼각형으로 분할될 수 있음

    • 블렌드맵: 다양한 지형 텍스처 레이어를 자연스럽게 혼합하는 데 사용되는 RGBA 텍스처

    • LOD 시스템: 거리에 따른 자동 LOD와 프러스텀 컬링으로 성능 최적화

    • 메모리 최적화: 하이트맵을 16비트 float으로 저장하여 메모리 사용량 절반으로 감소

하이트맵 로드

  • RAW 파일 열기 및 데이터 읽기

HeightMap을 바이너리 모드로 열고, 모든 바이트 데이터를 in 배열에 한 번에 읽어옵니다. RAW 파일은 각 픽셀마다 1바이트(0-255)의 높이 정보를 담고 있습니다.

여기서 주목할 설정값:

  1. heightMapFilename: 하이트맵 RAW 파일 경로

  2. heightScale: 50.0f - 이 값에 의해 RAW 파일의 0-255 값이 0-50 단위의 실제 높이로 변환됨

  3. heightmapWidth/Height: 2049x2049 - 하이트맵의 해상도

  4. cellSpacing: 0.5f - 하이트맵의 각 셀 간격(월드 단위)

결과적으로, 이 과정을 통해 2049x2049 크기의 RAW 하이트맵 데이터가 0~50 범위의 실제 높이값을 가진 float 배열로 메모리에 로드됩니다.

스무딩

  • Smooth()

  1. Smooth() 함수는 높이맵과 같은 크기의 새 배열(dest)을 생성합니다.

    1. 모든 높이맵 좌표(i,j)에 대해:

      1. Average(i,j) 함수를 호출하여 해당 위치와 주변 픽셀의 평균 계산

        1. 계산된 평균값을 dest 배열의 같은 위치에 저장

        2. 모든 처리가 끝나면 원본 높이맵을 새로 계산된 값으로 완전히 교체합니다

  • Average() / InBound()

Average() 함수는 중심 픽셀(i,j)과 그 주변 8개 픽셀을 포함한 3x3 영역을 순회합니다.

  1. 각 픽셀이 유효한 범위인지 InBounds()로 검사하고, 유효한 경우에만(HeightMap을 벗어나지 않도록):

    1. 픽셀의 높이값을 avg에 누적

    2. 카운터 num을 1 증가

  2. 모든 유효 픽셀의 높이 합을 픽셀 수로 나누어 평균값 반환

패치 구성

  • 패치 구조 초기화

패치 구성 계산:

  1. CellsPerPatch = 64: 각 패치는 64x64 크기의 셀을 담당

  2. (heightmapHeight-1) / CellsPerPatch + 1: 높이맵 치수를 패치 크기로 나누고 1을 더함

    1. (2049 - 1) / 64 + 1 = 32 + 1 = 33

  3. 따라서 33 x 33 = 1089개의 패치 정점이 생성됨

  4. 실제 패치 면은 (33 - 1) x (33 - 1) = 3 2x 32 = 1024개

  • 패치 정점 버퍼 생성

  1. 33 x 33개의 패치 정점을 위한 배열 생성

  2. 각 정점의 XZ 위치를 계산 ( (-512, -512)부터 (512, 512)까지 )

  3. 각 정점의 UV 좌표 계산 ( (0, 0)부터 (1, 1)까지 )

  4. 각 패치의 바운딩 정보(높이 범위)를 패치의 왼쪽 상단 정점에 저장

  5. 인덱스 버퍼를 생성하여 정점들이 패치를 형성하도록 구성

  • 패치 인덱스 버퍼 생성

  1. 32x32=1024개의 사각형 패치를 생성하기 위한, 총 4096개의 인덱스를 담은 배열 생성

  2. 각 패치는 정확히 4개의 인덱스로 구성 (좌상단, 우상단, 좌하단, 우하단)

  3. 이 패치들이 테셀레이션의 기본 단위가 됨

  • 패치 구성 시각화

  1. 분할 방식: 2049x2049 하이트맵을 64셀 단위로 나눔

    1. 실제 계산: (2049-1) / 64 + 1 = 33

    2. 33x33개의 패치 제어점과 32x32개의 실제 패치 생성

  2. 공간 매핑:

    1. 월드 공간: (-512,-512)부터 (512,512)까지 1024x1024 크기

    2. 패치 크기: 약 32x32 월드 단위

    3. UV 공간: 전체 지형이 (0,0)부터 (1,1)까지 매핑

바운딩 정보 계산

  • 모든 패치의 바운딩 정보 계산

  1. 32x32=1024개의 패치에 대한 바운딩 정보를 저장할 벡터 준비

  2. 모든 패치에 대해 반복 루프를 실행하며 각 패치의 바운딩 정보 계산

  • 각 패치의 높이 범위 계산

  1. 패치의 인덱스(i,j)를 하이트맵 좌표 범위로 변환 (CellsPerPatch=64를 곱함)

  2. 이 범위 내의 모든 하이트맵 높이값을 검사하여 최소/최대값 찾기

  3. 찾은 최소/최대 높이를 Vec2 형태로 해당 패치의 ID에 저장

  • 패치 정점에 바운딩 정보 저장

이 코드는 각 패치의 바운딩 정보를 해당 패치의 좌상단 정점에 저장합니다. 이렇게 하면 셰이더에서 이 정보를 전달하여 프러스텀 컬링과 LOD 계산에 사용할 수 있습니다.

하이트맵 SRV

  • 텍스처 설정

    • DXGI_FORMAT_R16_FLOAT 형식을 사용

      • 일반 32비트 float(4바이트)보다 절반 크기인 16비트(2바이트) 사용

      • 하이트맵은 단일 채널(높이값)만 필요하므로 R 채널만 사용

      • 높이값은 정확도가 매우 중요하므로 정수가 아닌 부동소수점 형식 사용

    • 32비트 float에서 16비트 half-float로 변환

      • 원본 하이트맵(_heightmap)은 32비트 float 배열(약 16MB)

      • 이를 16비트 half-float(hmap)로 변환하여 메모리 크기를 절반(약 8MB)으로 줄임

      • MathHelper::ConvertFloatToHalf 함수는 IEEE-754 표준에 따라 float를 half로 변환

VS 단계

  • 버텍스 셰이더 입력/출력 구조체

  1. VertexIn: BuildQuadPatchVB에서 생성된 정점 버퍼의 데이터 형식

  2. VertexOut: VS가 처리한 결과를 헐 셰이더로 전달하는 데이터 형식

  • 상수 버퍼 (필요한 변환 행렬과 데이터)

  1. worldMatrix: 로컬 좌표를 월드 좌표로 변환하는 행렬

  2. gHeightMap: BuildHeightmapSRV에서 생성된 하이트맵 텍스처

  3. samHeightmap: 하이트맵 샘플링 방식 정의

  • Vertex 셰이더 함수

  1. 정점의 로컬 좌표(vin.PosL)에 월드 변환 행렬을 곱해 월드 좌표로 변환

  2. 이 시점에서는 Y좌표(높이)가 아직 정확하지 않음

  3. 정점의 텍스처 좌표(vin.Tex)를 사용해 하이트맵의 해당 위치 샘플링

  4. .r은 R 채널만 사용(하이트맵은 단일 채널 높이값만 저장)

  5. 이 단계에서 제어점의 정확한 높이가 설정됨

  6. 입력 텍스처 좌표를 그대로 출력 구조체로 전달

  7. 패치의 높이 범위 정보(BoundsY)도 그대로 전달

  8. 이 정보들은 이후 HS 단계에서 테셀레이션 인자 계산과 컬링에 사용됨

HS 단계

패치 상수 함수 (ConstantHS)

  1. 프러스텀 컬링: 패치의 AABB가 시야 밖이면 테셀레이션 인자를 0으로 설정

  2. 거리 기반 LOD: 카메라와의 거리로 테셀레이션 수준 결정

  3. 에지별 테셀레이션: 각 에지마다 별도 인자 계산 (이웃 패치와 공유 에지의 일관성 유지)

  • CalcTessFactor 함수 - 거리 기반 LOD

    • p: 테셀레이션 수준을 결정할 점 (패치의 에지 중점이나 중심)

    • cameraPosition: 카메라의 현재 위치

    • gMinDist: 최대 테셀레이션이 적용될 최소 거리 (이보다 가까우면 항상 최대)

    • gMaxDist: 최소 테셀레이션이 적용될 최대 거리 (이보다 멀면 항상 최소)

    • gMinTess: 로그 스케일의 최소 테셀레이션 인자 (예: 2.0 → 2^2 = 4)

    • gMaxTess: 로그 스케일의 최대 테셀레이션 인자 (예: 6.0 → 2^6 = 64)

    • 카메라와의 거리 d를 [0,1] 범위로 정규화

    • saturate(): 값을 0~1 사이로 클램핑

      • s = 0: 카메라가 gMinDist 이내에 있음 (최대 테셀레이션)

      • s = 1: 카메라가 gMaxDist 이상 떨어짐 (최소 테셀레이션)

  1. 평면과 점의 거리:

    1. 평면 방정식 ax + by + cz + d = 0에서, 점(x,y,z)에서 평면까지의 부호있는 거리는 (ax + by + cz + d) / || (a,b,c) ||

  2. 박스의 투영 반경: 평면 법선 방향으로 바운딩 박스가 차지하는 최대 반경을 계산.

  3. 포함/배제 테스트:

    1. 박스 중심에서 평면까지의 거리 = s

    2. 박스의 평면 방향 투영 반경 = r

    3. 만약 (s + r < 0)이면 박스는 평면 "뒤쪽"에 완전히 위치

Hull Shader

  1. 테셀레이션 설정: 속성 지정자로 테셀레이터 단계 구성

    1. [domain("quad")]: 사각형 패치 사용

    2. [partitioning("fractional_even")]: 부드러운 LOD 전환을 위한 균일 분할

    3. [maxtessfactor(64.0f)]: 최대 64×64 테셀레이션 가능

  2. 패스스루: 입력 제어점의 데이터를 그대로 출력에 전달

테셀레이터 단계 (하드웨어 고정 기능)

  1. 패치를 ConstantHS 함수에서 정한 인자에 따라 UV 공간에서 분할

  2. 예를 들어 Edge[0]=Edge[1]=Inside[0]=Inside[1]=16이면 16×16 그리드 생성

  3. 각 새로운 정점에 대해 SV_DomainLocation(UV 좌표)만 계산하고 DS로 전달

DS(도메인 셰이더) 단계

  1. 바이리니어 보간: 테셀레이터가 생성한 UV 좌표에 대해 4개 제어점 사이를 보간하여 3D 위치 계산

  2. 디스플레이스먼트 매핑: 하이트맵에서 정확한 높이를 샘플링하여 Y좌표 설정

  3. 좌표 변환: 최종 월드 공간 위치를 클립 공간과 라이트 공간으로 변환

  4. 텍스처 좌표 계산: 기본 UV와 타일링된 UV 좌표 계산

PS 단계

  • 법선 벡터 계산 (하이트맵 기반)

법선 계산 원리:

  1. 중앙 차분법(Central Difference): 현재 픽셀의 좌/우/위/아래 지점의 높이를 샘플링하여 높이 변화 계산

  2. 접선 벡터(Tangent): X축 방향의 높이 변화로 계산 (2*간격, 오른쪽높이-왼쪽높이, 0)

  3. 종법선 벡터(Bitangent): Z축 방향의 높이 변화로 계산 (0, 아래높이-위높이, -2*간격)

  4. 법선 벡터: 접선과 종법선의 외적으로 계산 (표면에 수직인 벡터)

  • 다중 텍스처 레이어 블렌딩

텍스처 레이어 블렌딩 원리:

  1. 레이어 텍스처 배열: 5개의 서로 다른 지형 텍스처(잔디, 흙, 돌 등)를 2D 텍스처 배열로 관리

  2. 타일링된 텍스처 좌표: pin.TiledTex는 텍스처 반복을 위해 기본 UV에 배율(gTexScale)을 적용한 좌표

  3. 블렌드맵: RGBA 4채널 텍스처로, 각 채널이 특정 레이어의 혼합 비율을 결정

  4. 선형 보간(lerp): 블렌드맵의 값에 따라 텍스처를 순차적으로 혼합

    1. 첫 번째 텍스처(c0)를 기본으로 시작

    2. 레드 채널(t.r)로 두 번째 텍스처(c1) 혼합

    3. 그린 채널(t.g)로 세 번째 텍스처(c2) 혼합

    4. 블루 채널(t.b)로 네 번째 텍스처(c3) 혼합

    5. 알파 채널(t.a)로 다섯 번째 텍스처(c4) 혼합

Last updated