Improved Perlin Noise
몇 년 뒤에 (사실 2002년에, 그래서 Perlin이 noise에 대한 그의 첫 번째 논문을 출판한지 17년 뒤에), Perlin은 그의 noise function의 새로운 개선된 버전을 제안했었다. 그 원래 noise function은 두 가지 문제들을 가지고 있다.
The smoothstep function is not ideal and ehre is why...
첫 번째로, 우리가 gradients와 cell내에서 점을 향하는 벡터 사이의 내적의 결과를 보간하기 위해 사용했던 interpolatns는 smoothstep function이라는 것을 기억해라. 이 함수는 3t^2 - 2t^3의 형태를 가진다. 또한 우리가 이 함수의 도함수인 (6t - 6t^2)을 noise function의 편도함수를 연산하기 위해 사용했다는 것을 기억해라. 이전 챕터에서 설명했었 듯이, 이러한 도함수들은 noise function으로 옮겨진 정점들의 "true" normal를 연산하는데 유용하다 (수학적으로 올바른). 이제 이것과 관련한 문제는 그 smoothstep function derivative의 도함수가 (우리가 수학에서 f''(x)의 2차 도함수라고 부르는) t = 0과 t = 1에서 연속하지 않다는 것이다. 그 smoothstep function의 2차 도함수는 6 - 12t이다. 그래서 t = 0일 때, f''(x) = 6이고, t = 1일 때, f''(x) = -6이다. 이것은 문제인데, 왜냐하면 옮겨진 메쉬의 normal를 연산하기 위해 사용된 함수의 도함수가 연속이지 않다면, 그러면 그것은 이러한 normals들에서 불연속을 도입할 것이기 때문이다 ,t = 0이고 t = 1인 부분에서. 그것을 옮겨진 메쉬의 정점들이 noise grid cells의 edges와 정렬할 때 발생한다 (즉, 정점좌표들이 정수값을 가질 때). 이러한 시각적 artifacts들은 명백히 아래의 (왼쪽) 이미지에서 보인다.
{
- 생각 1
왜 smoothstep 3t^2 -2t^3 함수의 2차 도함수가 불연속인지 이해가 안된다. 글이나 페를린이 설명하는 것을 보면 2차 도함수의 가 t = 0일 때와 t = 1일 때 값이 6과 -6이라는 것이 연속하지 않다라고 말하는 이유인데. 이것으로 따지자면, noise texture의 입장에서 도함수들의 값이 계속 같아야 연속하지 않는 것으로 말하는 것 같다. 정확히 왜 불연속인지 모르겠다. 어쨋든 이미지상으로는 그런 끊기는게 확인되니 이렇게 알고 넘어가자.
- 생각 2
페이스북 생활코딩에 물어봐서 해결했다. 역시 세상에는 고수들이 많이 있다.
https://www.facebook.com/groups/codingeverybody/permalink/2595918853781968/
요점은 어떤 한 vertex에서 다른 vertex로 넘어갈 때 이다.
즉 smoothstep의 2차도함수가 6 - 12t인데 그것의 t가 0일 때 6이고 t가 1일 때 -6인데
이 때, 어떤 vertex v0과 v1의 smoothstep되는 그래프를 그려서 2차도함수를 본다면
v0의 t = 1 장소와 v1의 t = 0 장소에서 2차 도함수 값이 다르기 때문에 당연히 불연속이 될 것이다.
}
noise function deriavtive를 연산하는 analytical solution 수학적으로 옳은 반면에, 이것이 그 결과가 반드시 시각적으로 우리가 찾는 것이 아니라는 것을 이해하는 것이 중요하다. 이것은 수학적 구성일 뿐이고, 이 construct는 우리가 좋아하지 않는 특성을 가질지도 모른다. 그래서 그 결과는 수학적으로 옳을지라도, 그것들은 우리가 피하고싶은 시각적 artefacts를 만든다.
게다가, 이 함수의 문제를 언급하는 대부분의 문서들은 왜 우리가 이 문제를 갖는지를 설명하지 않고 그리고 그것을 시각화하는 방법을 제공하지 안흔다. 왜 그 문제가 첫 번째 장소에 있는지를 더 잘 이해하기 위해서, 너는 함수들과 함수 도함수들에 대한 세부사항을 기억할 필요가 있다. 물리학에서, 공통 함수들은 너가 움직이는 오브젝트의 위치를 시간의 함수로서 연산하게 해주는 함수이다. 이 함수를 p(t)라고 부르자. 만약 너가 물리학 강의를 기억한다면, 너는 이 오브젝트의 속도를 이 함수의 시간에 대한 도함수로 연산할 수 있다. 다시 말해서, p'(t) = P(t)/\delta t. 그리고 마지막으로 너는 그 오브젝트 가속도를 시간에 대한 그 속도함수의 도함수로 연산할 수 있다, 또는 p''(t) = p'(t) / \delta t. 그래서 본질적으로 너는 함수 f(x)와 그것의 도함수 f'(x) and f''x()를 개별적으로 한 오븢게트의 psotiion, speed, acceleration으로 볼 수 있다. 우리의 예제에서, smoothstep function은 분석적으로 position이고, tangent를 연산하기 위해 사용했던 smoothstep function의 1차 도함수는 spped이고, smoothstep function의 마지막 이차 도함수는 가속도이다. 물론 이러한 것은 analogies이다. 하지만 요점은, 만약 그 가속도가 불연속성을 가진다면, 그러면 우리는 그 속도가 급작스럽게 때때로 변한다는 것을 기대할 수 있다. 우리의 특별한 예제에서, 이것은 만약 그 smoothstep function second order derivative가 불연속성을 가진다면, 그러면 우리는 그 접선이 때때로 급작스럽게 변하는 것을 기대할 수 있다. 그리고 그것은 정확히 cell boundaries에서 visual artefacts가 발생하는 것이다. 이러한 여역들의 normals들은 급작스럽게 바뀐다.
컴퓨터 그래픽스와 수학에서, original function이 연속일 떄, 우리는 geometry continuity라고 말하고, 우리는 그 함수를 C0이라고 말한다 (또는 G0, 우리가 기하를 다룰 때). 만약 그것의 도함수가 연속이라면, 우리는 tangent continuity라고 말한다 (C1 or G1). 그리고 그 함수 2차 도함수가 또한 연속일 때, 우리는 curvature continuity라고 말한다. 다시 말해서, 두 곡선 또는 두 평면이 join하는 curvature은 접촉 점에서의 곡선들 또는 두 표면에 대해서 같다.
아래의 이미지는 noise function의 slice를 보여준다. 그리고 그것의 analytically 연산된 normals를 보여준다. normals의 orientation과 이러한 normals의 directions이 cell boundary의 각각에서 같지 않다는 것이 명백하다.
바라건대 해결책이 있다. 만약 유일한 문제가 그 smootstep function의 이차 도함수가 불연속하는 거라면, 그 문제를 고치기 위해 우리가 할 필요가 있는 것은 명백히 2차 도함수가 연속인 interpolant에 대한 함수를 고르는 것이다. smoothstep 함수와 유사한 도형을 가진 것들이 꽤 있지만, Perlin은 이 quintic function을 고른다 (그림 1):
6t^5 - 15t^4 + 10t^3.
그것의 1차 도함수는
30t^4 - 60t^3 + 30t^2
그리고 그것의 2차 도함수는
120t^3 - 180t^2 + 60t
너가 볼 수 있듯이 이 도함수는 연속이다 (t = 0과 t = 1일 때, 그 함수가 0이다).
Choosing random gradients is not ideal and here is why...
그 다음 문제는 gradients가 무작위로 선택되기 때문에, 가끔씩 그 gradient directions는 같은 축을 따라 또는 그 cell의 대각선을 따라 정렬된다 ,그림 2에서 [missing] 보여지듯이. 그 noise function은 이러한 영역 (1에 가까운)에서 매우 높은 값을 갖는 경향이 있고, 그 영역은 부분적으로 원래 Noise implementation의 "splotchy appearance"를 만든다.
바라건대, 이 문제는 서로 다른 12개의 방향의 한 집합으로 256개의 random directions를 대체하여 쉽게 해결될 수 있다 (그리고 이러한 경사들이 같은 방향을 따라서 가리키는 상황을 제외하고). 그 permutation table은 풍부한 randomness를 제공한다. 그래서 실제로, 만약 이러한 12개의 벡터들이 미리 정해져있는지 아닌지를 중요하지 않다. 그의 2002 논문에서 Perlin은 다음의 벡터들을 선택하기로 결정했었다:
(1, 1, 0), (-1, 1, 0), (1, -1, 0), (-1, -1, 0),
(1,0,1,), (-1,0,1),(1,0,-1), (-1,0,-1),
(0,1,1), (0,-1,1), (0,1,-1), (0,-1,-1)
그런데, 우리의 permutation table이 [0,255] 범위의 정수들을 반환한다는 것을 기억해라. 그래서 이러한 벡터들 중 하나를 선택하기 위해서, 우리는 P[Hash[x,y,z]] %12와 같은 것을 할 필요가 있다. 너가 알듯이 뤼가 우리는 modulo operator를 사용하길 좋아하지 않는다. 왜냐하면 비트 연산자보다 더 느리기 때문이고, 그것은 음수를 다르게 처리하기 때문이다 (비트 연산자는 이 문제를 고친다). 그 modulo 연산자를 비트 연산자로 바꾸기 위해서, Perlin은 12개의 벡터들의 배열을 16개의 벡터들로 확장하는 것으로 제안한다. 그래서 다음의 4개의 방향을 처음 12개의 추가한다:
(1,1,0), (-1,1,0), (0, -1,1), (0, -1,-1).
우리는 이미 이러한 방향들을 상요했지만, Perlin은 처음 12개의 방향들이 regular tetrahedron(4면체)를 형성하기 때문에, 이러한 방향을 중복으로 추가하는 것은 텍스쳐에 어떤 bias를 도입하지 않는다고 주장한다.
최종 결과는 original distribution과 같은 non-directional apperance를 갖지만, 덜 clumping(무리지어 지는)이 된다.
splotchy 같은 apperance는 더 이상 보이지 않는다 (아래의 비교이미지를 체크해보아라). 이 변화를 구현하는 것은 실제로 꽤 간단하다. 우리의 해쉬 함수는 [0,255]의 범위의 숫자들을 반환한다. 그래서 우리는 이 숫자를 [0,15]의 범위로 설정하기 위해 또 다른 비트 연산자를 사용한다.
uint8_t p = hash(xi, yi, zi) & 15;
그러고나서 모든 방향들이 0과 1로 설정되어 있기 때문에, 그 내적이 오른손 좌표계 벡터의 좌표의 합으로 간단하게 된다.
// float a = dot(c000, p000);
// float a = c000.x * p000.x + c000.y * p000.y + c000.z * p000.z;
// for the gradient (1, 1, 0) for instance, this simplifies to:
float a = 1 * p000.x + 1 * p000.y + 0 * p000.z = p000.x + p000.y;
우리는 이 코드를 그것 자신의 함수로 옮길 수 있다.
아래의 이미지는 원래의 Perlin noise 구현과 개선된 버전 사이의 비교를 보여준다:
크게 다르지는 않지만, 두개의 구현은 다른 결과를 준다. 개선된 버전은 vertical and horizontal lines를 만든다. 비록 너가 원래 버전에서 못볼지라도. 그 사실로 인해서, 개선된 버전에서, gradients들은 xy, xz and yz 평면을 따라 정렬읻 ㅚㄴ다. 반면에 interpolant로서 quintic function의 선택은 visual artefacts를 줄어주고 개선물로서 불려질 수 있지만, 우리는 random gradients에 대해 미리 정의된 gradients를 사용하는 것이 반드시 좋은 것이라고 생각하지 않느다. 너는 이 강의의 마지막 챕터에서 개선된 버전의 완전한 구현을 찾을 수 있다.
What's Next?
이것은 Perlin noise에 대한 우리의 강의를 결론짓는다. 이 강의는 꽤 이미 광범위 하지만, 일반적으로 노이즈 함수에 대해 말할 것이 더 많다. 첫 째로, procedural noise를 생성하는 좀 더 많은 방법들이 꽤 존재한다. 몇가지 다른 방법들은 wavelet noise (Pixar에 의해 개발된), Gabor noise, simplex noise이다. simplex noise 또한 Ken Perlin에 의해 개발된 노이즈의 종류이다. 우리는 나중에 이러한 기법들 각각에 대한 강의를 쓸 것이다.
마지막으로, 다른 것들에 비해 noise implementation의 퀄리티를 판단할 수 있는 것이 중요하다. 우리는 그것을 어떻게 하는가? 우리는 이미 한 noise function이 가져야 하는 퀄리티들을 언급해었다. 이러한 퀄리티들 중에서, 그 noise는 가능한 균일한 frequencies의 distribution을 가져야하고, 그러나 여전히 random하고 smooth해 보여야 한다. 한 주어진 noise function이 이 기준에 맞는지를 찾으려는 한 가지 방법은 그것의 output을 frequency 여역에서 분석하는 것이다. 우리는 또한 나중에 기법에 이후의 강의에서 배울 것이다.
How to Use the Noise Function?
일반적으로 procedural noise function은 많은 것들에 사용될 수 있다:
- Terrain : 이 강의에서, 우리는 그 함수 자체의 구현에 대해서 공부했다. 비록 우리가 또한 테레인 같은 것을 만들기 위해서 그것을 어떻게 사용하는지를 보여줬을지라도. 우리는 noise function을 사용하여 아직 정말 복잡하고 현실적인 surface details를 가진 테레인을 어떻게 만드는지 보여주지 않았다. 우리는 이 기법을 아직 나오지 않은 별개의 강의에서 보여줄 것이다.
- Water surfaces: 그것은 또한 water surface를 재현하는데 사용될 수 있다. 그 입력 점을 y좌표를 offsetting하여, 파도같은 이동된 표면을 animate하는 것이 가능하다. 이것은 잘 작동하는 간단한 기법이다. 비록 water surfaces를 재현하는 더 좋은 방법들이 있을지라도.
- Cloud : 구름 같은 volumes에 surface details를 추가하는데 사용될 ㅜㅅ 있다. 우리는 다양한 cloudy shapes의 외관을 재현하기 위해 volume rendering의 강의에서 noise function을 사용한다.
- Texturing : 마지막으로 표면에 texture details를 더하기 위해 사용될 수 있다. 컬러, specular, 같은 쉐이더의 입력을 조절하거나, 한 오브젝트의 표면에 bump를 추가하기 위해 사용될 수 있다.
- Animation: noise function은 또한 애니메이션에 어떤 noise를 더하기 위해 사용되어질 수 있다. 예를들어 일반적인 사용 예시는 어떤 카메라 shakes를 재현하기 위해 camera animation에 noise를 더하는 것이다.
===================================================
Simple Directional Light를 추가해서, Perlin Noise Generator로 렌더링했다.
댓글 없음:
댓글 쓰기