Post Lists

2018년 12월 24일 월요일

Scratchpixel Procedural Generation of Virtual Worlds Value Noise and Procedural Patterns: Part1

https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1

이걸 Introduction부터 차근차근 공부해 나가겠다.

Value Noise and Procedural Patterns: Part 1

Introduction
Keywords: value noise, 절차적 패턴 생성, deterministic, random number, RNG, 주기적인, 지속성, differentiability, 샘플링, aliasing, white noise, solid textures, permutation, hash table.

"물리학자에게, 시간 t에 변하는 어떤 양 V의 예측할 수 없는 변화는 noise라고 알려져 있다." (The Science of Fractal Images, Richard F. Voss).

이 수업은 noise의 개념을 매우 간단한 (거의 naive) 형태로 설명한다. 너는 noise가 무엇인지, 그것의 특성은 무엇이고, 그것으로 무엇을 할 수 있는지를 배울 것이다. Noise는 이해하기에 복잡한 개념은 아니지만, 그것은 많은 중요 요소들을 가진다. 그것을 올바르게 상요하는 것은 그것이 어떻게 작동을하고 그것이 어떻게 만들어지는지에 대한 이해를 요구한다. 어떤 이미지를 만들고 다양한 파라미터로 실험하기 위해서, 우리는 간단한 (그러나 완전히 기능적인) value noise라고 알려진 noise 버전을 구현할 것이다. 이 강의를 읽는 동안 우리가 여기에서 완전히 공부되기에 너무 복잡한 많은 기법들을 넘어가고 있따는 것에 명심해라.

이것은 noise에 대한 간단한 개론이고, 그것의 가능한 프로그램의 몇몇을 보옂운다. 웹사이트는 많은 수업을 제공하는데, 거기에서 이 수업에서 언급되는 각 주제는 개별적으로 연구될 수 있다 (aliasing, texture generation, complex noise functions, landscape cloud and water surface generation, 뿐만 아니라 bitwise arithmetic, hashing, 등등.)

Historical Background
Noise는 objects에 텍스쳐를 입히는데 이미지를 사용하는 대안으로서 80년대 중반에 개발되었다. 우리는 오브젝트의 외관에 시각적 복잡성을 더하기 위해 이미지로 오브젝트들에 매핑할 수 있따. CG에서, 이것은 texture mapping으로 알려져있다. 그러나 80년대 중반에, 컴퓨터들은 매우 제한된 메모리를 가지고 있었고, 텍스쳐링을 위해 사용된 이미지들은 쉽게 RAM 맞지 않았다. CG 스튜디오에서 일한 사람들은 대안 해법을 찾기 시작했었다. solid colors로 렌더링 되는 오브젝트들은 깔끔하게 보였다. 그것들은 그것들의 표면을 가로질러 오브젝트들의 시각적 특성들 (color, shininess)를 조절하여 이 clean look을 망가트리는 어떤 것이 필요했다. 프로그래밍에서, 우리는 random numbers를 만들 필요가 있을 때 마다 random number generators를 사용한다. 그러나 3D 오브젝트의 외간에 변형을 더할 RNG를 사용하는 것은 충분하지 않다. 우리가 자연에서 관찰할 수 있는 무작위 패턴들은 보통 부드럽다. 실제 오브젝트의 표면에서 두 점들은 보통 거의 같다. 그것들이 서로에 꽤 가까울 때. 그러나 한 같은 오브젝트에 멀리 떨어져 있는 표면의 두 점들은 매우 다르게 보일 수 있다. 다시 말해서, local changes는 점진적인 반면에, global changes는 클 수 있다. RNG는 이 특성을 갖지 않는다. 우리가 그것들을 호출할 때 마다, 서로에게 관련이 없는 숫자들을 반환한다.

