Post Lists

2018년 12월 27일 목요일

Perlin Noise: Part 2, Using Perlin Noise to Create a Terrain Mesh

https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/perlin-noise-part-2/perlin-noise-terrain-mesh

Using Perlin Noise to Create a Terrain Mesh
이 챕터에서, terrain을 만드는 mesh의 vertex를 옮기기 위해서 2D Perlin noise를 사용하는 것으로 구성된 재미있는 기법을 배울 것이다. noise에 대한 첫 번째 강의에서 언급했듯이, noise function은 매우 유용한 "procedural texture" primitive인데, 거기에서 좀 더 복잡한 procedural textures들이 만들어질 수 있따, 예를들어 fractal or turbulence pattern같은. 우리는 우선 Perlin noise를 사용할 것이고, 그러고나서 대신에 fractal pattern을 사용하여 생성된 terrain의 예시를 보여줄 것이다.

이 기법은 다음 챕터의 주제인 Perlin noise function의 도함수를 연산하는 것의 중요성을 더 잘 이해하는데 유용할 것이다.

이 챕터를 읽은 후에, 너는 아래의 이미지를 만들어낼 수 있을 것이다.

이 기법 뒤에 있는 아이디어는 매우 간단하고, 우리가 displacement mapping이라고 부르는 것과 유사하다. 만약 너가 위에서 grid를 바라본다면, 만약 너가 noise image를 grid로 덮어씌운다면 너는 완벽히 일치한다는 것을 볼 수 있을 것이다 : grid의 각 정점이 noise image의 pixel과 일치한다. 너가 알듯이, 우리는 픽셀들의 좌표를 어떤 normal device coordinates의 종류에 정의할 수 있다 (pixel 좌표들은 그러고나서 [0.1]의 범위에 있다). 그 같은 것은 grid vertices에 되어질 수 있다: 이러한 것은 기술적으로 texture coordinates라고 불려진다. grid를 만들기 위해 우리가 사용할 함수들을 봐보자:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
PolyMesh* createPolyMeshPlane(
 uint32_t width = 1,
 uint32_t height = 1,
 uint32_t subdivisionWidth = 40,
 uint32_t subdivisionHeight = 40
)
{
 PolyMesh* poly = new PolyMesh;
 poly->numVertices = (subdivisionWidth + 1) * (subdivisionHeight + 1);
 poly->vertices = new Vec3f[poly->numVertices];
 poly->st = new Vec2f[poly->numVertices];

 float invSubdivisionWidth = 1.f / subdivisionWidth;
 float invSubdivisionHeight = 1.f / subdivisionHeight;
 for (uint32_t j = 0; j <= subdivisionHeight; ++j)
 {
  for (uint32_t i = 0; i < subdivisionWidth; ++i)
  {
   poly->vertices[j * (subdivisionWidth + 1) + i] = Vec3f(width * (i * invSubdivisionWidth - 0.5), 0, height *(j * invSubdivisionHeight - 0.5));
   poly->st[j * (subdivisionWidth + 1) + i] = Vec2f(i * invSubdivisionWidth, j * invSubdivisionHeight);
  }
 }

 poly->numFaces = subdivisionWidth * subdivisionHeight;
 poly->faceArray = new uint32_t[poly->numFaces];
 for (uint32_t i = 0; i < poly->numFaces; ++i)
  poly->faceArray[i] = 4;

 poly->verticesArray = new uint32_t[4 * poly->numFaces];
 for (uint32_t j = 0, k = 0; j < subdivisionHeight; ++j)
 {
  for (uint32_t i = 0; i < subdivisionWidth; ++i)
  {
   poly->verticesArray[k] = j * (subdivisionWidth + 1) + i;
   poly->verticesArray[k + 1] = j * (subdivisionWidth + 1) + i + 1;
   poly->verticesArray[k + 2] = (j + 1) * (subdivisionWidth + 1) + i + 1;
   poly->verticesArray[k + 3] = (j + 1) * (subdivisionWidth + 1) + i;
   k += 4;
  }
 }

 return poly;
}

vertex의 텍스쳐 좌표는 line 16에서 연산된다. (원문기준). 이것은 또한 좌표들이 [0, 1]의 범위에 있는 한 공간이다. gird의 upper left corner에 있는 vertex는 텍스쳐 좌표로 (0,0)이다. 반면에, lower-right coordinate에 있는 정점은 텍스쳐 좌표[1,1]이다. 따라서 noise image에서 lookup하기 위해 vertex texture coordinates를 사용하는 것이 쉽게 된다.

이 예제에서, 우리가 2D noise function으로 부터 값을 읽기위해 noise image를 사용하지만, 만약 원한다면 우리가 2D noise function을 직접적으로 evaluate를 할 수 있따는 것에 주목해라. 우리는 2D noise function의 결과를 image file로 output하기 위해 첫 번째 챕터에서 만든 그 배열을 재사용하기로 결정했다. 한 image가 한 오브젝트의 정점을 움직이게 하기 우해 사용될 때, 우리는 이 이미지를 height map이라고 말한다.

