Generatywne kwiaty Fibonacciego w Three.js
W tym tutorialu stworzymy proceduralny generator organicznych kwiatów w Three.js - inspirowany strukturami biologicznymi, spiralami Fibonacciego i nowoczesnym generative art.
Efekt końcowy pozwala:
- generować płynne organiczne formy,
- zmieniać liczbę płatków,
- sterować deformacją,
- używać gradientów kolorystycznych,
- obracać i przybliżać model myszą,
- korzystać z gotowych presetów.
Najważniejsze jest jednak to, że po drodze dokładnie omówimy matematykę i sposób działania całego algorytmu.
Przygotowanie sceny Three.js
Zaczynamy od klasycznej konfiguracji Three.js.
Import bibliotek
Najpierw importujemy Three.js, OrbitControls oraz bibliotekę GUI.
import * as THREE from "three";
import { OrbitControls }
from "three/examples/jsm/controls/OrbitControls.js";
import GUI
from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
OrbitControls pozwoli obracać scenę myszą, a lil-gui stworzy panel parametrów.
Tworzenie sceny
let scene = new THREE.Scene(); scene.background = new THREE.Color(0xf6f6f6);
Scena jest kontenerem na wszystkie obiekty 3D.
Dodatkowo ustawiamy jasne tło, dzięki któremu kolory kwiatu będą dobrze widoczne.
Kamera
let camera = new THREE.PerspectiveCamera(
45,
innerWidth / innerHeight,
0.1,
100
);
camera.position.set(0, 1.5, 4);
Tutaj definiujemy kamerę perspektywiczną.
Parametry oznaczają:
- 45 - kąt widzenia,
- 0.1 - minimalna odległość renderowania,
- 100 - maksymalna odległość renderowania.
Kamera zostaje odsunięta od środka sceny, aby cały kwiat był widoczny.
Renderer WebGL
let renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(devicePixelRatio);
document.body.appendChild(renderer.domElement);
Renderer odpowiada za rysowanie grafiki.
Opcja antialias wygładza krawędzie, co ma duże znaczenie przy organicznych kształtach.
Sterowanie myszą - OrbitControls
let controls = new OrbitControls(
camera,
renderer.domElement
);
controls.enableDamping = true;
Dzięki temu użytkownik może przybliżać, oddalać, obracać oraz przesuwać scenę. enableDamping dodaje płynności ruchu kamery.
Oświetlenie sceny
Dodajemy dwa źródła światła - ambient oraz kierunkowe.
Światło ambient
scene.add(
new THREE.AmbientLight(
0xffffff,
0.7
)
);
To miękkie światło rozjaśniające całą scenę.
Światło kierunkowe
let dir = new THREE.DirectionalLight(
0xffffff,
1.2
);
dir.position.set(2, 3, 3);
scene.add(dir);
To światło działa trochę jak słońce - ma konkretny kierunek padania.
Parametry generatora
Naturalne formy nigdy nie są idealne matematycznie. Dlatego dodajemy delikatne zakłócenia chociaż możemy to wyłączyć.
function noise(x, y){
return (
Math.sin(x * 2.3 + y * 3.1) * 0.5 +
Math.sin(x * 5.7 - y * 1.9) * 0.25
);
}
To bardzo prosty proceduralny noise oparty o funkcje sinusoidalne.
Generowanie geometrii
Najważniejsza część programu to funkcja createFlower(). To właśnie tutaj budujemy cały model.
function createFlower() {
// tutaj generujemy geometrię
}
Tworzenie BufferGeometry
let geo = new THREE.BufferGeometry();
BufferGeometry to najwydajniejszy sposób tworzenia własnych modeli w Three.js.
Musimy ręcznie zdefiniować:
- pozycje wierzchołków,
- kolory,
- normalne,
- indeksy trójkątów.
Tablice geometrii
let vertices = []; let normals = []; let colors = []; let indices = [];
Każda z tych tablic pełni inną funkcję.
Siatka punktów
Generator działa na siatce parametrów u i v.
for(let j = 0; j <= heightSegments; j++) {
let v = j / heightSegments;
for(let i = 0; i <= radialSegments; i++) {
let u = i / radialSegments;
}
}
Można to traktować jak współrzędne na powierzchni. u odpowiada za obrót wokół środka, a v za odległość od centrum.
Tworzenie fal płatków
Najważniejszy fragment matematyczny:
let petalWave = Math.pow(
0.5 + 0.5 *
Math.sin(angle * params.petals),
params.softness
);
To właśnie tutaj rodzą się płatki.
Jak działa ten wzór?
Krok 1 - sinusoida
Math.sin(angle * params.petals)
Tworzy falę wokół okręgu.
Jeśli petals = 8, otrzymamy osiem szczytów.
Krok 2 - zmiana zakresu
Sinus zwraca wartości od -1 do 1. Dlatego przesuwamy zakres:
0.5 + 0.5 * Math.sin(...)
Teraz wartości mieszczą się w zakresie od 0 do 1.
Krok 3 - wygładzanie
Math.pow(..., params.softness)
To niezwykle ważny trik.
Potęgowanie powoduje:
- miękkie przejścia,
- bardziej organiczny profil,
- naturalniejsze płatki.
Dodawanie deformacji
let flowNoise =
noise(
angle + v * params.flow,
v * 2
)
* params.noiseAmp;
To tworzy lokalne zakłócenia powierzchni. Dzięki temu kwiat nie wygląda idealnie symetrycznie. Oczywiście można z tego zrezygnować i uzyskać idealną symetrię.
Obliczanie promienia
let r =
v +
petalWave *
params.amplitude *
v +
flowNoise;
Promień każdego punktu zależy od:
- odległości od środka,
- wysokości płatka,
- deformacji noise.
Skręcanie płatków
let bendFactor = Math.pow(
v,
params.bendPower
);
let bendAngle =
angle +
bendFactor *
params.bend;
Im dalej od środka tym mocniejsze skręcenie.
Współrzędne 3D
let x =
Math.cos(bendAngle) * r;
let z =
Math.sin(bendAngle) * r;
To klasyczna konwersja: promień + kąt → współrzędne kartezjańskie.
Dodawanie wysokości
let y =
Math.pow(v, 1.2) *
0.85 *
(1 + petalWave * 0.35);
To nadaje płatkom objętość.
Gradient kolorystyczny
let c = colA.clone().lerp(
colB,
v
);
Kolory są płynnie interpolowane od środka ku końcówkom płatków.
Tworzenie trójkątów
indices.push(a, b, d); indices.push(b, c, d);
W ten sposób punkty zostają połączone w pełną siatkę geometryczną.
Materiał fizyczny
let material = new THREE.MeshPhysicalMaterial({
vertexColors: true,
roughness: 0.55,
transmission: 0.25,
thickness: 0.7,
transparent: true,
opacity: 0.96,
side: THREE.DoubleSide
});
To właśnie materiał odpowiada za:
- miękkie światło,
- półprzezroczystość,
- organiczny wygląd.
GUI - interaktywny edytor
gui.add(
params,
"petals",
3,
120,
1
).onChange(createFlower);
Każdy slider zmienia parametr i automatycznie przebudowuje geometrię.
Animacja obrotu
if (params.rotate && flowerMesh) {
flowerMesh.rotation.y += 0.002;
}
Subtelny ruch bardzo poprawia odbiór modelu.
Pętla renderująca
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
To serce każdej aplikacji realtime.
Najciekawsze w tym projekcie jest to, że organiczne struktury powstają z kilku prostych funkcji matematycznych.