그러므로 이 함수를 호출하는 것은 매우 다른 숫자를 만들어낼 가능성이 높다. 이것은 공간적으로 가까운 두 점의 시각적 외관에 느슨한 변형을 도입하기에 적절하지 않다. 여기에 한 예시가 있다: 실제 바위의 이미지를 관찰해보자 (그림 1의 왼쪽) 그리고 우리의 업무가 이 오브젝트의 외관을 복제하는 CG imgae를 만드는 것이라고 가정하자. 너는 텍스쳐들을 사용할 수 없다 (가장 명백한 솔루션은 이 이미지를 가져다가 그것을 평면에 매핑 하는 것이다). 우리가 가진 것은 텍스쳐링이 없이 완전히 평평해 보이는 한 평면이다 (uniform color). 이 예제는 흥미로운데, 왜냐하면 우리는 rock pattern이 세 개의 주된 컬러들로 만들어져 있따는 것을 관찰할 수 있기 때문이다 : green, pink, and grey. 이러한 색들은 다소 바위의 표면을 가로질러서 동일한 양으로 분배되어 있다. 이 문제에 대한 첫 번째 명백한 솔루션은 이 이미지를 예를들어 포토샵으로 열어서, 이러한 세 개의 평균 색들 중 각 하나를 집어서, 그것들을 한 프로그램에서 재사용할 수 있게 하기 위해서, 절차적으로 우리의 레퍼런스 픽쳐에 다소 유사하게 보이는 패턴을 생성하는 것이다. 이것은 다음의 코드가 하는 것이다. 그러나 지금, 픽셀 사이의 변형을 만들기 위해 우리가 할 수 있는 것은 C drand48() 함수를 사용하는 것인데, [0:1] 범위의 랜덤 값을 반환한다.


#include <iostream>
#include <fstream>
#include <sstream>
#include <cmath>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#define STBI_MSC_SECURE_CRT
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

#include "GPED_random.h"

#define CHANNEL_NUM 3
struct Color3f
{
 float r, g, b;

 float operator[] (unsigned i)
 {
  assert(i >= 0 && i <= 3);
  return (&r)[i];
 }
};

inline unsigned min(const unsigned& a, const unsigned& b) { return (a < b ? a : b); }

int main()
{
 const int width = 512;
 const int height = 512;

 const unsigned kNumColors = 3;
 Color3f rockColors[kNumColors] = {
   { 0.4078, 0.4078, 0.3764 },
   { 0.7606, 0.6274, 0.6313 },
   { 0.8980, 0.9372, 0.9725 } };

 /*** NOTICE!! You have to use uint8_t array to pass in stb function  ***/
 // Because the size of color is normally 255, 8bit.
 // If you don't use this one, you will get a weird imge.
 uint8_t* pixels = new uint8_t[width * height * CHANNEL_NUM];
 

 GPED::Random myRand(3324);
 int index = 0;
 for (int i = 0; i < width; ++i)
 {
  for (int j = 0; j < height; ++j)
  {
   unsigned colorIndex = min(unsigned(myRand.randomReal() * kNumColors), kNumColors - 1);
   pixels[index++] = rockColors[colorIndex][0] * 255;
   pixels[index++] = rockColors[colorIndex][1] * 255;
   pixels[index++] = rockColors[colorIndex][2] * 255;
  }
 }
 
 stbi_write_png("stbpng.png", width, height, CHANNEL_NUM, pixels, width * CHANNEL_NUM);

 
 
 delete[] pixels;
 return 0;
}


너가 그림 1의 중간에서 볼 수 있듯이, 이 프로그램의 결과는 납득가지 않는다 (사실 이 패턴은 이름을 갖고 있는데; 그것은 white noise라고 불려진다. 우리는 나중에 white noise가 무엇인지 설명할 것이다. 그러나 지금은, 그것이 그림 1에서의 이미지 처럼 생겼다는 것을 기억해라). 우리는 절차적으로 생성된 텍스쳐의 각 픽셀에 대한 한 color를 선택하기 위해 RNG를 사용하고, 이것은 픽셀마다 큰 변형을 만들어 낸다. 그 결과를 향상시키기 위해, 우리는 텍스쳐의 작은 지역 (10X10 pixels)을 복사했고, 원래 이미지 크기에 (256x256 pixels) 사이즈를 조정했다 (크게 했다). 그 프레임의 이 부분을 확장시키는 것은 픽셀을 흐리게 만들지만, 그 결과를 심지어 더 부드럽게 만들 것이다. 우리는 Photoshopt에서  gaussian blur를 적용한다. 우리는 아직 reference image와 잘 부합하지 않는다. 그러나 너가 볼 수 있듯이, 그 최종 이미지 (그림 1에서 오른쪽 이미지)는 원래의 것(중간의 것) 보다 더 좋다. Locally 우리는 작은 변형으 ㄹ갖는데 (픽셀에서 color까지 color에서 작은 변화들) , 반면에 globally하게 우리는 큰 변형을 갖는다 (세 입력 컬러들의 혼합)

