[VoiceroidRPG] 절차적 생성 – 1. 노이즈

마인크래프트(Minecraft), 테라리아(Terraria), 그리고 아이작의 번제(The binding of Isaac) 등, 어떤 게임들은 그때 그때 다른 모습의 레벨들을 자동으로 생성한다. 이렇게 정해진 규칙으로 게임 내의 컨텐츠를 자동 생성하는 것을 ‘절차적 컨텐츠 생성(Procedural Content Generation)’이라고 한다.

현재 개발 중인 VoiceroidRPG 는 절차 생성이 특징인 여러 게임들 중에서도 특히 마인크래프트의 지형 생성을 흉내 내는 것을 목표로 하고있다.

마인크래프트가 어떻게 지형을 만드는지 조사해 보면 알게 되는 정보들은 대략 다음과 같다.

  • Perlin noise, Simplex noise 등 다양한 노이즈 함수들이 있다는 것
  • 이것들을 이용해서 각 좌표에 어떤 블록을 놓을 지 결정한다는 것
  • Perlin noise 를 height map 으로 활용한 지형 생성 예제들

Unity3D 는 Mathf.PerlinNoise 라는 메소드를 제공하고 있으므로 이것을 이용해 간단히 울퉁불퉁한 지형을 생성해 볼 수 있다. 관련 예제도 유튜브를 찾아보면 많이 나온다. 원리는 간단히 2차원 좌표값을 노이즈 함수에 넣어서 값을 얻고 이것을 높이로 삼아 그 아랫부분을 블록으로 메우는 것이다.

bandicam 2018-05-07 06-52-14-426

좋은 노이즈 함수를 사용하면 노이즈를 높이값으로 이용하는 간단한 규칙으로도 그럴듯한 지형을 만들 수 있다. 하지만 이 방법은 높이를 결정하고 그 아래를 전부 메우기 때문에 동굴이나 절벽과 같은 지형은 표현할 수가 없다.

이 문제를 해결하려면 2차원의 좌표만 순회하지 말고 3차원 좌표를 모두 순회하면서 노이즈 값이 0보다 클 경우 블록을 놓고, 그렇지 않을 경우 비워 두는 방법을 사용하면 된다. 마인크래프트 개발자 노치는 이것을 ‘밀도’라고 표현했다.

노이즈에 어떤 가공도 거치지 않고 그대로 사용하게 되면 높이(y 좌표)에 상관없이 노이즈 함수의 값이 무작위로 분포하기 때문에 실제로는 미로같은 3차원 동굴이 만들어진다. 그래서 높이가 높아질 수록 값이 0을 넘지 못하도록 제한을 주어야 한다. 또 한편으로는 바닥에 구멍이 뚫리지 않도록 일정 높이까지는 값이 0 이상이 되도록 해야 한다.

내 경우에는 노이즈 가공 함수를 다음과 같이 정의함으로써 문제를 해결했다. 조금만 생각해 보면 직관적으로 이해할 수 있는 함수이다.

private float GetTerrainNoise(FastNoise noise, int x, int y, int z)
{
	float scale = 4f;
	// 최소 높이
	float offset = 8f;

	// [-1, 1]
	float rawNoise = noise.GetNoise(x, y, z);
	// offset을 더하고 현재 y좌표를 뺌으로써 최소 높이와 최대 높이를 고려할 수 있다.
	float value = rawNoise * scale + offset - y;
	return value;
}
bandicam 2018-05-07 08-30-55-823
y 좌표 값을 빼는 방법이 잘 먹힌다
bandicam 2018-05-07 08-27-12-003
height map 방식으로는 표현할 수 없는 지형이 나타난다

결론 :
노이즈 함수는 라이브러리가 널렸으니 적당히 가져다 쓰면 되고, 중요한 것은 노이즈를 가공하는 함수를 어떻게 정의할 것이냐 하는 것. 그리고 3차원 노이즈를 밀도로 활용하기 위한 기초적인 가공 함수는 다음과 같다.

density = noise * scale + offset – y

  • noise : 노이즈 함수의 함수값 [-1, 1]
  • scale : 지형의 굴곡의 정도를 결정하는 실험값
  • offset : 지형의 최소 높이
  • y : 높이가 높아질 수록 블록의 등장 확률을 낮추기 위해 y 좌표 값을 빼 준다.