Javascript의 스프레드 연산자(spread operator)를 적용해 보자

스프레드 연산자를 적용할 수 있는 클래스 AA가 있다고 할때 활용 예는 다음과 같다.

const aa = new AA();

aa.addItem(1);
aa.addItem(2);
aa.addItem(3);

console.log(...aa)

콘솔에 1 2 3이 찍힌다. 바로 … 연산자가 스프레드 연산자이다. 이처럼 스프레드 연산자를 적용할 수 있도록 하기 위해서 AA 클래스는 다음처럼 작성되어야 한다.

class AA {
  constructor() {
    this.items = []; // 이터러블한 내용을 저장할 배열
  }

  // 아이템을 추가하는 메소드
  addItem(item) {
    this.items.push(item);
  }

  // Symbol.iterator를 구현하여 이터러블하게 만듭니다.
  [Symbol.iterator]() {
    let index = 0;
    let data  = this.items;
    return {
      next: () => {
        if (index < data.length) {
          return { value: data[index++], done: false };
        } else {
          return { done: true };
        }
      }
    };
  }
}

참고로 스프레드 연산자는 매우 유용한 문법으로, 배열이나 이터러블 객체의 요소를 개별 요소로 확장하거나, 함수 호출 시 인수로 사용하거나, 객체 리터럴에서 속성을 복사할 때 사용된다. 예를들어 아래의 코드의 예시가 있다.

사례1

let arr1 = [1, 2, 3];
let arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]

사례2

function sum(x, y, z) {
  return x + y + z;
}

let numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

사례3

let obj1 = { foo: 'bar', x: 42 };
let obj2 = { ...obj1, y: 1337 }; // { foo: 'bar', x: 42, y: 1337 }

스프레드 연산자는 얕은 복사를 수행한다는 점을 유념하자. 위의 예제를 보면 알겠지만 스프레드 연산자는 코드를 더욱 간결하고 가독성을 높여주며 데이터 구조를 쉽게 조작할 수 있게 해준다. 하지만 얕은 복사라는 점을 다시 한번 더 유념하자.

GLSL의 mat4 타입에 대한 정리

mat4는 4×4 행렬입니다. 열을 우선(Column major) 순서로 해서 각 요소가 저장됩니다. 아래처럼요.

그래서 행렬의 첫번째 열과 세번째 열은 다음처럼 vec4 로 얻을 수 있습니다.

vec4 col1 = m[0];
vec4 col4 = m[3];

반면 첫번째 행과 세번째 행은 다음처럼 얻을 수 있습니다.

vec4 row1 = vec4(m[0][0], m[1][0], m[2][0], m[3][0]);
vec4 row4 = vec4(m[0][3], m[1][3], m[2][3], m[3][3]);

행렬의 특정 요소의 접근은, 예를들어 3번째 열의 4번째 행 요소는 다음과 같습니다.

float e = m[2][3];

행렬의 연산은 벡터의 수학적인 곱으로 많이 사용되는데 일반적으로는 다음과 같습니다.

vec4 v = ...;
mat4 m = ...;
vec4 t = m * v;

행렬과 벡터의 곱 순서를 다음처럼 바꿀 수 있는데, 그 결과는 위와 다릅니다.

vec4 t2 = v * m;

위처럼 벡터에 행렬을 곱하면 행렬을 전치시킨 것으로써, 위의 코드는 아래의 코드와 동일합니다.

vec4 t2 = traverse(m) * v;

행렬의 각 요소별 곱은 matrixCompMult이며 함수 이름에서 알 수 있듯이 거의 사용되지 않습니다.

마지막으로 역행렬을 구하는 함수는 inverse 입니다.

Mix Color 노드의 Factor 인자의 역할

Blender의 쉐이더 노드 중 Mix Color 노드가 있습니다.

이 노드의 Factor 인자의 내용을 한가지 수식으로 정의하면 다음과 같습니다.

출력 색상=(1−Fac)×Color1 + Fac×블렌드함수(Color1, Color2)