이 실험의 결론은 smooth random pattern을 만들기 위해서, 우리는 한 grid에 고정된 위치에서 RNG를 사용해서 우리가 lattice라고 부르는(우리의 예제에서 grid는 우리의 10x10 input image의 pixel 위치와 대응된다) random values를 할당할 필요가 있다. 그리고 이러한 값을 gaussian blur와 비슷한 걸 사용해서 흐리게 한다 (이러한 랜덤 값들을 blur시키는 smoothing function). 다음 챕터에서, 우리는 너에게 이것이 어떻게 구현되어질 수 있는지를 보여줄 것이다. 그러나 지금에서 너가 기억할 필요가 있는 모든 것은 noise (컴퓨터 그래픽스의 문맥에서)는 grid에서 생성된 random values를 (우리가 종종 lattice라고 부르는) blurs시키는 함수이다.

우리가 설명했던 그 프로세스는 bilinear interpolation과 유사하다 (interpolation 강의를 보아라). 다음의 예제에서 (그림 2), 우리는 10x10의 2D grid를 만들었고, grid의 각 정점에 random number를 설정했다. 그리고 더 큰 이미지를 렌더링하기 위해 linear interpolation을 사용했다.

너가 볼 수 있듯이, 그 결과의 품질은 매우 좋다. 우리는 interpolation 수업에서, bilinear interpolation이 data를 보간시키는 가장 간단한 가능한 기법이라고 지적했었고, 그 결과의 품질을 향상시키기 위해 (필요하다면) 더 높은 차수의 interpolants (data를 보간시키는데 사용되는 함수) 사용하는 것이 가능하다고 했다. 이것은 다음 챕터에서 상세히 설명될 것이고, 우리는 2D grid data의 이 간단한 linear interpolation보다 더 좋은 결과를 제공하는 noise function을 쓸 수 있을 것이다.

noise의 가장 인기있는 구현은 Ken Perlin에 의해 1983년에 쓰여졌다 (그리고 아마도 그것의 종류에서 첫 번쨰 것이다) 그 와중에 그는 film Tron의 1982 버전에 작업중이였다 만약 너가 그의 웹사이트를 간다면 (또는 너가 가장 좋아하는 search engine에서 Ken Perlin을 찾아라), 너는 쉽게 페이지에 대한 레퍼런스를 찾을 수 있을 것인데, 거기에서 Ken Perlin은 스스로 그의 noise pattern의 역사를 알려준다. Ken Perlin은 1997년에 영화에 대한 그의 공헌으로 Academy of Motion Picture Arts and Sciences로 부터 Academy Award for Technical Achievement를 수상했다. 그는 그의 작업을 1984년 Siggraph에서 보여주었고, 1985년에 한 논문 "An Image Synthesiser"를 출판했다. 그것은 texture synthesis 분야에서 주앧한 것이다. 만약 너가 noise function의 기초에 대해 배우는 것에 관심이 있다면 (그리고 그것의 발전 역사), 이 논문을 읽는 것이 매우 추천된다. Perlin noise는 lesson Noise Part 2에서 설명된다.

