fBm clouds

fBm clouds

프랙탈 브라운 운동.

TypeScript로 쓰고 TypeShade가 컴파일한 fBm clouds 셰이더. 빌드할 때 렌더링함.
// ═══ typeshade example — fBm clouds (fractal value noise) ═══
//
// Value-noise summed over octaves (fractal Brownian motion): each octave doubles
// the frequency and halves the amplitude, drifting over time. The textbook
// procedural-cloud / terrain primitive. Showcases helper fns + a `Loop`-driven
// octave accumulator. One DSL source → WGSL (WebGPU) + GLSL ES 3.00 (WebGL2).
import {
fn,
module,
u32,
f32,
toF32,
vec2,
vec3,
vec4,
sin,
floor,
fract,
dot,
mix,
clamp,
Loop,
Let,
f32T,
vec2fT,
} from '../src/index.js';
import { VsOut, vs, fullscreenUniforms } from './_fullscreen.js';
import type { ShaderExample } from './_shared.js';
const U = fullscreenUniforms({ octaves: f32T });
// scalar hash of a lattice point → [0,1)
const hash = fn('hash', { p: vec2fT }, ({ p }) =>
fract(sin(dot(p, vec2(127.1, 311.7))).mul(43758.5453)),
);
// bilinear value noise with smootherstep weights.
const noise = fn('noise', { p: vec2fT }, ({ p }) => {
const i = Let(floor(p));
const f = Let(fract(p));
const u = f.mul(f).mul(vec2(3).sub(f.mul(2))); // 3f² − 2f³
return mix(
mix(hash({ p: i }), hash({ p: i.add(vec2(1, 0)) }), u.x),
mix(hash({ p: i.add(vec2(0, 1)) }), hash({ p: i.add(vec2(1, 1)) }), u.x),
u.y,
);
});
const fs = fn(
'fs',
{ vo: VsOut },
({ vo }) => {
const uv = vo.uv;
const q = vec2(uv.x.mul(3), uv.y.mul(3)); // mutable sample point (octave frequency)
const v = f32(0);
const amp = f32(0.55);
Loop(
u32(0),
(i) => toF32(i).lt(U.field.octaves),
() => {
v.assign(v.add(amp.mul(noise({ p: q.add(vec2(U.field.time.mul(0.08), 0)) }))));
q.assign(q.mul(2.02));
amp.assign(amp.mul(0.5));
},
);
// sky → cloud ramp
const sky = vec3(0.2, 0.42, 0.72);
const cloud = vec3(0.97, 0.97, 1.0);
return vec4(mix(sky, cloud, clamp(v, 0, 1)), 1);
},
{ stage: 'fragment', retAttr: '@location(0)' },
);
// `hash`/`noise` are called via their handles, so module() collects them transitively — funcs lists only the entry points.
const fbmModule = module({
structs: [U.struct, VsOut.decl],
bindings: [U.binding],
funcs: [vs, fs],
});
export const fbmClouds: ShaderExample = {
id: 'fbm-clouds',
title: 'fBm clouds',
blurb:
'Fractal Brownian motion — value noise summed over octaves of doubling frequency / halving amplitude, drifting over time. The classic procedural cloud field, octave count live-tunable.',
category: 'generic',
file: 'fbm-clouds.ts',
module: fbmModule,
renderable: true,
controls: {
time: { kind: 'time' },
resolution: { kind: 'resolution' },
octaves: { kind: 'slider', label: 'Octaves', min: 1, max: 8, step: 1, value: 6 },
},
};
struct Uniforms {
time: f32,
resolution: vec2<f32>,
octaves: f32,
}
struct VsOut {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
}
@group(0) @binding(0) var<uniform> U: Uniforms;
fn hash(p: vec2<f32>) -> f32 {
return fract((sin(dot(p, vec2<f32>(127.1, 311.7))) * 43758.5453));
}
fn noise(p: vec2<f32>) -> f32 {
let _v0 = floor(p);
let _v1 = fract(p);
let _cse0 = vec2<f32>(3.0);
let _gv0 = ((_v1 * _v1) * (_cse0 - (_v1 * 2.0)));
let _lc0 = _gv0.x;
return mix(mix(hash(_v0), hash((_v0 + vec2<f32>(1.0, 0.0))), _lc0), mix(hash((_v0 + vec2<f32>(0.0, 1.0))), hash((_v0 + vec2<f32>(1.0, 1.0))), _lc0), _gv0.y);
}
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> VsOut {
let _cse0 = ((f32((vi & 1u)) * 4.0) - 1.0);
let _cse1 = ((f32((vi >> 1u)) * 4.0) - 1.0);
return VsOut(vec4<f32>(_cse0, _cse1, 0.0, 1.0), vec2<f32>(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5)));
}
@fragment
fn fs(vo: VsOut) -> @location(0) vec4<f32> {
let _licm0 = U.octaves;
let _licm1 = vec2<f32>((U.time * 0.08), 0.0);
var _av0: f32 = 0.0;
var _av1: vec2<f32> = vec2<f32>((vo.uv.x * 3.0), (vo.uv.y * 3.0));
var _av2: f32 = 0.55;
for (var _v0: u32 = 0u; (f32(_v0) < _licm0); _v0 = (_v0 + 1u)) {
_av0 = (_av0 + (_av2 * noise((_av1 + _licm1))));
_av1 = (_av1 * 2.02);
_av2 = (_av2 * 0.5);
}
return vec4<f32>(mix(vec3<f32>(0.2, 0.42, 0.72), vec3<f32>(0.97, 0.97, 1.0), clamp(_av0, 0.0, 1.0)), 1.0);
}
#version 300 es
precision highp float;
precision highp int;
out vec2 uv;
void main() {
uint vi = uint(gl_VertexID);
float _cse0 = ((float((vi & 1u)) * 4.0) - 1.0);
float _cse1 = ((float((vi >> 1u)) * 4.0) - 1.0);
gl_Position = vec4(_cse0, _cse1, 0.0, 1.0);
uv = vec2(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5));
}
#version 300 es
precision highp float;
precision highp int;
layout(std140) uniform Uniforms {
float time;
vec2 resolution;
float octaves;
} U;
float hash(vec2 p) {
return fract((sin(dot(p, vec2(127.1, 311.7))) * 43758.5453));
}
float noise(vec2 p) {
vec2 _v0 = floor(p);
vec2 _v1 = fract(p);
vec2 _cse0 = vec2(3.0);
vec2 _gv0 = ((_v1 * _v1) * (_cse0 - (_v1 * 2.0)));
float _lc0 = _gv0.x;
return mix(mix(hash(_v0), hash((_v0 + vec2(1.0, 0.0))), _lc0), mix(hash((_v0 + vec2(0.0, 1.0))), hash((_v0 + vec2(1.0, 1.0))), _lc0), _gv0.y);
}
in vec2 uv;
layout(location = 0) out vec4 _ret;
void main() {
float _licm0 = U.octaves;
vec2 _licm1 = vec2((U.time * 0.08), 0.0);
float _av0 = 0.0;
vec2 _av1 = vec2((uv.x * 3.0), (uv.y * 3.0));
float _av2 = 0.55;
for (uint _v0 = 0u; (float(_v0) < _licm0); _v0 = (_v0 + 1u)) {
_av0 = (_av0 + (_av2 * noise((_av1 + _licm1))));
_av1 = (_av1 * 2.02);
_av2 = (_av2 * 0.5);
}
_ret = vec4(mix(vec3(0.2, 0.42, 0.72), vec3(0.97, 0.97, 1.0), clamp(_av0, 0.0, 1.0)), 1.0);
}

fBm clouds. 빌드할 때 그린 화면입니다.

WGSL과 GLSL 탭은 커밋 c66579bf의 컴파일러가 직접 낸 출력입니다. 컴파일러의 출력 검사가 구워 둔 골든 파일에서 그대로 읽어 왔습니다(emit-goldens.test.ts).

이 예제는 fn() 빌더 API로 작성해서 Playground의 편집기가 받지 않습니다.

GitHub의 파일

이 페이지 편집 문제 보고