복셀 기반 절차적 맵 생성

Perlin Noise와 Simplex Noise를 활용한 절차적 지형 생성 연구

Language
Rust
Engine
Bevy
Topic
Procedural Generation

결과

선형 합동법

펄린 노이즈와 심플렉스 노이즈를 활용하여 복셀 기반의 절차적 맵 생성 시스템을 구현했습니다.

청크 단위로 지형과 동굴을 생성하고 최적화하여 효율적인 렌더링을 달성했습니다.


복셀 기반 절차적 맵 생성을 구현하기 위한 연구 과정들을 설명합니다.

1. 유사 난수 생성기 구현 및 비교

난수처럼 보이는 어떤 수의 나열을 만들어 내는 일을 하는 것을 유사 난수 생성기라고 합니다.

선형 합동법, 메르센 트위스터 등이 있으며, 좋은 난수 생성기를 선택해 무작위성이 높은 난수를 생성합니다.

지형 생성에 많이 쓰는 Perline Noise 전 난수에 대해 먼저 알아보았습니다.

선형 합동법


선형 합동법

메르센 트위스터


메르센 트위스터

2. 펄린 노이즈 및 프랙탈 브라운 모션 구현

펄린 노이즈는 완전히 랜덤이 아닌, 자연적인 질서를 가진(부드러운) 일련의 값들을 생성하는 알고리즘입니다.

텍스처 및 지형과 같은 것들을 알고리즘을 통해 생성할 수 있으며, 결과 값은 실수입니다.

Ken Perlin 이 발명한 Perlin Noise 가 대표적인 알고리즘입니다.

불규칙 노이즈


불규칙 노이즈

펄린 노이즈


펄린 노이즈

프랙탈 브라운 모션

펄린 노이즈는 여러 스케일을 갖도록 노이즈를 만들고, 합산하여 랜덤하게 보이도록 하는 것이 좋습니다.

좀 더 자연스러운 노이즈를 생성할 수 있습니다.

이를 위해 옥타브(octave)라는 개념을 사용합니다.

규칙적인 단계로 주파수(frequency)를 연속적으로 증가시키고, 다양한 노이즈 반복(옥타브)을 추가합니다.

이를 프랙탈 브라운 운동 또는 프랙탈 노이즈라는 기법이라고 합니다.

3. Rust와 Bevy 엔진

새로운 언어와 엔진을 사용하고 싶어, Rust 언어로 작성된 Bevy 엔진을 사용했습니다.

또한, 많은 복셀들을 제어하기 위해서 ECS 기반의 엔진을 선택했습니다.

Rust 언어

메모리 안전성과 성능 및 편의성에 중점을 둔 프로그래밍 언어입니다.

컴파일 타임에 모든 메모리 안전성을 검사합니다.

Bevy 엔진

Rust로 구축된 데이터 기반 게임 엔진입니다.

Entity Component System 패러다임을 활용한 데이터 중심 아키텍처를 사용합니다.

Bevy 로고 Bevy 예제

3-1. 프로젝트 구조

프로젝트 구조

Rust에서는 하나의 폴더가 모듈로 인식됩니다.

  • perlin noise를 구현한 noise 모듈
  • voxel 구조를 구현한 voxel 모듈
  • 시작점은 main.rs
  • noise.rs와 voxel.rs는 모듈 안의 rs 파일들을 밖에서 접근할 수 있게 선언해주는 역할

Block.rs

각 복셀들의 속성을 정의합니다.

Grass 타입 블록에 맞는 텍스쳐를 지정하고, 이어 돌, 모래 블록들도 어떤 텍스쳐를 사용해야 하는지 정의합니다.

Chunk.rs

청크를 생성하고, 거리에 따라 활성화/비활성화하는 최적화 작업, 내부 복셀을 생성합니다.

구체적인 과정은 링크를 참고 부탁드립니다.

mesh.rs

복셀들의 Mesh Data를 정의합니다. (버텍스, 인덱스, 텍스쳐 등)

청크 크기(10x20), 월드 크기(30x30), 시야 거리(10청크) 등의 설정값을 상수로 정의합니다.