The World of Procedural Texturing
noise의 발전은 컴퓨터 그래픽스에서 완전 새로운 연구 분야를 이끈다. Noise는 기본 building block으로 보여질 수 있는데, 거기에서 많은 흥미로운 절차적 텍스쳐들 (또는 solid textures라고 불리는, Texturing에 대한 강의를 보아라)이 생성된다. procedural texturing의 세계에서, 텍스쳐들의 많은 유형들이 생서되는데, 그것들은 자연의 패턴들을 항상 닮을려고 노력하지 않는다. 예를들어, brick wall type으 패턴을 생성하는 프로그램을 쓰는 것은 간단하다. 어떤 패턴들은 규칙적이거나 불규칙적이거나, 규칙적인 패턴으로부터 떨어져서 stochastic (non deterministic)이다 (그리고 이것은 오직 기하학적 규칙에 복종하고, 도형에 들어맞는다), 모든 다른 패턴들은 불규칙성을 도입하기 위해 noise를 사용할 수 있고, 절차적으로 생성된 텍스쳐에 명백한 무작위성을 이끌 수 있다. Ken Perlin이 noise function의 그의 버전을 도입한 이후로, CG community에서 많은 사람들은 복잡한 materials, objects를 모델링하는데 그것을 사용하기 시작했다. 예를들어, terrains, clouds, or animating water surfaces. Noise는 한 오브젝트의 visual appearance를 바꾸는 것에만 제한되지 않지만, procedural modeling에 사용되어질 수 있다, 이것은 한 오브젝트의 표면을 바꾸기 위해서 (terrains을 생성하기 위해서)이다 또는 한 부피의 밀도를 조절하거나 (cloud modelling). 프레임마다 noise input을 offsetting하여, 우리는 절차적으로 animating objects를 위해 그것을 사용할 수 있다. 물의 표면을 animating하는 것에대해 90년대 중반까지 가장 인기있는 방법이였다 (Jerry Tessendorf가 90년대 후반에 좀 더 현실적인 방법을 제안했었다. Animating Ocean Water 강의를 보아라).

어떤 간단한 solid textures (fractal)의 예제들은 이 강의의 마지막 챕터에 주어진다. Texture Synthesis에 대한 강의는 이 주제애 대한 너에게 더 많은 정보를 줄 것이다.

Main Advantage/Disadvantage of Noise
우리가 도입에서 언급했듯이, noise는 compact하다는 장점을 가진다. 그것은 texture mapping과 비교하여 많은 메모리를 사용하지 않고, noise function을 구현하는 것은 매우 복잡하지 않다 (noise function은 작은 데이터 용량을 요구한다). 이 매우 간단한 함수로 부터, 많은 다양한 텍스쳐를 만드는 것이 또한 가능하다 (우리는 이 강의의 마지막 챕터에서 몇 가지 예제를 줄 것이다). 마지막으로, 한 오브젝트에 텍스쳐를 더하기 위해 noise를 사용하는 것은 texture mapping을 위해 보통 필요한 surface의 어떤 parametrisation을 요구한다. (우리는 texture coordinates가 필요하다).

반면에, 그것들은 texture mapping보다 더 느리다. noise function은 꽤 작은 수학 연산들의 실행을 요구한다 (비록 그거슫ㄹ이 간단할지라도, 몇 가지 것들이 있다). 반면에 ampping은 오직 memory에 불러와진 텍스쳐의 픽셀에 대한 접근만을 요구한다.

Properties of an Ideal Noise
ideal noise가 가져야 하는 모든 특성들을 열거하는 것이 가능하다면, noise function의 모든 구현들이 그것들에 부합하는 것은 아니다 (꽤 몇 버전들은 존재한다).

Noise는 pseudo-random이고, 이것은 아마도 그것의 주된 특성이다. 그것은 random하게 보이지만, deterministic하다. 같은 입력이 주어진다면, 그것은 항상 같은 값을 반환한다. 만약 너가 한 film을 위해 여러번 이미지를 렌더링한다면, 너는 noise pattern이 일관되거나/예측가능하길 원한다. 또는 만약 너가 예를들어 한 평면에 이 noise pattern을 적용한다면, 너는 그 패턴이 프레임마다 같기를 원한다 (비록 그 카메라가 움직이거나 그 평면이 변형될지라도. 그 패턴은 그 평면에 붙어있는다. 우리는 그 함수가 invariant하다고 말한다: 그것은 변형할 때 바뀌지 않는다).

