Fibonacci Flowers - wizualizacja generatywnych kwiatów w Three.js
Fibonacci Flowers to aplikacja 3D napisana w czystym JavaScript przy użyciu Three.js, która generuje proceduralne kwiaty inspirowane matematycznymi wzorcami natury. Projekt wyróżnia się zaawansowanym podejściem do renderowania - customowe shadery GLSL, autorska implementacja bloom bez zewnętrznych bibliotek post-processingu oraz precyzyjna kontrola nad geometrią płatków generowaną proceduralnie.
Matematyczne podstawy: Dlaczego Fibonacci?
Sekwencja Fibonacciego to ciąg liczb, w którym każda liczba jest sumą dwóch poprzednich: [mathb]0,1,1,2,3,5,8,13,21,34,55,89,144,…[/mathb] Z ciągiem tym wiąże się złota proporcja, która pojawia się powszechnie w naturze - od układu liści na łodygach (filotaksja), przez spirale w słonecznikach, po kształty muszli.Złoty kąt w generowaniu kwiatów
Sercem algorytmu jest złoty kąt (Golden Angle), wyprowadzony ze złotej proporcji:const GA = Math.PI * (3 - Math.sqrt(5)); // ≈ 2.39996 rad ≈ 137.5°Każdy płatek jest pozycjonowany według wzoru:
for(let i = 0; i < n; i++){
const t = i / n;
const ang = i * GA;
const r = Math.sqrt(t) * R;
const x = r * Math.cos(ang);
const z = r * Math.sin(ang);
const y = dome * Math.pow(Math.max(0, 1 - r/R), dP);
// ...
}
Ten wzór zapewnia, że płatki układają się w naturalne spirale, minimalizując nakładanie się - dokładnie tak, jak w prawdziwych kwiatach (np. słonecznikach, gdzie nasiona układają się w spirale Fibonacciego).
Architektura renderowania
Geometria proceduralna płatków
Kluczowa funkcja mkPetal() generuje geometrię BufferGeometry z trzema atrybutami:- position - pozycje wierzchołków
- uv - współrzędne tekstury
- pa - parametry anizotropowe używane w shaderze do obliczeń oświetleniowych
function mkPetal(len, wid, sharp, bend, curl, ridge, crease, SU, SV){
const pos = [], pa = [], uva = [], idx = [];
for(let iu = 0; iu <= SU; iu++){
const u = iu / SU;
const w = Math.pow(Math.sin(u * Math.PI), sharp) * wid;
const ly = Math.sin(u * Math.PI * .65) * bend
- Math.pow(Math.max(0, u - .62) / .38, 2.8) * curl;
for(let iv = 0; iv <= SV; iv++){
const v = (iv / SV) * 2 - 1;
pos.push(
v * w,
ly + (1 - v*v*v*v) * ridge * Math.sin(u * Math.PI * .9) - Math.abs(v) * w * crease,
u * len
);
uva.push((v + 1) * .5, u);
pa.push(u, Math.abs(v));
}
}
// ... generowanie indeksów
}
Geometria jest parametryzowana przez 9 zmiennych, co pozwala na tworzenie różnych kształtów - od ostrych płatków anemonów po grube, mięsiste liście sukulentów.
Custom shadery GLSL
Aplikacja używa własnych shaderów zamiast standardowych materiałów Three.js: Vertex Shader (FVS):
attribute vec2 pa;
uniform float pt;
varying vec2 vPA;
varying vec3 vN, vVP;
varying float vPT;
void main(){
vPA = pa; vPT = pt;
vec4 mv = modelViewMatrix * vec4(position, 1.);
vN = normalize(normalMatrix * normal);
vVP = -mv.xyz;
gl_Position = projectionMatrix * mv;
}
Fragment Shader (FFS): implementuje zaawansowany model oświetlenia z:
- Diffuse lighting z dwoma źródłami światła
- Specular highlights (Blinn-Phong)
- Fresnel effect dla krawędzi
- Subsurface scattering symulowany przez wzmocnienie czerwieni przy krawędziach
- Color grading z korekcją luminancji
uniform vec3 cA, cB, cE;
varying vec2 vPA;
varying vec3 vN, vVP;
varying float vPT;
void main(){
float u = vPA.x, ev = vPA.y, ft = 1. - vPT;
vec3 rc = mix(cA, cB, ft * .44);
vec3 tc = mix(cA, cB, min(1., ft * .5 + .54));
vec3 base = mix(rc, tc, pow(u, .60));
base = mix(base, cE, pow(ev, 2.2) * .42);
// ... oświetlenie
}
Autorski Bloom bez post-processing library
Zamiast korzystać z EffectComposer z three/examples/jsm/postprocessing, aplikacja implementuje własną klasę Bloom:
class Bloom {
constructor(renderer, w, h){
this.strength = 1.40;
this.threshold = .45;
this.radius = 2.;
this.passes = 3;
// ... render targets
}
render(sc, cam){
// 1. Render sceny do _sRT
// 2. Threshold do _pRT
// 3. Wielokrotny blur (Gaussian) w _pRT i _qRT
// 4. Composite: scene + bloom * strength
}
}
Kluczowe elementy:
- Separate render targets dla sceny, threshold i blur
- Gaussian blur z 9-tapowym filtrem (wagi: 0.054, 0.0918, 0.1216, 0.1945, 0.1762...)
- Tonemapping w kompozycji: <span class="kod">col = col / (col + 1.)</span> + gamma correction <span class="kod">pow(col, 0.4545)</span>
Animacja kwitnienia
System animacji kwitnienia to jeden z najbardziej zaawansowanych elementów aplikacji.Struktura referencji płatków
let petalRefs = [];
// Każdy entry: { tiltNode, finalRot, petalT }
// petalT = 0 (najbardziej wewnętrzny) … 1 (najbardziej zewnętrzny)
Funkcja opóźnienia (stagger)
function petalDelay(petalT) {
return P.bloomOrder === 'inside → outside'
? petalT * P.animStagger
: (1 - petalT) * P.animStagger;
}
To pozwala na wybór kierunku kwitnienia - od środka na zewnątrz lub odwrotnie.
Easing functions
const easeOutBack = t => {
const c = 1.70158, p = c + 1;
return 1 + p * (t - 1)**3 + c * (t - 1)**2;
};
const easeInOutCubic = t =>
t < .5 ? 4 * t * t * t : 1 - (-2 * t + 2)**3 / 2;
- Scale używa <span class="kod">easeOutBack</span> - daje efekt "sprężystego" rozkwitu,
- Rotation używa <span class="kod">easeInOutCubic</span> - płynny, symetryczny ruch