VOXEL_TRIS는 큐브 6개 면의 삼각형을 구성하는 버텍스 인덱스 배열입니다.

FACE_CHECKS는 각 면의 법선 벡터로, 인접 복셀 확인에 사용됩니다.

VOXEL_UVS는 텍스처 매핑을 위한 UV 좌표입니다.

world.rs

생성된 청크들의 복셀 메시 데이터를 가지고 실제로 메시를 생성해주는 역할을 합니다.

World 구조체가 청크들을 HashMap으로 관리하며, 플레이어 위치 기반으로 청크를 동적으로 생성/활성화합니다.

check_view_distance 함수로 시야 거리 내의 청크만 활성화하여 최적화합니다.

setup 함수에서 초기 청크 메시를 생성하고, 텍스처와 조명을 설정합니다.

get_voxel 함수는 Perlin 노이즈를 사용해 지형 타입(풀, 모래, 돌 등)을 결정합니다.

update 함수로 플레이어 이동에 따라 새로운 청크를 실시간으로 스폰합니다.

4. 복셀 맵에 펄린 노이즈 적용하기

실제로 구현한 펄린 노이즈 알고리즘을 게임 엔진에 적용해보기 위한 복셀 맵을 제작했습니다.

  • 100 X 100 복셀에 펄린 노이즈의 결과 값을 높이 값으로 사용
  • 높이에 따라 복셀의 색을 다르게 설정

구현 결과

복셀 맵 결과 1 복셀 맵 결과 2

5. Simplex Noise (간단한 노이즈)

펄린 노이즈로 복셀의 높이만 조절한다면, 내부에는 비어있고 동굴과 같은 구조도 존재하지 않습니다.

따라서 실제 마인크래프트 같은 맵을 만들기 위해 2차원 표면이 아닌, 3차원의 노이즈가 필요합니다.

그래서 Ken Perlin이 n 차원의 노이즈를 위해서 만든 Simplex Noise를 사용했습니다.

구현 결과

Simplex Noise 결과

6. 청크 생성과 최적화

복셀 환경에서 더 빠르고 효율적인 테스트를 위해 청크 단위로 개발 및 최적화를 진행했습니다.

청크를 쓰지 않을 때 프레임이 떨어지는 현상이 크게 있었습니다.

'마인크래프트' 게임에서 왜 청크를 사용했는지 몸소 체험할 수 있었습니다.

구현 결과

청크


청크

청크와 텍스쳐


청크와 텍스쳐 적용

청크 최적화

7. 청크 구조에서의 지형 생성

청크 구조 안에서 펄린 노이즈를 이용해 지형을 생성합니다.

구현 과정

  • 결과 노이즈값을 Y축의 값으로 설정합니다.
  • 청크에서 y축 높이가 펄린 노이즈 값과 일치하는 좌표에는 풀(Grass)을 그립니다.
    • 노이즈 값은 (0)에서 청크의 높이 (10) 사이로 설정합니다.
    • 현재 복셀의 높이가 노이즈 값과 같다면 풀을 그립니다.
  • 더 높은 경우에는 공기(Air)를 그립니다.
    • 노이즈 값은 높이이기 때문에 그 복셀의 위치의 최대 높이입니다.
  • 높이 0은 베드락(BedRock)을 그립니다.

구현 결과

옥타브 적용 전

옥타브 적용 X


2D 펄린 노이즈에서 프랙탈 브라운 운동을 구현하며 적용했던 옥타브를 사용합니다.

좀 더 완만한 지형을 보여줍니다.

옥타브 적용 후

옥타브 적용 O

8. 청크 구조에서의 동굴 생성

위에서 구현했던 Simplex Noise 를 이용해 동굴을 생성합니다.

Simplex Noise는 라이브러리를 사용했습니다.

구현 과정

min_ground 땅 밑으로는 simplex_result의 값이 0보다 작을 때, 즉 영향력이 적은 복셀들은 Air로 설정합니다.

구현 결과

동굴 측면

측면

동굴 최종 결과

동굴 최종 결과