
Perlin 노이즈는 창의적 코딩, 시각 예술, 게임 개발 등에서 자연스럽고 유기적인 무작위성을 구현하는 핵심 기법입니다.
Perlin 노이즈는 Ken Perlin이 1980년대 개발한 절차적 노이즈(Procedural Noise) 알고리즘입니다.
일반적인 랜덤 값은 불연속적이지만, Perlin 노이즈는 인접한 좌표에서 비슷한 값을 반환해 부드러운 변화를 만듭니다.
이 덕분에 구름, 물결, 지형, 불꽃 같은 자연 현상을 효과적으로 흉내 낼 수 있습니다.
Nannou는 내부적으로 noise 크레이트를 사용해 Perlin 노이즈를 제공합니다.
use nannou::noise::{NoiseFn, Perlin};
Perlin: 노이즈 생성기 구조체NoiseFn: .get() 메서드를 통해 노이즈 값을 계산할 수 있게 해주는 트레이트let perlin = Perlin::new(42); // 42는 임의의 시드 값
noise 크레이트 버전 0.9는 u32 타입의 시드(seed) 인자를 요구rand 크레이트를 사용해 랜덤 시드를 생성let value = perlin.get([x as f64, y as f64]);
[f64; N] 배열 (2D라면 [x, y])f64, 일반적으로 -1.0 ~ 1.0f64를 사용해야 합니다. f32는 동작하지 않습니다. Perlin 노이즈는 너무 "느리게" 또는 "빠르게" 변할 수 있으므로, 좌표를 스케일링하는 것이 중요합니다.
let noise_val = perlin.get([p.x as f64 * 0.01, p.y as f64 * 0.01]);
* 0.01 → 낮은 주파수: 부드러운 변화* 0.1 → 높은 주파수: 세밀하고 복잡한 패턴0.0~1.0)로 변환하려면 map_range를 사용합니다:let brightness = map_range(noise_val, -1.0, 1.0, 0.0, 1.0);
let n = perlin.get([x * 0.02, y * 0.02]);
let gray = map_range(n, -1.0, 1.0, 0.0, 1.0);
draw.ellipse().color(gray);
// Model에 시간 저장
struct Model {
time: f32,
perlin: Perlin,
}
fn update(app: &App, model: &mut Model, _update: Update) {
model.time = app.time;
}
// view에서 사용
let n = model.perlin.get([x * 0.02, model.time * 0.1]);
for x in 0..800 {
for y in 0..800 {
let height = perlin.get([x as f64 * 0.05, y as f64 * 0.05]);
let color = if height > 0.3 { rgb(0.2, 0.8, 0.2) } else { rgb(0.1, 0.1, 0.8) };
draw.rect().x_y(x as f32 - 400.0, y as f32 - 400.0).w_h(1.0, 1.0).color(color);
}
}
더 자연스러운 결과를 원한다면, 여러 주파수의 Perlin 노이즈를 겹쳐서(octaves) 사용하세요.
fn fractal_noise(perlin: &Perlin, x: f64, y: f64, octaves: usize) -> f64 {
let mut value = 0.0;
let mut amplitude = 1.0;
let mut frequency = 1.0;
let mut total_amp = 0.0;
for _ in 0..octaves {
value += perlin.get([x * frequency, y * frequency]) * amplitude;
total_amp += amplitude;
amplitude *= 0.5; // 진폭 점점 줄이기
frequency *= 2.0; // 주파수 점점 높이기
}
value / total_amp // 정규화하여 -1.0~1.0 유지
}
이 방식으로 구름, 마블 무늬, 산맥 지형 같은 복잡한 텍스처를 만들 수 있습니다.
f64 배열 ([f64; 2]) 사용0~800)를 그대로 넣으면 너무 빠르게 변함 → * 0.01 권장
- 좌표를 스케일링하세요 (
* 0.01)map_range로 값을 유용한 범위로 변환하세요- 시간(
app.time)과 결합해 애니메이션을 만드세요- 옥타브를 쌓아 더 복잡한 패턴을 구현하세요

