목차
- 중간고사 풀이
- source 만들어주고 solver에 불러서 포인트 누적하기
- 초기 속도
- one direction Gravity & point Gravity
- 포인트를 선으로 묘사할 수 있는가 없는가
- 포인트의 소멸
- 바람
- VEL UPDATE
- source에 흑백으로 패턴 주기
- 첫 Birth 위치에 대해
- 일기
아... 중간고사 과제 수행한 것이 100프로 만족이 되질 못하니까(하다못해 80%라도) 강의를 보기가 매우 찝찝하다.
문제 풀다가 답지 보는 기분...?
이런 기분 다시 느끼지 않도록 최선을 다하자.(물론 중간고사 과제를 최선을 다하지 않은 것은 아니다...)
이번 강의는 중간고사 힌트영상 이후에 보게 될 내용이기 때문에
바로 체크리스트를 하나씩 지워가면서 빠르게 진행해볼 예정이다.
그리고 이제는 어느정도 기본기가 쌓였을 것이라 생각하기 때문에 불필요한 설명은 조금 줄여가면서 속도감 있게 진행해보려 한다.
모든 랭글러를 위하여!
중간고사 풀이 강의
목적은 간단하다.
우리가 구현할 수 있는 선에서 기본적인 particle system을 일반 solver를 가지고 만들어볼 예정이다.
<작업 순서>
- source 만들어주고 solver에 불러서 포인트 누적하기
- 초기 속도
- one direction Gravity & point Gravity
- 포인트를 선으로 묘사할 수 있는가 없는가
- 포인트의 소멸
- 바람
- VEL UPDATE
- source에 흑백으로 패턴 주기
- 첫 Birth 위치에 대해
- 정리 : 하나의 컨트롤러로
source 만들어주고 solver에 불러서 포인트 누적하기
'누적'이 중요한 요소이다. 만들어보면서 추가적으로 이야기하자.
초기속도
초기 속도를 만들어주고 움직임을 줄 것이다.
one direction Gravity & point Gravity
중력을 만들어 줄 것이다. 총 두가지 방식으로 만들어줄 것인데, 고난주간 6일차의 복습이 될 듯 하다.
포인트를 선으로 묘사할 수 있는가 없는가
여기의 핵심은 포인트 id를 어떻게 만들어주느냐 이다.
포인트의 소멸
계속 포인트가 누적된다면, 캐시파일이 너무 커져서 후디니가 감당할 수 없다. 그래서 필요없는 포인트들은 사라지도록 해줄 것이다. 이 때, 규칙을 만들어줄 것이다.(얼마나 오랫동안 지속할 것인지, 어떤 랜덤함에 의해서 사라지게 할 것인지)
바람
힘으로써 속도에 지속적인 영향을 주게 될까? 아니면 위치정보에 바람의 양을 더해주면 될까?
VEL UPDATE
최고 속도에 limit를 정해줄 것이다.
source에 흑백으로 패턴 주기
여기에서부터는 앞에서 만들었던 source에 대한 내용에 변화를 줄 것이다. 어느 부분에서 파티클이 생성되고 어느 부분에서 생성되지 않을지에 대한 내용이다.
첫 Birth 위치에 대해
이 부분은 어렵지만, dynamic solver에 가서도 있는 항목이기 때문에 한번쯤은 다뤄볼만한 내용이다.
정리 : 하나의 컨트롤러로
null 노드를 만들어주고, 노드 안에 우리가 조절해주고 싶은 내용들을 parameter로 만들어줄 것이다. 그리고 그렇게 만든 null 노드 하나로 모든 solver system이 작동될 수 있도록 해줄 것이다.
아무것도 없는 상태에서 시작해보자.
지오메트리를 생성하고, 소스로 만들고 싶은 오브젝트를 불러온다.(선생님은 박스를 불러오셨다.)
박스의 포인트만 보기 위해 add 노드를 달아주자.
누적되는 내용을 확인하기 위해 간단하게 transform 노드를 활용해서 회전을 가해주자.
add 노드를 transform 노드 아래에 달아주면, 아까처럼 포인트만 남은 것을 확인할 수 있는데, 시간의 흐름에 따라 box의 rotate가 변하게 되면서 각 프레임마다 포인트의 위치가 변하게 된다.
이렇게 위치가 변하는 모든 포인트들이 solver 안에서 누적이 되었으면 좋겠다.
solver를 꺼내주자.
위의 노드를 연결해주고, solver 내부의 prev_frame에 일단 OUTPUT 노드를 연결해주면, 회전하게 되는 포인트를 얻을 수 있을 것이라고 기대해보지만, 그런것이 아닌, 11frame의 순간의 움직임에 대한 위치에 멈춰있는 포인트들만 확인이 된다.
이유는 현재 시작 프레임이 11인 solver에 들어온 prev_frame은 스타트 프레임인 11프레임에 고정이 되어있는 상태이다. 이때 얻게 되는 11프레임의 위치가 output 노드로 가게 되고, 그 결과는 다시 12프레임의 prev_frame에 들어가서 output 노드에 전달되고, 그것은 또 다시 13프레임의 prev_frame 노드로 들어가게 되는 것이 반복된다. 그렇기 때문에 11프레임에서 얻은 결과에 solver가 머무르게 되는 것이다.
우리는 이것을 원하는 것이 아니라, 매순간 바뀌는 포지션의 정보들이 solver에 누적되기를 원한다.
연습 차원에서 연결을 끊어주고, add 노드를 solver의 네번째 인풋에 연결해주자.
그리고 solver 내부에서 input4 아래 null을 달아주고 이름을 source라고 지어준 뒤, prev_frame과 merge로 묶어준 후 이 내용을 output에 연결해준다.
어떤 결과가 나올 것인가? 상상할 수 있어야한다.
source로 11프레임의 포지션이 들어가고, prev_frame은 현재 없으므로, 이것은 곧장 아웃풋 노드에 전달된다. 이렇게 전달된 내용은 12프레임의 prev_frame으로 들어가게 되고, 12프레임에는 source에 위치가 이동된 포인트와 prev_frame에서 이전 위치의 포인트가 merge로 묶여서 output 노드로 전달되게 될 것이다.
결과는 움직이는 포인트의 궤적대로 포인트가 누적되고 있다.
11프레임에는 포인트의 갯수가 8개였고, 12프레임에는 16개가 되었다. 11프레임에 인풋4로 들어온 내용이 merge에 의해 solver 내부에 누적되었고, 12프레임에 새롭게 인풋4로 들어오는 내용(11프레임과 위치가 다른 포인트 8개)이 11프레임에서 누적된 8개의 포인트와 함께 merge에 의해 또 누적이 되었다.
그래서 시간이 지남에 따라 포인트의 숫자가 많이 늘어나게 되는 것을 확인할 수 있다.
소스로 들어가는 내용에 변화를 줘보자.
box와 solver만 남겨두고 중간 노드들은 삭제해주자.
그리고 box에 scatter를 달아줘서 box 표면에 포인트를 생성해준다.(50개)
그리고 solver의 인풋4로 연결을 해준 뒤 플레이를 해보면 마치 누적되지 않은 것 처럼 보이지만, 포인트 갯수를 확인해보면 매 프레임마다 50개의 포인트가 누적이 잘 되고 있는 것을 확인할 수 있다. 그런데, scatter로 인해 만들어지는 포인트들의 위치정보가 같기 때문에 포인트 넘버가 보이도록 플래그를 켜주면 같은 자리에 여러 숫자가 겹쳐져 있는 것을 확인할 수 있다. 매순간 scatter에 의해 서로 다른 위치에서 포인트가 생성되도록 해주고 싶다. 그래서 scatter의 global seed 값에 $F을 적어줄 것이다. 그것은 scatter의 display render flag를 켜서 플레이 해보면 매 프레임마다 다른 위치에서 포인트가 생성되는것을 확인할 수 있다.
위의 내용이 source가 될 것이고, source로 들어온 내용은 결과에 영향을 미치게 될 것이고, 이 결과는 다음 프레임의 prev_frame으로 들어가게 된다. 그리고 새로운 위치에서 생성된 source가 merge에 의해 합쳐지게 된다.
만약에 원한다면 이렇게도 해볼 수 있을 것이다.
solver에 들어가는 source의 내용을 바꿔줄 것인데, scatter의 force total count에 애니메이션을 적용해서 한순간 카운트 수치를 확 늘려서 포인트의 생성을 폭증시켰다가 카운트 수치를 0으로 만들어서 포인트가 더이상 생성되지 않도록 할 수 있다. 카운트 수치가 확 늘어나는 구간은 포인트가 그 구간에서 많은 숫자가 누적되고, 카운트 수치가 0이 된 이후에는 더이상 solver에 포인트가 누적되지 않는다.
이 이후에는 box가 아닌, 어떤 오브젝트에 적용해도 잘 생성되는 것을 확인할 수 있다.
어려운 내용은 아니지만, 여기에서 우리가 꼭 알아야 할 것은 지금까지 우리는 source가 되는 내용을 input1에 연결하고 Prev_frame으로 받아오고 있었다. input1로 들어오는 내용을 활용하겠다는 의미였는데, 이제는 다른 input을 이용하고 있다. 그 이유는 '변화하게 되는 정보를 매 프레임마다 받고 싶다' 라는 의도 때문이다. 그리고 solver 내부에서 prev_frame 과 source를 합쳐주는 merge가 굉장히 중요하다. |
![]() |
우리는 어떠한 결과를 얻어낼 것이고, 그 결과는 다음 프레임의 prev_frame으로 들어가는데 prev_frame - merge 사이에 달아주게 되는 내용이 있을 것이고, source - merge 사이에 필요한 내용이 있을 것이다. 또한 merge - output 사이에 필요에 의해 달아주게 되는 내용도 있을 것이다. |
두번째로 초기 속도를 만들어보자.
@vel을 만드는 것이 목적이고, 속도가 존재할 때, solver에 의해 위치가 변했으면 하는 것이다.
solver 안의 내용부터 만들어보자.
@vel을 받아왔다는 가정 하에, wrangle을 이용해서 우리의 위치정보가 @vel이 더해짐으로써 바뀌었으면 좋겠다는 것이다.
지금 상태에서는 플레이를 해도 아무 변화가 없다. @vel에 대한 정의가 아무것도 없기 때문이다.(가정은 가정일뿐)
@vel을 추가해줘보자.
sphere가 현재 primitive이므로 polygon으로 바꿔주었다. 그리고 아래에 normal 노드를 달아서 normal을 생성해주고, 이것을 가지고 @vel을 정의내린다.
너무 빠르기 때문에 @vel에 적정 수치를 곱해줘서 조절할 수 있도록 해보자.
source를 바꿔보자.
이정도면 초기 속도에 대한 이야기는 충분한듯 싶다.
추후에 바꿔줄 내용이 많겠지만, 큰 세팅부터 잡아나가는 것으로 목표를 잡고 진행해보도록 하겠다.
노드를 한번 정리하고 가자.
solver가 눈에 잘 띄도록 색을 바꿔주자.
그리고 input4로 연결된 위쪽의 노드를 전부 오른쪽으로 미뤄둔다. 그 이유는 오른쪽이 solver의 source가 될 것이기 때문이다.
이제 중력에 대한 이야기를 해보자.
null node를 하나 만들어주고, 이름은 setting으로 바꿔준 뒤 solver의 input3에 연결해준다.
정보를 운반하기 위해 add 노드를 활용해서 포인트 하나를 생성해준다. 그 아래에는 정보를 생성하기 위한 attribute wrangle을 만들어준다. 그리고 null node를 달아서 구분해주자. 이것은 아래 방향으로 향하는 one direction gravity 중력이다.
그리고 그 옆에는 또 다른 포인트를 하나 생성해주는데, 이 포인트는 point gravity의 target이 될 것이다.
쉬운 것 부터 작업해보자.
일단 작동 확인을 위해 setting에 연결해주고, solver 내부를 작업해주자.
우리가 가진 포인트들이 solver에 의해서 움직이는 원인이 무엇인가? 우리가 가진 모든 포인트의 포지션의 정보(@P)에 속도(@vel)만큼 더해지기 때문이다.
그런데 중력이라는 것은 시간에 따라 속도(@vel)가 변하는 '힘'이 존재한다는 것이다.
wrangle(gravity) 노드에서 우리는 @vel을 업데이트 해주자. @vel은 gravity가 더해짐으로써 업데이트되는 것이다.
중력의 크기는 solver 바깥의 wrangle(one direction gravity)에서 channel vector 수치를 조절하는 것으로 바꿔줄 수 있다.
이제 point gravity를 만들어줄 차례이다.
일단 target의 위치를 만들어주고, 새로운 어트리뷰트를 생성해서 이 정보를 할당하자.
이제 이 정보가 solver에 전달될 수 있도록 null(setting)에 연결해줄 차례이다. 그런데, 여기에서 한가지 안해본 방식을 적용해볼 것이다. 바로 gravity와 point gravity를 merge로 묶어주고 solver에 전달하는 것이다.
이 경우에, solver 내부에서 각각의 gravity를 불러오려면 어떻게 해야할까?
merge의 정보를 확인해보면, 포인트 두개를 가지고 있고, 각각 0번 포인트는 one direction gravity의 크기에 대한 정보, 그리고 1번 포인트는 point gravity의 target에 대한 정보를 가지고 있는것을 확인할 수 있다.
solver 내부에서 target의 정보를 불러오기 위해서는 point function을 활용하면 될 것이다.
point(1, "target", 1);
매번 point function의 세번째 인자로 0번 포인트를 불러오기 위해 0을 써줬지만, target의 정보는 1번 포인트가 가지고 있으므로, 1을 적어주면 된다.(정보를 가지고 있는 포인트 번호를 기입해주면 된다.)
지난시간에 배운 내용의 복습이다.
target이 있고, 결과로서 얻은 prev_frame에 @P를 활용할 것이다.
target을 향하는 vector를 구해줘야 한다. / target - @P
그리고 이렇게 구해지는 vector는 위치의 다름으로 인해 각각 사이즈가 다르기 때문에 normalize를 해준다. 크기가 1이고 방향이 target을 향하는(각기 다른) vector들이 생성된다. / normalize(target - @P);
이렇게 얻은 vector들이 point gravity가 될 것이고, 이것을 활용해서 @vel을 업데이트 해준다.
vector point_gravity = normalize(target - @P);
@vel += point_gravity;
point gravity가 현재 너무 강하게 작용하고 있다.
solver 밖에서 point gravity의 세기를 조절할 수 있도록 어트리뷰트를 만들어주자.
이 정보는 target과 마찬가지로 1번 포인트가 운반하는 정보이기 때문에, solver 내부에서 point function을 활용해서 불러올 수 있다.
target이 움직이도록 애니메이션을 줘보자.
이제 solver 내부에서 무언가를 좀 바꿔보자.
bypass를 켰다 껐다가 하는 것 만으로도 각각 gravity / point gravity가 작동하도록 하고 싶다.
만일 gravity에 bypass가 켜져있다면, 중력에 영향을 받지 않고 point gravity에 영향을 받게 될 것이고, gravity / point gravity 둘 다 bypass가 꺼져있다면, 중력에 영향을 받은 @vel은 point gravity에 한번 더 영향을 받게 될 것이다.
이 두 wrangle에서는 @vel만 갱신되고 있다.
결론적으로 위치가 정해지는 것은 그 아래의 wrangle(make it move)이다.
이제 우리의 결과물을 포인트로 보는 것이 아닌, 라인으로 볼 수 있도록 해보자.
지금까지는 source로 들어오고 있는 곳에서 @id를 만들어주고 solver에서 얻은 결과에 대해 trail을 달아서 경로를 추측하고 add 노드를 활용해서 같은 id끼리 선으로 연결되도록 해주었다.
이 방식을 적용하면 현재 우리가 기대했던 결과는 나오지 않고 있다.
기대했던 결과가 나오지 않는 이유는...
여기는 내가 확인하게 된 이유
그 이유는 매 프레임마다 다른 위치에서 scatter에 의해 포인트가 생성이 되고, 그렇게 생성된 포인트들은 이전 프레임의 포인트들과 동일하게 0번 부터 id를 부여받게 된다.
0번 id를 가진 것에 주목해보자. 11프레임의 0번 id를 가진 포인트가 12프레임이 되었을 때 trail에 의해 궤적을 만들어내는데, scatter로 인해 새로운 위치에서 생성된 point 또한 0번부터 id를 부여받게 되면서 add를 적용했을 때 새롭게 생성된 포인트도 같이 연결이 되는 것이다.
여기서부터는 선생님 설명해주시는 이유
거꾸로 생각해보자. 왜 예전에는 이와같은 방식으로 세팅을 진행했을 때, 선으로 연결되어서 볼 수 있었던 것인가?
그 이유는 solver에서 prev_frame의 정보만을 활용하는 것이 아니라, 새로운 source들의 정보를 받아서 합쳐주고 있기 때문이다.
예전에는 source를 첫번째 인풋으로 넣어주면서 solver의 prev_frame만을 활용했기 때문에 만일 solver의 첫번째 인풋으로 들어가는 기존의 정보가 100개의 포인트였다면, 0 ~ 99번의 id가 존재하게 된다. 그리고 추가적인 다른 점들은 없었다.
그런데 지금은, 점들이 계속해서 생성되어 추가되고 있다. 각각의 프레임마다 생성되는 새로운 점들이 만약 100개씩 생성이 된다면, 28프레임에서도 id가 0~99인 포인트가 존재할 것이고, 29프레임에서도 또한 id가 0~99인 포인트가 존재하게 될 것이다.
지금 우리가 신경써줘야 할 것은, 생성되는 id가 겹치는 일이 없이 고유한 id로 부여받을 수 있도록 하는 것이다.
- 내생각 - 프레임마다 생성되는 것이니까, id에 frame 번호를 같이 붙여주거나 하면 고유한 id로 부여받을 수 있을 것 같은데...
- 여기는 선생님 강의 -
힌트를 주자면, integer attribute id는 사용하지 않을 예정이고, string attribute id를 만들어줄 예정이다.
두가지 정보를 같이 넣어줄 것이다. 몇프레임에 생성된 몇번째 포인트. 이런식으로 이름을 지어줄 예정이다.
이 방식은 scatter의 total count와 관계없이 모두 고유한 id를 가질 수 있는 방식이다.
integer 정보를 string으로 변환해주는 itoa를 활용했다.
그런데 이 방식으로 진행하다보면, 약간의 문제가 발생하는 순간이 온다.
id가 298일 때, 2프레임에 생성된 98번 포인트 넘버를 가진 포인트인지, 29프레임에 생성된 8번 포인트 넘버를 가진 포인트인지 구분이 되지 않는다.
그래서 사이에 다른 문자를 추가로 기입해주기로 한다.
id가 좀 더 명확해졌다.
우리가 원했던 결과 또한 잘 나오고 있다.
포인트가 소멸하도록 작업해보자.
포인트의 소멸은 너무 부정적이므로, 포인트의 삶에 대해, 그 운명에 대해 이야기해볼까 한다.(더 무거운데..........)
접근방법은, 포인트의 나이 또는 삶의 길이를 source가 제공되는 단계에서부터 미리 정해주도록 한다.
우리가 이용하고자 하는 변수 혹은 어트리뷰트의 이름을 적어보자.
life
lifelimit
liferandom
(각각 포인트에 life가 부여되고, 한계값을 정하고, 좀 더 풍성한 결과를 얻기 위해 life에 랜덤성을 부여하게 될듯 하다.)
life는 solver에서 작동한다.
처음 생성되는 life는 0이 되고, 시간이 흐름에 따라 1씩 증가하게 될 것이다.
i@life가 solver 밖 source의 어딘가에서 만들어지고, solver 안에서는 @life++가 적용되게 될 것이다.
@life를 통해 각 포인트가 살아온 프레임길이를 알 수 있게 된다.
lifelimit 또한 source단계에서 만들어져서 제공될 것이다. 만약 이 값이 7인 경우라면, life가 7보다 크게 될 경우에 그 포인트가 사라져줬으면 하는 것이 우리의 의도이다.
이것을 식으로 표현한다면, 다음과 같을 것이다.
if(@life > @lifelimit){
removepoint( ... );
}
이 내용은 어디에 쓰이게 될까? solver 밖에서 쓰이게 될까? 아니면 solver 안에서 쓰이게 될까?
solver 밖에서는 life가 0이라고 선언만 해주고, 그 값의 증가는 solver 내부에서 일어나기 때문에, 아마도 solver 안에서 lifelimit에 대한 규칙을 적용하는 내용이 들어가게 될 것이다.
liferandom에 대해 이야기해보자.
만약 lifelimit의 값이 17이라면, 모든 포인트들이 17프레임까지만 살아있다가 제거될 것이다. 무언가 뚝 끊기는 느낌이 들 수 있다. 그래서 liferandom이라는 값을 이용해서 lifelimit의 값을 좀 더 풍성하게 만들어주고 싶다.
만약 liferandom의 값이 -3 ~ 3 사이의 값을 가진다면, 이 정보를 활용해서 각각의 포인트가 가지고 있는 lifelimit의 값이 갱신되어 14 ~ 20 사이의 어떤 수가 되었으면 하는 것이다.
대략적인 식을 가정해본다면,
liferandom에 rand()를 활용해서 0 ~ 1 사이의 값을 받고, 이 값을 fit을 활용해서 증폭시킨 결과를 저장한다. lifelimit이 상수형태로 정해지게 되고, 이 값은 liferandom을 활용해서 갱신되게 된다.
lifelimit = constant;
liferandom = fit(rand(...), 0, 1, 증폭최솟값, 증폭최댓값);
lifelimit = lifelimit + liferandom;
신경써줘야하는 부분이 있다면, 자료형에 신경을 써줘야한다. lifelimit은 integer이고, liferandom은 rand 함수를 사용하는 float에서 출발하게 되는 자료이다.
이제 작업을 해보자.
일단 source에서 life를 정의해주자.
생성되는 모든 포인트들은 life가 0으로 solver에 들어간다는 것을 의미한다.
solver로 들어가보자. source로 받고 있는 내용에 대해 @life가 이미 존재할 것이다. 왜냐하면, @life가 input4로 들어오고 있는 내용이고, input4를 통해 받은 내용이기 대문에 @life는 0인 상태로 어떤 작업이 진행되게 된다. 여기에서 얻은 결과는 다음 프레임의 prev_frame이 될 것인데, 이때까지만 해도 우리가 가지고 있는 @life는 0일 것이다. 이 타이밍에 @life의 값을 1 증가시켜주려고 한다. 그렇기 때문에 wrangle 노드를 prev_frame 노드 바로 아래에 달아주게 될 것이다.
이전 결과에서 새로운 프레임으로 넘어올 때, 가장 먼저 해주게 될 일이 life의 조절이 되는 것이다.
여기에 life에 대한 제한을 걸어보자.
@life가 20보다 커진다면, 포인트를 제거해주는 조건을 적용했다.
@life가 20보다 커지면 포인트가 사라지고 있기 때문에 뚝 끊기는 느낌이 든다.
여기의 20이라는 숫자, 이것이 lifelimit이다.
현재는 @lifelimit이 존재하지 않는다. source 단계에서 정의해주자.
선작업 했는데, 선생님 이 방법 안쓰신단다...
현재 integer 형태로 @lifelimit을 만드는 것은 매우 불안정하다. 그 이유는 rand 함수를 활용한 값을 더해주게 될 터인데, int 형에 float형을 더해주거나 곱해주는 등의 연산은 순서에 따라 처음 오게 되는 자료형을 따라가게 되겠지만, 불안하다. 그래서 일단 지금은 선언하지 않고, 변수형태의 float limit, float rand를 만들어서 연산을 진행해주고, 이 값에 floor function을 활용해서 소숫점을 날려버려서 integer 형태로 만들어줄 것이다. 이렇게 해준 뒤에 i@lifelimit를 선언해서 값을 할당할 것이다.
앞에서 만들었던 고유한 id를 활용해서 life limit의 rand에 활용해보자.
앞서 사용했던 itoa는 integer에서 string 정보로 바꿔주는 역할을 했다.
그렇다면, 만약 string 문자열이 숫자만 존재한다면, string 정보를 integer 로 변환해주는 것도 존재하지 않을까?
이 때 사용하는 것은 atoi 이다.
그런데 위에서도 이야기했었지만, frame과 ptnum 만의 조합은 값이 중복되게 될 수도 있기 때문에, 중간에 약간의 문자열을 추가하기로 하자.
중간에 넣어준 0000은 공백으로서 만들어준 숫자이다.
이번엔 바람을 적용해보자.
바람은 포인트에 더해지는 추가적인 위치정보로 생각하면 될 것이다.
물론 바람을 힘이라고 할 수도 있겠으나, 바람이 부는 느낌은 위치정보가 바람이 적용되어서 이동을 하게 되는 것이다.
컨셉을 이야기해보자.
wind라는 정보를 만들어줄 것이고, 이 정보의 자료형은 vector일 것이다.
변수로 선언될 수도 있고(vector wind) 어트리뷰트로 선언될 수도 있다(v@wind).
이렇게 만들어진 바람은 매 프레임마다 @P에 어떤식으로든 더해지면 될 것이다.
@P += wind;
solver 내부를 생각해본다면,
이곳 wrangle(make it move)에서 @P += @vel; 뿐만 아니라, 그 아래에 @P += @wind;를 적어줘서, 위치정보가 속도(@vel)에 의해서 변하지만, 바람(@wind)의 영향도 받는다. 라고 정의해줄 수도 있을 것이다.
그렇다면, 저 wrangle 위에 다른 wrangle을 추가해주는 것 또한 가능할 것이다.
이번에는 solver의 두번째 input에 정보를 입력해보려고 한다.
이번에 만드는 포인트 또한 정보를 전달해주는 역할을 하는 포인트이다.
solver 내부에는 wrangle(wind)를 만들어주고, input2를 끌어와서 wrangle의 두번째 input에 연결해주었다.
선생님은 여기에 아이디어로 각각의 포인트들이 무게가 존재해서, 어떤 포인트는 바람의 영향을 많이 받고, 어떤 포인트들은 바람의 영향을 적게 받는다면, 좀 더 바람의 영향을 받는것처럼 느껴지지 않을까? 하셨다고 한다.
그래서, source 입력 단계에서 각 점에 대해 f@windamount 라는 어트리뷰트를 만들어주고, 0~1사이의 랜덤한 가중치를 부여해주기로 했다.
그리고 solver 내부의 wrangle(wind)에서 @wind에 대해 @windamount 값을 곱해주는 업데이트를 해주기로 했다.
선생님은 @windamount가 너무 균일하게 분포되는것을 chramp를 활용해서 방지해주셨다.
어떤 포인트는 바람의 영향을 거의 안받고 있고, 어떤 포인트는 바람의 영향을 받고 있다.
근데 왜 바람같지 않을까...?
그 이유는 노이즈에 있다.
바람이 이렇게 이쁘게 부는게 아니다. 약간 소용돌이 치는 것도 있고, 방향도 자꾸 바뀌고 그러는게 바람에 대한 묘사에 더 적합할듯 싶다.
노이즈란... 위치 정보에 대응되는 패턴의 값을 돌려주는 내용
소스의 다발 쪽에 노이즈 정보를 추가해준다면, 항상 소스 근처에서만 노이즈를 받게 될 것이 때문에 이 부분에 추가하는 것은 적합하지 않다.
gravity를 적용해주는 다발에 노이즈를 추가해준다면, 하나의 점에 대한 위치정보에 대응되는 노이즈 패턴의 값을 돌려받기 때문에 이곳도 적합하지는 않다.
wind를 적용해주는 곳 또한 gravity와 같은 이유로 적합하지 않다.
solver 내부로 들어가보자.
source가 들어오는 곳에 노이즈를 추가해준다면, 소스 다발에 추가해주는 것과 비슷한 이유로 적합성이 떨어진다.
source와 prev_frame이 합쳐지는 merge의 아랫단에 노이즈를 추가하는 것은 과거의 output에서 만들어준 정보와 새롭게 추가되는 점의 정보를 합쳐서 활용할 수 있기 때문에 이 부분은 노이즈를 적용할 가능성이 있다.
prev_frame - merge 까지 연결되는 노드들의 사이 또한 가능성이 노이즈를 적용할 수 있는 가능성이 있다. 왜냐하면, 이미 이전 프레임에서 얻은 결과가 prev_frame으로 들어오는 것인데, 이것은 source에 있는 위치가 아니라, 바람이나 중력의 영향을 받고 wrangle(make it move)에 의해 움직여진, source와는 다른 위치의 포인트 정보들을 가지고 있을 것이기 때문이다.
Prev_frame - merge 사이의 구간은 life 노드가 중간에 있긴 하지만, 그것이 특별하게 위치정보에 영향을 미치지는 않을 것이기 때문에 비슷한 이야기가 될 듯 싶고, 이 구간과 merge - gravity 구간은 조금 뉘앙스 차이가 있다.
만약 merge - gravity 구간 사이에 노이즈가 적용이 된다면, source로 들어온 포인트들에도 노이즈가 바로 적용이 될 것이다. 하지만 prev_frame - merge 구간에 노이즈가 적용이 된다면, source로 들어온 포인트들은 일단 노이즈에 영향을 받지 않게 된다.
선생님은 두번째 상황, prev_frame - merge 사이에 노이즈가 적용되는 것을 택하셨다. 한 프레임이라도 원래 진행하려던 방향으로 진행을 하고, 그 이후에 노이즈가 먹는 상황인 것이다.
attribute vop을 이용해보자.
이곳에서 우리가 만들어줄 것은 바람에 대한 노이즈정보를 만들어주고 싶은 것이다.
그렇기 때문에 우리는 bind export로 어트리뷰트를 만들어줄 것이다.
Type : wrangle에서 type casting 하는것과 동일하다고 보면 된다. vector로 적용할 것이다.
우리는 이번에 Anti-Aliased Flow Noise(이하 aaflow noise)를 적용할 것이다.
signature는 4D input, 3D noise를 적용할 것인데, 4d input을 사용한다는 말의 의미는 일반적인 위치정보만을 활용한다는 것이 아니라, 다른 정보를 추가적으로 바인딩해줘서 시간에 따라 변하는 노이즈패턴을 받고 싶다는 것이다.
지금 이 vop의 결과로 얻을 수 있는 노이즈 정보의 숫자는 포인트의 갯수만큼이다.
이제 만들어진 노이즈 정보를 활용해보자.
일단 만들어진 노이즈를 더해보자.
noise의 frequency를 조절해보자.
frequency를 키워주면, 노이즈 패턴이 바뀌는 속도가 더 빨라지게 되어서 스월 사이즈가 더 작아지게 된다.
스월(swirl) - 거미줄 또는 달팽이 모양의 소용돌이
선생님은 wind에 대한 식을 좀 더 다듬어주셨다.
현재는 windamount가 windnoise에도 적용되어서 windamount 값이 작은 포인트들은 windnoise도 적게 받고 있는데, 이런것이 아니고, noiseamount도 따로 만들어서 windamount를 받는것과 관계없이 noiseamount를 받도록 수정하겠다 하셨다.
windamount를 활용해서 noiseamount를 만들어줄 것인데, 현재의 windamount를 만들기 위해서 0에서 1까지 균일하게 증가하는 어떤 값을 chramp의 모양을 바꿔줌으로써 어떤 결과를 만들어냈는데, 이것은 결국 또 0에서 1 사이의 값이었다. 이 값을 noise amount로 바꿔줄 것인데, fit을 활용할 것이다.
fit(@windamount, 0, 1, 노이즈 최솟값, 1);
노이즈 최솟값이 존재하게 된다면, @windamount가 0일때에도 노이즈는 적용되도록 만들어줄 수 있다.
노이즈의 역전 또한 표현이 가능하다.
이말인즉슨, 바람의 영향을 적게 받는곳은 노이즈의 영향이 커지고, 바람의 영향을 많이 받는 곳에서는 노이즈의 영향아 적어지는 것을 의미한다.
Vel Update를 적용해보자.
우리가 만들었던 point gravity를 활용하려고 한다.
@vel이 위치정보에 적용되기 전에 wrangle을 만들어주고, @vel을 업데이트해주자.
이 내용은 어떠한 @vel에 대해 일정한 비율로(여기에서는 0.9의 비율로) @vel이 업데이트 되는 것을 의미한다.
이번에는 우리가 가진 @vel에 대해, @vel이 가지는 크기에 대해 제한을 주려고 한다.
@vel에 한계값을 두고, 그 값보다 큰 @vel은 @vel의 크기를 한계값으로 만들어서 @vel이 한계값을 넘어가지 않도록 할 예정이다. @vel이 한계값보다 작다면, 아무런 변경 없이 그대로 @vel이 사용되도록 할 것이다.
어떤 식으로 식을 적으면 될지 정리해보자.
v@vel; // @vel을 사용해줄 것이기 때문에 선언을 해주자. type casting 꼭 해주자.
float len = length(@vel); // @vel의 크기를 측정한다. length function을 사용하고, 이 값을 len이라는 변수에 저장한다.
float vellimit = chf("vellimit"); // 사용자에게 @vel의 한계값을 입력받는 변수 vellimit를 만들어준다.
if( len > vellimit ){
@vel = normalize(@vel) * vellimit;
}
/* 우린 len을 가지고 판별을 할 수 있다. '사용자가 입력한 @vel의 한계값 vellimit보다 @vel의 길이(크기) len이 크다면' 이라는 조건으로 안의 내용을 수행한다. @vel과 방향은 같고 크기가 1인 vector를 만들어주기 위해 @vel에 normalize를 적용하고, 그 결과에 vellimit를 곱해준 다음 그 값으로 @vel을 갱신한다. 이 말인즉슨, 크기가 vellimit이고, 방향은 @vel과 같아진다는 것을 의미한다. */
정리해보자면, vel limit wrangle은 우리가 위에서 만들어준 @vel에 한계값을 정해줬고, vel update wrangle은 전반적인 @vel의 힘을 조절해주는 것이다.
이제 전반적인 세팅은 다 만들었다.
우리가 만들어준 시스템의 특징은 solver 내부에서 불필요하다고 생각되는, 혹은 적용하고 싶지 않은 것들(ex. gravity or wind)에 대해 bypass를 걸어주는 것으로 배제해줄 수 있다는 것이다.
- 이 느낌, 꼭 알고 있어야 한다.
dynamic solver에 가서도 이와같이 노드를 달아주느냐 마느냐에 따라 적용이 되느냐 마느냐가 정해진다.
dynamic solver에 가게 되면, 와이어를 쌓는 세팅의 순서가 중요해진다.
noise를 적용했던 것을 기억해보자. merge 아랫단에 noise를 적용해서 처음 source부터 노이즈가 적용되도록 할 것인지, prev_frame 아랫단에 noise를 적용해서 처음 들어오는 source에는 노이즈가 적용되지 않도록 해줄 것인지 생각했었다. 이 뉘앙스 차이를 알 필요가 있다.
dynamic solver에 가게 되면 동일한 내용들을 와이어로 엮어주게 될 터인데, 순서에 따라 적용되는 순서가 바뀌기 때문에 약간의 뉘앙스 차이가 발생할 수 있다.
지금부터는 solver 밖에서의 이야기가 될 것이다.
우리는 흑백으로 영역을 구분해준 다음에 색이 칠해진 부분에 대해서만 포인트(파티클)가 생성이 되길 원한다.
grid를 생성해주고, 색을 만들어준 뒤에 어느 부분을 사용하고 어느 부분을 사용하지 않을지 표현해보도록 하자.
일단 grid의 색의 검정으로 바꿔줬고,
attribute noise를 사용해서 컬러에 노이즈를 줬다. 이 때, 처음 적용을 하면 알록달록한 색이 표현되는데, vector(3D)로 적용이 되어서 그렇다. 이 부분을 float(1D)로 바꿔주면 흑백으로 표현된 컬러 노이즈가 출력된다.
group expression을 사용해서 컬러가 임의의 값 이상인 부분들을 그룹으로 지정해준다.
blast는 위에서 지정한 그룹을 뜯어내는 역할을 하고, add는 불필요하게 생성되고 사용되지 않는 포인트들을 삭제해주는 역할을 한다.
source를 하나만 더 만들어보자.
고난주간 4일차 과제를 기억하는가, grid에 sphere의 궤적을 받아오는 작업을 진행했었는데, 그 궤적을 source로 만들어주는 작업을 해보자.
이렇게 만들어준 source를 바로 우리가 만들어준 solver에 연결해주기엔, solver의 데이터를 또 다른 solver에 연결하는 것이기에 불안정하다. 그래서 file cache를 사용해서 source 데이터를 받아주고, 그 데이터를 source로 활용해보자.
source를 꾸민다는 것은 매우 간단하다.
어떠한 면에서 포인트가 나올지만 만들어줄 수 있으면 되는 것이다.
처음 birth 위치에 대해 이야기해보자.
우리가 만들어준 결과를 옆에서 확인해보면 층이 나뉘어져 있는 것을 확인할 수 있다.
그리고 또 한가지, y = 0인 grid의 표면에서 포인트가 생성되는 느낌이 아닌, 그 위에서 포인트가 생성되는 느낌이다.
우리가 원하는 것은 grid 표면에 딱 붙어서 포인트가 생성되기 시작하거나, 혹은 위 사진의 층 사이에서 포인트가 생성이 되는 것이다.
분명 이렇게 층이 지는 결과는 좋은 결과는 아닌듯 하다.
지금까지 작업했던 solver를 좀 뜯어야할 필요가 있다.
solver 내부에 merge 되어있던 내용을 분리시키자.
나눠주긴 했지만, 작업의 순서는 같다.
현재 노드가 다르게 보이는 이유는... 공부 정리한 글이 한번 날아가는 바람에 작업내용은 이미 마지막 작업까지 다 된 결과를 캡쳐하는 것이다...
처음 source가 들어가서 위치변화가 일어날 때, 층이 지면서 뭉쳐지게 되는 것을 풀어주는 작업을 진행할 것이다.
이를 위해 solver 밖에서 @mix라는 어트리뷰트를 생성해주고, 이 어트리뷰트를 활용해서 포인트들의 위치정보를 갱신해주는 외력들(@wind, @vel)에 곱해줌으로써 위치정보가 고르게 풀어지도록 할 것이다.
그리고 이것만으로도 어느정도 블랜딩의 효과를 얻을 수 있지만, point jitter 노드를 활용해서 포인트들을 조금 더 흩어줄 예정이다.
@mix는 우리가 위에서 만들었던 @windamount를 만들어줬던 아이디어를 그대로 활용할 것이다.
- @windamount - 바람을 받는 가중치를 0~1 사이의 값으로 각 포인트들에 부여해주고, 그 값에 따라 바람의 영향을 받는 것을 포인트들마다 각기 다르게 만들어줬던 어트리뷰트
solver 내부의 make it move wrangle을 수정해주자.
지금까지 사용하지 않았던 것을 한번 사용해보려고 한다.
현재 우리는 색상에 대한 정보만 가지고 있는데, alpha에 대한 정보를 추가해주려고 한다.
투명도에 대한 이야기인데, 이 부분은 dynamic solver에 들어가서 좀 더 디테일하게 설명하기로 한다.
하나의 노드에 parameter를 만들어서 컨트롤러를 만드는 과정은 불필요한 노가다 과정이라 생각되어 일단 넘어가려고 한다.
그리고 오브젝트가 넘어가는 효과는 switch 노드를 활용하면 된다.
switch 노드의 select input 에 애니메이션을 적용해서 값을 바꿔주면 된다.
박스에 대해서는 면이 부족하기 때문에, 우리가 작업한 내용이 제대로 출력이 되지 않을 수 있다. 박스에 remesh를 적용해주자.
체크리스트를 보는 순간부터 굉장히 맘이 답답하다.
대학교 중간고사였고, 채점 체크리스트였다면, 완전 채점조차 필요없을정도로 체크리스트에서 고민한 것은 반타작 조금 넘는건가...
solver... 제대로 이해한건 맞나? 제대로 공부하고 있는거 맞나? 혹시 공부한다 라고 착각하고 그냥 타이핑만 하고 있는거 아닌가?
다만, 지금까지 공부하면서 절실히 느낀 것이 있다면, 아이디어를 구상하고, 말로 표현해는 것이 굉장히 중요하다는 것이다. 선생님 강의를 보게 되면, 선생님이 막 엄청나게 강력한, 남들이 모르는 어떤 노드를 짜잔~ 하고 가져와서 작업을 하시는 것이 아니다. 아이디어를 구상하고, 체계적으로 말로 표현해낸 다음, 이것들을 굉장히 기본적인 노드를 가지고 구현해내신다.
중간고사도 그러했다. 내가 말로 표현할 수 있었던 부분들은 계속 고민하고 고민하면서 풀어낼 수 있었지만, 선생님의 체크리스트 중에서 내가 생각조차 못했던 단계의 아이디어는 구현할 수 없었다.
이 부분에 대해서 작업을 계속 하면서 끊임없이 고민하고 머리를 굴리면서 경험을 쌓아야겠다.
하... 티스토리 글 작성 도중 한번 날아가니까 공부일기 쓰는 것이 매우 힘든 일이 되어버렸다...
다음부터는 임시저장도, 중간 비공개로라도 저장을 꼭꼭 해줘야겠다...
'Houdini > Houdini1_Starter' 카테고리의 다른 글
STARTER09_고난주간 6일차_Point Gravity (2차 수정) (0) | 2023.01.28 |
---|---|
STARTER09_고난주간 5일차_Gravity & Substeps (0) | 2023.01.26 |
STARTER09_고난주간 4일차 (0) | 2023.01.25 |
STARTER09_고난주간 3일차_Bounce with Solver (0) | 2023.01.21 |
STARTER09_고난주간 2일차 (1) | 2023.01.19 |