noise function은 항상 float를 반환하는데, 입력 값이 어떤 차원이든 그렇다. point의 차원은 noise의 이름에 주어진다. 1D, 2D, 3D noise 함수들은 입력 파라미터들로서 1D, 2D, and 3D points를 받는다. 심지어 4D noise도 있는데, 그것은 3D point를 입력 파라미터로 받고, 시간에 따라 noise pattern을 animate하기위해 사용되는 부가적인 float value를 받는다. 1D와 2D noise 예제들의 이 강의에서 주어진다. 3D와 4D noise 예제들은 Noise Part 2에서 주어진다. 수학적인 용어에서, 우리는 noise function이 Rn에서 R로의 mapping이라고 말한다 (n은 noise function에 넘겨지는 값의 차원이다). 그것은 n차원의 점을 실수 좌표와 함께 입력으로 받고 float를 반환한다. 1D noise는 objects를 animating하는데 사용되고, 2D와 3D는 objects를 texturing하는데 사용된다. 3D noise는 특히 부피의 밀도를 조절하는데 유용하다.

Noise는 band limited이다. noise가 주로 한 함수라는 것을 기억해라. 너는 그것이 한 신호라는 것을 알 수 있다 (만약 너가 그 함수를 그린다면, 너는 신호인 한 곡선을 얻는다). signal processing에서, 신호를 받아서 그것을 spatial domain에서 frequency domain으로 바꾸는 것이 가능하다. 이 연산은 한 결과를 주는데, 거기에서 signal이 만들어지는 다른 주파수를 보는 것이 가능하다. 만약 너가 spatial에서 frequency space로 가는 것이 무슨 의미인지 이해하지 못했다면 너무 걱정하지 말라 (그것은 여기에서 주제에서 벗어나지만, 너는 Fourrier Transform에서 그 강의를 확인할 수 있다). 너가 기억할 필요가 있는 모든 것은 noise function이 잠재적으로 다양한 주파수로 구성되어져 있다는 것이다 (낮은 주파수들은 큰 scale changes를 차지하고, 높은 주파수들은 작은 변화를 차지한다). 그러나 이러한 주파수들 중의 하나는 모든 다른 것들을 지배한다. 그리고 이 한 주파수는 너의 noise function의 visual and frequency appearance/characteristic 둘 다를 정의 한다 (만약 너가 너의 신호를 frequency space에서 본다면). 왜 우리는 noise function의 주파수에 신경써야 하는가? noise가 프레임에서 작을 때 (카메라로부터 멀리있는 noise로 텍스쳐된 오브젝트를 그린다고 할 때), 그것은 다시 white noise가 되고, 우리가 전문용어로 aliasing이라고 부르는 것의 원인이 된다. 이것은 그림4에서 보여진다.

이 이미지의 배경에서, 이웃 픽셀들은 랜덤 값들을 취한다. 이것은 시각적을 유쾌하거나 현실적이지 않을 뿐만 아니라, 만약 너가 연속의 이미지를 렌더링한다면, 이 지역에 있는 픽셀들의 값은 프레임마다 극적으로 변할 것이다 (figure 4). Aliasing은 sampling의 주제와 관련되어 있고, 그것은 컴퓨터 그래픽스에서 매우 크고 중요한 주제이다. 우리는 이 원치 않는 이펙트를 발생시키는 것이 무엇인지 이해하길 원한다면 sampling 강의를 읽기를 초대한다.