use nannou::prelude::*;
use nannou::noise::{NoiseFn, Perlin};
fn main() {
nannou::app(model).update(update).run();
}
struct Model {
perlin: Perlin,
points: Vec<Point2>,
}
fn model(app: &App) -> Model {
app.new_window()
.size(800, 800)
.title("Perlin Noise Grid")
.view(view)
.build()
.unwrap();
let perlin = Perlin::new();
let mut points = Vec::new();
let step = 10.0;
for y in (0..800).step_by(step as usize) {
for x in (0..800).step_by(step as usize) {
points.push(pt2(x as f32 - 400.0, y as f32 - 400.0));
}
}
Model { perlin, points }
}
fn update(_app: &App, _model: &mut Model, _update: Update) {
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(BLACK);
for p in &model.points {
let noise_val = model.perlin.get([p.x as f64 * 0.01, p.y as f64 * 0.01]);
let brightness = map_range(noise_val, -1.0, 1.0, 0.0, 1.0);
if brightness > 0.5 {
draw.ellipse().x_y(p.x, p.y).w_h(2.0, 2.0).color(WHITE);
}
}
draw.to_frame(app, &frame).unwrap();
}
// =============================================================================
// 0. 모듈 및 크레이트 임포트 (Imports)
// =============================================================================
// `nannou::prelude::*`는 nannou 애플리케이션 개발에 필요한 대부분의 기본 타입과 함수를 한 번에 가져옵니다.
// 여기에는 벡터(Point2, Vec2), 색상(BLACK, WHITE), 유틸리티 함수(map_range 등)가 포함됩니다.
use nannou::prelude::*;
// Perlin 노이즈 생성기를 사용하기 위해 nannou의 noise 모듈에서 Perlin 구조체와 NoiseFn 트레이트를 가져옵니다.
// Perlin은 procedural noise(절차적 노이즈)를 생성하는 데 사용되며, 자연스러운 텍스처나 패턴을 만들 때 유용합니다.
use nannou::noise::{NoiseFn, Perlin};
// =============================================================================
// 1. 메인 함수 (Application Entry Point)
// =============================================================================
// Rust 프로그램의 진입점입니다.
// `nannou::app(model)`은 nannou 앱을 초기화하고, `model` 함수를 통해 초기 상태를 설정합니다.
// `.update(update)`는 매 프레임마다 호출될 업데이트 로직을 지정합니다.
// `.run()`은 앱을 실행하고 이벤트 루프를 시작합니다.
fn main() {
nannou::app(model).update(update).run();
}
// =============================================================================
// 2. 모델 정의 (State Structure)
// =============================================================================
// `Model` 구조체는 애플리케이션의 상태를 저장합니다.
// Rust에서 구조체는 관련된 데이터를 그룹화하는 사용자 정의 타입입니다.
struct Model {
// Perlin 노이즈 생성기 인스턴스.
// 이는 2D 좌표를 입력받아 -1.0 ~ 1.0 사이의 연속적인 노이즈 값을 반환합니다.
perlin: Perlin,
// 화면에 그릴 점(Point2)들의 벡터.
// `Point2`는 nannou에서 제공하는 2D 좌표 타입으로, x와 y 필드를 가집니다.
// `Vec<T>`는 Rust의 동적 배열 타입으로, 크기가 런타임에 변할 수 있습니다.
points: Vec<Point2>,
}
// =============================================================================
// 3. 모델 초기화 함수 (Model Initialization)
// =============================================================================
// `model` 함수는 앱 시작 시 한 번만 호출되며, 초기 상태(`Model`)를 반환합니다.
// `app: &App`은 nannou 앱 인스턴스에 대한 불변 참조입니다.
fn model(app: &App) -> Model {
// 새로운 창을 생성하고 설정합니다.
// 메서드 체이닝을 통해 창의 속성을 순차적으로 설정합니다.
app.new_window()
.size(800, 800) // 창 크기를 800x800 픽셀로 설정
.title("Perlin Noise Grid") // 창 제목 설정
.view(view) // 렌더링에 사용할 `view` 함수 지정
.build() // 창 빌드 요청
.unwrap(); // Result 타입을 처리: 오류 발생 시 패닉 (간단한 예제에서는 허용)
// Perlin 노이즈 생성기 인스턴스를 생성합니다.
// 기본 시드로 초기화되며, 항상 같은 시퀀스의 노이즈를 생성합니다.
let perlin = Perlin::new();
// 점들을 저장할 빈 벡터를 생성합니다.
let mut points = Vec::new();
// 샘플링 포인트 (격자 기반)
// `step`은 격자 간격을 픽셀 단위로 정의합니다.
// f32 타입으로 선언되어 있으나, 반복문에서 usize로 캐스팅되어 사용됩니다.
let step = 10.0;
// y축 방향으로 0부터 799까지 `step` 간격으로 반복합니다.
// `(0..800)`은 0 이상 800 미만의 범위(Range)를 생성합니다.
// `.step_by(n)`은 반복을 n씩 건너뛰게 합니다 (여기서는 10픽셀 간격).
// 주의: `step as usize`는 부동소수점 값을 정수로 변환하므로, 정확한 간격을 유지하려면 step이 정수여야 합니다.
for y in (0..800).step_by(step as usize) {
// x축 방향으로 동일하게 반복합니다.
for x in (0..800).step_by(step as usize) {
// `pt2(x, y)`는 nannou에서 제공하는 Point2 생성 매크로입니다.
// 좌표계를 화면 중앙(0,0) 기준으로 맞추기 위해 400을 뺍니다.
// 원래 좌표는 (0,0) ~ (800,800)이지만, 이를 (-400,-400) ~ (400,400)으로 변환합니다.
points.push(pt2(x as f32 - 400.0, y as f32 - 400.0));
}
}
// 초기화된 Model 인스턴스를 반환합니다.
Model { perlin, points }
}
// =============================================================================
// 4. 업데이트 함수 (Per-Frame Update Logic)
// =============================================================================
// `update` 함수는 매 프레임마다 호출됩니다.
// 이 예제에서는 정적인 이미지를 그리므로, 업데이트 로직이 없습니다.
// `_` 접두사는 사용하지 않는 변수를 명시적으로 무시함을 나타냅니다 (Rust의 관용적 표현).
fn update(_app: &App, _model: &mut Model, _update: Update) {
// 여기서는 아무 것도 업데이트하지 않음 (정적 이미지)
}
// =============================================================================
// 5. 뷰 함수 (Rendering Logic)
// =============================================================================
// `view` 함수는 매 프레임마다 화면을 그리는 데 사용됩니다.
// `frame: Frame`은 현재 렌더링 대상 프레임 버퍼입니다.
fn view(app: &App, model: &Model, frame: Frame) {
// `app.draw()`는 새로운 드로우 컨텍스트를 생성합니다.
// 이 컨텍스트를 통해 도형을 정의하고, 마지막에 프레임에 렌더링합니다.
let draw = app.draw();
// 배경을 검은색(BLACK)으로 설정합니다.
// `draw.background()`는 전체 캔버스를 지정된 색상으로 채웁니다.
draw.background().color(BLACK);
// Perlin noise로 점 찍기
// `model.points` 벡터의 각 점에 대해 반복합니다.
// `&model.points`는 소유권을 이동시키지 않고 참조만 전달합니다 (성능 최적화).
for p in &model.points {
// Perlin 노이즈 값 계산:
// `model.perlin.get([x, y])`는 2D 좌표를 입력받아 노이즈 값을 반환합니다.
// 입력 좌표는 f64 타입이어야 하며, 원본 좌표(p.x, p.y)를 0.01배로 스케일링하여
// 노이즈의 "주파수"를 조절합니다 (값이 작을수록 더 부드러운 변화).
let noise_val = model.perlin.get([p.x as f64 * 0.01, p.y as f64 * 0.01]);
// 노이즈 값을 0.0~1.0 범위로 매핑합니다.
// `map_range(value, in_min, in_max, out_min, out_max)`는 선형 보간을 수행합니다.
// Perlin 노이즈는 일반적으로 -1.0 ~ 1.0 사이의 값을 반환하므로,
// 이를 밝기(brightness)로 사용하기 위해 0.0~1.0으로 변환합니다.
let brightness = map_range(noise_val, -1.0, 1.0, 0.0, 1.0);
// 밝기가 0.5보다 클 경우에만 점을 그립니다 (임계값 기반 필터링).
if brightness > 0.5 {
// 흰색(WHITE) 타원(원)을 그립니다.
// `.ellipse()`은 타원 드로우 명령을 시작합니다.
// `.x_y(x, y)`는 타원의 중심 좌표를 설정합니다.
// `.w_h(width, height)`는 타원의 너비와 높이를 설정합니다 (여기서는 2x2 원).
// `.color(color)`는 타원의 색상을 설정합니다.
draw.ellipse().x_y(p.x, p.y).w_h(2.0, 2.0).color(WHITE);
}
}
// 드로우 컨텍스트의 내용을 실제 프레임 버퍼에 렌더링합니다.
// `.unwrap()`은 렌더링 오류 발생 시 패닉을 유발합니다 (간단한 예제에서는 허용).
draw.to_frame(app, &frame).unwrap();
}

