레슨 5 / 8·20분
노이즈와 패턴 생성
절차적 노이즈와 고급 패턴
노이즈 함수는 자연스러운 패턴(구름, 불꽃, 지형 등)을 생성하는 핵심 기법입니다. GLSL에는 내장 노이즈 함수가 없으므로 직접 구현해야 합니다. 해시 기반 랜덤, Value Noise, Perlin Noise, Simplex Noise 등을 구현하고, FBM(Fractal Brownian Motion)으로 여러 옥타브의 노이즈를 합성하여 복잡한 패턴을 만듭니다.
glsl
precision mediump float;
uniform float u_time;
uniform vec2 u_resolution;
// ── 해시 기반 의사 랜덤 함수 ──
// 입력 좌표에 대해 0~1 범위의 "랜덤"값 반환
float random(vec2 st) {
return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453);
}
// ── Value Noise ──
// 격자 꼭짓점의 랜덤값을 보간하여 부드러운 노이즈 생성
float valueNoise(vec2 st) {
vec2 i = floor(st); // 격자 좌표 (정수)
vec2 f = fract(st); // 셀 내 위치 (소수)
// 격자 4개 꼭짓점의 랜덤값
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
// 부드러운 보간 (hermite 곡선)
vec2 u = f * f * (3.0 - 2.0 * f);
// 이중 선형 보간
return mix(
mix(a, b, u.x),
mix(c, d, u.x),
u.y
);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
st *= 8.0; // 노이즈 스케일
float n = valueNoise(st + u_time * 0.5);
gl_FragColor = vec4(vec3(n), 1.0);
}FBM (Fractal Brownian Motion)
glsl
precision mediump float;
uniform float u_time;
uniform vec2 u_resolution;
float random(vec2 st) {
return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453);
}
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
// ── FBM: 여러 옥타브의 노이즈를 합산 ──
// 옥타브가 늘어날수록 세밀한 디테일이 추가됨
float fbm(vec2 st) {
float value = 0.0;
float amplitude = 0.5; // 진폭 (매 옥타브마다 감소)
float frequency = 1.0; // 주파수 (매 옥타브마다 증가)
for (int i = 0; i < 6; i++) {
value += amplitude * noise(st * frequency);
frequency *= 2.0; // 주파수 2배 (lacunarity)
amplitude *= 0.5; // 진폭 절반 (gain)
}
return value;
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
st *= 3.0;
// 시간에 따라 움직이는 FBM
float f = fbm(st + vec2(u_time * 0.2, u_time * 0.1));
// 구름/연기 같은 색상 매핑
vec3 color = mix(
vec3(0.1, 0.1, 0.3), // 어두운 부분
vec3(1.0, 0.9, 0.7), // 밝은 부분
f
);
gl_FragColor = vec4(color, 1.0);
}glsl
precision mediump float;
uniform float u_time;
uniform vec2 u_resolution;
float random(vec2 st) {
return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453);
}
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
vec2 u = f * f * (3.0 - 2.0 * f);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
float fbm(vec2 st) {
float v = 0.0; float a = 0.5;
for (int i = 0; i < 6; i++) {
v += a * noise(st);
st *= 2.0; a *= 0.5;
}
return v;
}
// ── 도메인 워핑 — FBM을 입력으로 FBM 호출 ──
// 대리석, 용암, 외계 지형 같은 복잡한 패턴 생성
void main() {
vec2 st = gl_FragCoord.xy / u_resolution * 3.0;
// 1차 워핑
vec2 q = vec2(
fbm(st + vec2(0.0, 0.0)),
fbm(st + vec2(5.2, 1.3))
);
// 2차 워핑 (워핑의 워핑)
vec2 r = vec2(
fbm(st + 4.0 * q + vec2(1.7, 9.2) + u_time * 0.15),
fbm(st + 4.0 * q + vec2(8.3, 2.8) + u_time * 0.12)
);
float f = fbm(st + 4.0 * r);
// 색상 매핑
vec3 color = mix(vec3(0.1, 0.0, 0.2), vec3(0.9, 0.5, 0.1), f);
color = mix(color, vec3(0.0, 0.3, 0.6), length(q));
color = mix(color, vec3(0.9, 0.2, 0.3), r.x);
gl_FragColor = vec4(color, 1.0);
}- •random(st) — 해시 기반 의사 랜덤 함수 (결정적)
- •Value Noise — 격자 꼭짓점의 랜덤값을 보간한 부드러운 노이즈
- •FBM — 여러 옥타브의 노이즈를 합산하여 프랙탈 패턴 생성
- •lacunarity — 옥타브 간 주파수 비율 (보통 2.0)
- •gain — 옥타브 간 진폭 비율 (보통 0.5)
- •Domain Warping — 노이즈를 좌표 왜곡에 사용하여 유기적 패턴 생성
💡
FBM의 옥타브 수를 늘리면 더 정밀한 패턴이 나오지만 GPU 연산량도 증가합니다. 모바일에서는 4~5 옥타브, 데스크톱에서는 6~8 옥타브가 적당합니다. Domain Warping은 FBM을 2~3번 중첩하므로 성능에 주의하세요.