height map의 경우에, 우리는 일반적으로 displacement의 amplitude를 제어하기 위해 픽셀들의 color의 밝기 (예를들어, luminance)를 사용한다. 일반적으로 픽셀이 밝을수록, displacement는 더 커진다. 비록 물론 너가 pixel value를 너가 바란다면 완전히 다른 방식으로 displacement로 map할 수 있을지라도. 그것은 모두 너가 만들고자 의도하는 효과에 달려있따. 너가 기억할 필요가 있는 것은 양을 어느정도 조절하기 위해 한 이미지를 사용한다는 것이고. 그 양에서, 오브젝트들의 정점들이 옮겨지거나 예를들어, 그것들의 normal을 위해 움직여진다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
for (unsigned j = 0; j < imageHeight; ++j)
{
 for (unsigned i = 0; i < imageWidth; ++i)
 {
  // Perlin noise is in the range [-1:1]
  float perlinNoise = PerlinNoise::evalAtPoint(Vec3f(i, j, 0) * (1 / 128.f));
  noiseMap[j * imageWidth + i] = (perlinNoise + 1) * 0.5;
 }
}

// displace
for (uint32_t i = 0; i < poly->numVertices; ++i)
{
 Vec2f st = poly->st[i];
 uint32_t x = std::min(static_cast(st.x * imageWidth), imageWidth - 1);
 uint32_t y = std::min(static_cast(st.y * imageHeight), imageHeight - 1);
 poly->vertices[i].y = 2 * noiseMap[y * imageWidth + x] - 1;
}

Perlin noise의 값들이 [-1,1]의 범위에 있다는 것을 명심해라. 그러나 우리는 이러한 값들을 [0,1]로 remapped한다. 우리가 이미지 버퍼에 그 값들을 저장할 때 (line 5).

우리가 (line 15)에서 정점들을 나중에 옮길 때 그 값들이 다시 [-1,1]로 mapped될 지라도, 그 mesh는 y축을 따라서 원점 주변에 있을 것이다. 우리는 그 정점들을 위 (만약 그 값들이 0보다 크다면) 또는 아래로(그 값들이 0보다 작다면) 밀 것이다. 그리고 만약 그 값이 0 이라면 그 정점 y좌표는 0으로 될 것이다.

만약 너가 이 mesh를 texture map으로서 꼭대기에 noise image와 함께 렌더링한다면, 너는 이 챕터의 첫 번째 이미지와 비슷한 것을 얻는다. noise image의 white/bright area가 mesh에서 bump와 어떻게 일치하는지를 주목해라. 반면에 image에서 어두운 부분들은 dents or valleys와 일치한다 (그리고 displacement의 양은 그 픽셀 값에 비레한다는 것에 주목해라).

이 챕터의 introduction에서 언급되었듯이, 너는 mesh vertices를 옮기기 위해 좀 더 흥미로운 procedural patterns을 사용할 수 있다, fractal pattern같은. 그리고 이것은 noise layers들의 weighted sum으로 구성될 수 있다. noise function을 사용하여 fractal pattern을 생성하는 것을 배우기 위해 noise에 대한 이전 강의를 체크해라. 여기에 mesh를 옮기기 위해 사용된 fractal image를 생성하는 코드가 있다.

fractal image는 일반적으로 single layer를 가진 noise에서 보다 더 높은 frequency의 세부사항을 포함한다. 따라서, displacement에서 이러한 세부사항을 보기 위해, 너가 그 mesh의 density를 그 자체로 증가시켜야만 할 가능성이 높다. 여기에 fractal image로 displaced 된 mesh의 render가 있다.

이전 강의에서 제안되었듯이, 이 기법은 현실적인 terrains을 생성하는데 사용될 수 있다 (바라건데 위의 이미지가 충분히 설득적이기를 바란다. 이 예제에서 우리가 사용한 (fractal) procedural pattern은 꽤 간단하다. 너는 terrain의 look을 변경하기 위해 그 파라미터들 가지고 놀 수 있지만, 우리는 또한 terrain의 realism을 증가시키기 위해 erosion같은 효과들을 어떻게 추가할지를 배울 것이다.

final note로서, 우리는 displacement후에 mesh의 normal를 연산하지 않았다. 우리는 어떻게 그것을 하는가? 이것은 우리의 다음 챕터의 주제이다. 우리는 displacement 후에 vertex position에서 "true" normal를 연산하기 위해 noise function derivatives를 어떻게 사용하는지를 배울 것이다.

===================================================
여기까지 하는데 시간이 걸렸지만, 일단은 Terrain 그렸다.












댓글 없음:

댓글 쓰기