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

React, 모바일 여부 확인을 위한 Hook

현재 실행환경이 모바일인지를 확인하기 위한 Hook입니다.

import { useState, useEffect } from 'react'

const useIsMobile = () => {
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    const checkIsMobile = () => {
      const userAgent = typeof window.navigator === 'undefined' ? '' : navigator.userAgent;
      const mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
      setIsMobile(mobile || ('ontouchstart' in window && window.innerWidth <= 1024));
    };

    checkIsMobile();
    window.addEventListener('resize', checkIsMobile);

    return () => window.removeEventListener('resize', checkIsMobile);
  }, []);

  return isMobile;
};

export default useIsMobile;

node로 실행한 서버 스크립트를 데몬(damon)으로 실행하기

nohup과 &의 조합으로 실행해도 해당 터미널을 종료하면 실행한 서버 스크립트가 같이 종료되 버리는 문제가 있습니다. 이 경우 forever란 패지키를 npm으로 설치해서 이용하면 됩니다.

npm i -g forever

그리고 index.js를 실행하고 싶다면 다음처럼 입력합니다.

forever start index.js

잘 실행되고 있는지 확인하기 위해서는 다음 명령을 입력합니다.

forever list

forever로 실행한 것을 종료하기 위해서는 다음처럼 명령을 입력합니다.

forever stop index.js

forever로 실행한 모든 프로그램을 종료하기 위해서는 다음과 같습니다.

forever stopall

Vite로 개발된 웹앱 배포 시 주의점

Vite로 구성된 웹 프로젝트를 배포할 때 npm run build로 배포본을 생성하는데, 이때 경로(path)가 틀려 필요한 리소스를 불러오지 못하는 에러가 발생합니다. vite.config.js 파일에 경로를 명시적으로 지정해주면 됩니다.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  base: "./",
})

위의 코드 중 7번째 줄을 추가했는데요. 이처럼 base를 지정하지 않으면 상대 경로가 / 로 잡힙니다.

이 부분 이외에도 문자열로 구성된 데이터 파일에 대한 URL에 대해서 주의를 해야 하는데요. 예를들어 glb 모델 파일을 로드할때 /가 아닌 ./로 지정해야 배포시에 데이터를 로드할 수 있습니다.

// 개발에서는 문제가 없지만 배포에서는 문제가 발생함
useGLTF.preload('/city_2.glb')

// 개발에서도, 배포에서도 문제가 없음
useGLTF.preload('./city_2.glb')