use nannou::prelude::*;
use noise::{NoiseFn, Perlin};
struct Model {
perlin: Perlin,
}
fn main() {
nannou::app(model).update(update).run();
}
fn model(app: &App) -> Model {
app.new_window()
.size(800, 800)
.view(view)
.build()
.unwrap();
let perlin = Perlin::new(11);
Model { perlin }
}
fn update(_app: &App, _model: &mut Model, _update: Update) {}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(WHITE);
const SCALE: f32 = 2.0;
const STEP: usize = (800.0 / SCALE) as usize; // 400 steps
for i in 0..STEP {
for j in 0..STEP {
let nx = (i as f64 * SCALE as f64) * 0.02;
let ny = (j as f64 * SCALE as f64) * 0.02;
let height = model.perlin.get([nx, ny]);
let color = if height > 0.3 {
rgb(0.2, 0.8, 0.2)
} else {
rgb(0.1, 0.1, 0.8)
};
// 왼쪽 아래 모서리를 (-400, -400)에 맞춤
let center_x = -400.0 + i as f32 * SCALE + SCALE / 2.0;
let center_y = -400.0 + j as f32 * SCALE + SCALE / 2.0;
/*
첫 번째 사각형: 중심 = (-400 + 2, -400 + 2) = (-398, -398)
마지막 사각형 (i=199): 중심 = (-400 + 796 + 2, ...) = (398, 398)
사각형 범위: 398 ± 2 → [-400, 400] 꽉 채움!
*/
draw.rect()
.x_y(center_x, center_y)
.w_h(SCALE, SCALE)
.color(color);
}
}
draw.to_frame(app, &frame).unwrap();
}