위의 수식에서 블렌드함수에는 Mix, Darken, Multuply, Color Burn, Add, Screen 등이 있습니다. 이 중 Mix의 경우 블렌드함수는 단순히 Color2입니다. 즉, 다음과 같습니다.

블렌드함수 mix = Color2

또 다른 블렌드함수로써 screen은 다음과 같습니다.

블렌드함수 screen =1−(1−Color1)×(1−Color2)

사실 Mix Color 노드에서 블렌드함수에 대한 수학적인 내용보다는 그 목적과 용도에 집중하는 것이 좋습니다. 즉, mix의 경우는 2개의 색상을 섞는다거나, screen의 경우는 2개의 색상을 섞어 더 밝은 색상을 만들어낸다는 등의 실제 결과가 어떻게 표현되는지를 이해하는 것에 더 집중하는 것이 좋습니다. 물론 수학적인 이해가 이런 실제 결과에 대한 표현을 이해하는데 큰 도움이 될 수도 있습니다.

voro-noise

노이즈는 렌덤을 기반으로 하며 FBM(Fractal Brownian Motion)을 위한 한 옥타브를 구성합니다. 몇가지 노이즈 중 하나로 보로노이(Voronoi)를 기반으로 하는 voro-noise가 있는데, Shader의 대가인 Inigo님이 2014년에 만든 알고리즘입니다. 내부 코드를 보면 퍼퍼먼스에 다소 부정적인 부분(일반적인 9개의 격자 그리드가 아닌 25개의 격자 그리드를 사용)이 보이지만 그 결과는 여타 다른 노이즈보다 훨씬 뛰어납니다.

아래의 코드는 voro-noise에 대한 구현 코드입니다.

vec3 hash3(vec2 p) {
    vec3 q = vec3(
        dot(p, vec2(127.1, 311.7)), 
        dot(p, vec2(269.5, 183.3)), 
        dot(p, vec2(419.2, 371.9))
    );
    return fract(sin(q) * 43758.5453);
}

float voronoise(in vec2 p, float u, float v) {
    float k = 1.0 + 63.0 * pow(1.0 - v, 6.0);

    vec2 i = floor(p);
    vec2 f = fract(p);

    vec2 a = vec2(0.0, 0.0);
    for(int y = -2; y <= 2; y++) {
        for(int x = -2; x <= 2; x++) {
            vec2 g = vec2(x, y);
            vec3 o = hash3(i + g) * vec3(u, u, 1.0);
            vec2 d = g - f + o.xy;
            float w = pow(1.0 - smoothstep(0.0, 1.414, length(d)), k);
            a += vec2(o.z * w, w);
        }
    }

    return a.x / a.y;
}

이 함수에 대한 가장 흔한 코드 예시는 다음과 같습니다.

void main() {
    vec2 st = gl_FragCoord.xy / uResolution.xy;
    st.x *= uResolution.x / uResolution.y;
    st *= 10.0;

    float f = voronoise(st, 1., 1.);
    gl_FragColor = vec4(f, f, f, 1.0);
}

위의 코드의 결과는 다음과 같구요.

voronoise 함수는 3개의 인자를 받는데, 첫번째 인자는 일반적으로 노이즈 함수가 받는 인자입니다. 2,3번째 인자인 u와 v는 voronoise에 특화된 인자인데 u는 노이즈 생성을 격자 그리드(Grid)를 얼마나 보로노이스럽게 표현할지에 대한 강도값으로 0에서 1까지의 값을 갖습니다. v는 격자 그리드 내부를 채우는 각 프레그먼트에 대한 보간 정도에 대한 강도 값인데 0부터 1의 값이며 0일때 전혀 보간이 이루어지지 않고 1일때 최대의 보간이 이뤄집니다. 아래의 결과는 u와 v를 모두 0으로 했을 때의 결과입니다.

아래는 u를 1로 v는 0으로 했을때의 결과입니다.

마지막으로 아래는 u를 0으로 v를 1로 했을때의 결과입니다.

아래는 노이즈 구현에 대한 유용한 사이트입니다.

https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83