우리는 noise가 lattice points에서 발생한 random values를 blur하는 smooth function을 사용한다고 언급했다. 수학에서 함수들은 특성들을 갖는다. 그것들의 두 가지는 특히 이 강의의 맥락에 흥미가 있다: continuity and differentiability. 이러한 것이 무언인지 설명하는 것은 여기에서 주제를 벗어나지만, 간단한 예제로 너는 직관적으로 그것이 무엇을 의미하는지 이해할 것이다. 한 도함수는 그림 5에서 보여지듯이 한 곡선의 profile에 접하는 직선이다. 그러나, 만약 그 함수가 연속(continuous)하다면, 이 도함수를 연산하는 것은 가능하지 않다 (한 함수는 또한 연속할 수 있지만 모든 곳에서 미분가능하지 ㅇ낳다, 그림 5에서 오른쪽에 보이듯이). 우리가 나중에 설명할 이유들 때문에, noise function의 도함수들을 연산하는 것은 어떤 장소에서 필요할 것이다. 그리고 연속하고 미분가능한 둘 다인 smooth function을 선택하는 것이 가장 좋다. Ken Perlin에 의해 만들어진 noise function의 원래 구현은 연속이지 않는 한 함수를 사용했었다. 그리고 그는 몇년 뒤에 이 문제를 고치기 위해 또 다른 것을 제안했다.

우리는 noise curve에서 어디에서나 접선을 찾을 수 있어야 한다. 만약 이 곡선에서 접선이 발견될 수 없거나 그것이 급작스럽게 변하는 한 장소가 있다면, 그것은 우리가 discontinuities라고 부르는 것을 발생시킨다.

마지막으로 너가 한 오브젝트에 적용된 noise를 볼 때, 이상적으로 너는 noise pattern의 반복을 보아서는 안된다. 우리가 제안하기 시작하듯이, noise를 미리 정의된 크기의 gird로부터 연산되어질 수 있다. 위의 예제에서, 우리느 10x10 grid를 선택했었다. 만약 우리가 그 grid의 영역 밖에서 noise를 연한다면, 우리는 어떻게 될 까? Noise는 tile과 같고, 어떤 차원의 타일들이 있는 더 큰 범위를 덮기 위해서, 너는 이러한 많은 타일들을 서로 옆에 배치할 필요가 있을 것이다. 그러나 이 접근법에 두 가지 명백한 문제점이 있을 것이다. 타일들의 영역에서, noise pattern은 불연속할 것이다.

이상적으로 너가 원하는 것은 보이지 않는 타일간의 변환이다. 그래서 너는 무한히 큰 영역을 seam을 보지 않고 다룰 수 있따. CG에서 2D texture가 seamless할 때, 두 방향(x and y)에서 periodic하다고 말해진다. tileable이라는 단어가 또한 가끔씩 사용되지만 혼란스럽다. 어떤 텍스쳐는 tileable하고, seamless하지 않을지도 모른다. 이상적으로 너의 noise function은 그 pattern이 periodic하도록 설계되어야 한다. 게다가, 너는 타일들에 같은 패턴을 가진 것을 사용하기 때문에, 너는 그 패턴의 반복을 알아차릴지도 모른다. 이 문제에 대한 해결책은 이 강의의 단계에서 설명하기에 조금 더 어렵다. 너가 지금 알 필요가 있는 것은 noise function에 의해 만들어진 패턴이 큰 특징들을 갖지 않는다는 것이다 (모든 특징들은 대충 같은 크기를 가즌다. 함수가 band limited된다는 것에 대해 말했던 것으 기억해라). 그것이 의미하는 것은, 만약 너가 zoom out한다면, 너는 큰 특징들이ㅡ 반복을 보지 않을 것이다, 왜냐하면 그것들이 함수에 있지 않기 때문에. noise가 만들어지는 다른 더 작은 특징들에 대해, 너가 zoom out을 많이 할 즈음에, 그것들은 보여지기에 너무 작다. pattern이 안보이게 하기 위한 트릭은 그것을 충분히 크게 하는 것이다 (lattice points의 grid를 충분히 크게) 이것은 너가 그것을 전체 screen에 덮을 즈음에, 그 특징은 프레임에서 정발로 보기에 작다. 우리는 이 효과를 다음 챕터에서 보일 것이다.

댓글 없음:

댓글 쓰기