Geometrie

V této části si vysvětlíme co je to geometrie a jaké nám Three.js poskytuje typy geometrií. S geometrií jsme se v tutoriálu již setkali. Vytvářeli jsme například kostku, kouly a kužel. K vytvoření těchto objektů jsme použili specifické typy geometrií, které nám Three.js nabízí. V této části se na geometrii podíváme více do detailu.

Co je geometrie

Ve Three.js geometrie slouží k vytvoření tvaru, který potom můžeme použít pro meshe. Jedná se o kombinaci vertexů (bodů v 3D prostoru) a polygonů (třech bodů spojených do trojúhelníku). Mesh je kombinace geometrie a materiálu a představuje tedy objekt, který můžeme vidět ve scéně. Kromě meshů ale můžeme geometrii použít také třeba pro particles k uchování lokace jednotlivých částic. Také bych chtěl zmínit, že kromě toho že geometrie uchovává tvar pomocí vertexů a polygonů, tak může ukládat i data jako jsou UV souřadnice, normály, nebo třeba barvy vertexů. Když to shrnu, tak geometrie slouží jako tvar, na který potom můžeme aplikovat materiál (vytvořit mesh) a umístit jej do scény. Použitím geometrie pro particles se budeme zabývat až v jiné části.

Startovní kód

Abychom si mohli zkusit vytvořit různé typy geometrií, které nám Three.js nabízí, tak k tomu potřebujeme scénu, ve které si je po vytvoření můžeme prohlédnout. Pomocí startovního kódu z části o Webpack si vytvořte nový projekt a do JavaScript souboru si zkopírujte kód, který ukazuje následující ukázka. Tento kód vytváří scénu a přidává do ní zatím jen světla. Světla potřebujeme, jelikož budeme používat jiný typ materiálu než který jsme doposud používali. Tento materiál potřebuje světlo aby byl vidět. Světlem se budeme zabývat v jiné části, nemusíte zkoumat jak to funguje, stačí když chápete že jej přidáváme do scény. Také vytváříme animační smyčku, roztahujeme canvas přes celé okno a vytváříme OrbitControls ovládání, abychom se mohli po scéně pohybovat. U OrbitControls ještě nastavujeme možnost enableDamping na true, abychom měli takový tlumený pohyb při posunutí kamery.

import './style.css';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

// vytvoření scény
const scene = new THREE.Scene();

// vytvoření materiálu
const material = new THREE.MeshStandardMaterial({
    color: 0x78E8FA,
    roughness: 0.5,
});

// přidání světel do scény
scene.add(new THREE.AmbientLight(0xffffff, 0.2));
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(0.5, 2, 0.7);
scene.add(directionalLight);

// vytvoření kamery
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.z = 3;
scene.add(camera);

// vytvoření rendereru
const renderer = new THREE.WebGLRenderer({
    canvas: document.getElementById("WebGLCanvas")
});
// nastavení velikosti canvasu a pixel ratio
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

// přidání event listeneru pro změnu velikosti okna
window.addEventListener("resize", () => {
    // aktualizace poměru stran kamery
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    // změnění velikosti canvasu a pixel ratio
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});

// vytvoření OrbitControls ovládání
const controls = new OrbitControls(camera, renderer.domElement);
// zapnutí tlumení při posunutí
controls.enableDamping = true;

// tato funkce je volána každý frame
function tick() {
    // protože máme zapnuté tlumení při posunutí,
    // tak musíme OrbitControls aktualizovat
    controls.update();
    // vyrenderování scény na canvas
    renderer.render(scene, camera);
}

// nastavení animační smyčky
// - funkce tick se bude volat každý frame
renderer.setAnimationLoop(tick);

Jelikož roztahujeme canvas přes celou velikost okna prohlížeče, tak se chceme zbavit defaultních marginů a paddingů. Proto si ještě stejně jako v minulých částech zkopírujte následující CSS kód a vložte do CSS souboru.

*, *::before, *::after {
    padding: 0;
    margin: 0;
}

body {
    overflow: hidden;
}

Pokud si aplikaci spustíte, tak by jste zatím neměli vidět nic. Postupně si scénu zaplníme různými typy geometrií.

Typy geometrií

Pokud bychom chtěli, tak bychom si mohli vytvořit svoji vlastní geometrii úplně od základu. Three.js nám ale pro některé tvary nabízí třídy, pomocí kterých si můžeme geometrii vytvořit jednodušeji. Všechny si je tu postupně projdeme a vyzkoušíme si s jejich pomocí vytvořit geometrie. Tyto třídy dědí od třídy BufferGeometry, která nám umožňuje vytvořit si vlastní geometrii úplně od základu. To si později také ukážeme.

Box Geometry

První geometrií, kterou si ukážeme, je BoxGeometry. Často jsme ji v tutoriálu již použili. Umožňuje nám vytvořit krychly nebo kvádr. Jako parametr při jejím vytváření předáváme šířku, výšku a hloubku krychle/kvádru. Také můžeme předat pár dalších parametrů o kterých si můžete přečíst v dokumentaci. Také si je tam můžete vyzkoušet, je tam dost dobrá interaktivních ukázka, takže mi přijde zbytečné o nich zde psát.

Následující kód si můžete zkopírovat do svého kódu a ve scéně by se vám měla vytvořit kostka.

/* ... */

// BOX GEOMETRY
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const box = new THREE.Mesh(boxGeometry, material);
scene.add(box);

Capsule Geometry

Druhou geometrií, kterou si ukážeme, je CapsuleGeometry. Slouží k vytvoření takové jakoby kapsle nebo tobolky. Jako parametr předáváme poloměr kapsle, výšku, počet segmentů na krajích kapsle a počet radiálních segmentů. Nevím jak to popsat, v dokumentaci máte interaktivní ukázku, na které si můžete jednotlivé parametry vyzkoušet.

Vložením následujícího kódu do našeho kódu si můžeme do scény kapsly přidat.

/* ... */

// CAPSULE GEOMETRY
const capsuleGeometry = new THREE.CapsuleGeometry(0.5, 2, 6, 18);
const capsule = new THREE.Mesh(capsuleGeometry, material);
capsule.position.x = 1.5;
scene.add(capsule);

Circle Geometry

Jako další geometrii si ukážeme CircleGeometry. Slouží k vytvoření kruhu. Jako parametr předáváme radius a počet segmentů. O dalších parametrech, které můžeme předat si můžete přečíst v dokumentaci.

/* ... */

// CIRCLE GEOMETRY
const circleGeometry = new THREE.CircleGeometry(0.5, 16);
const circle = new THREE.Mesh(circleGeometry, material);
circle.position.x = 3;
scene.add(circle);

Cone Geometry

Dalším typem geometrie je na řadě ConeGeometry. Slouží k vytvoření kužele. Jako parametr předáváme poloměr podstavy kužele, výšku, počet radiálních segmentů a můžeme předat ještě spoustu dalších parametrů. O těch si můžete přečíst v dokumentaci a vyzkoušet si je tam na interaktivní ukázce.

/* ... */

// CONE GEOMETRY
const coneGeometry = new THREE.ConeGeometry(0.5, 1, 12);
const cone = new THREE.Mesh(coneGeometry, material);
cone.position.x = 4.5;
scene.add(cone);

Cylinder Geometry

Další geometrii, kterou si ukážeme, je CylinderGeometry. Slouží k vytvoření válce. Jako parametr předáváme poloměr horní části válce, poloměr dolní části válce, výšku, počet radiálních segmentů a spoustu dalších parametrů, které můžete prozkoumat v dokumentaci.

/* ... */

// CYLINDER GEOMETRY
const cylinderGeometry = new THREE.CylinderGeometry(0.4, 0.4, 1, 12);
const cylinder = new THREE.Mesh(cylinderGeometry, material);
cylinder.position.x = 6;
scene.add(cylinder);

Dodecahedron Geometry

Další je na řadě DodecahedronGeometry. Nevím jak tento tvar popsat, podívejte se do dokumentace na interaktivní ukázku. Jako parametr předáváme poloměr a úroveň detailu.

/* ... */

// DODECAHEDRON GEOMETRY
const dodecahedronGeometry = new THREE.DodecahedronGeometry(0.5, 0);
const dodecahedron = new THREE.Mesh(dodecahedronGeometry, material);
dodecahedron.position.x = 7.5;
scene.add(dodecahedron);

Icosahedron Geometry

Jako další geometrii si ukážeme IcosahedronGeometry. Tato geometrie je podobná té předchozí a také nevím jak ji popsat. Takže se podívejte na ukázku v dokumentaci. Jako parametr předáváme poloměr a úroveň detailu.

/* ... */

// ICOSAHEDRON GEOMETRY
const icosahedronGeometry = new THREE.IcosahedronGeometry(0.5, 0);
const icosahedron = new THREE.Mesh(icosahedronGeometry, material);
icosahedron.position.x = 9;
scene.add(icosahedron);

Lathe Geometry

Teď si ukážeme geometrii jménem LatheGeometry. Tato geometrie slouží k tvorbě symetrických tvarů jako jsou třeba vázy. Jako parametr předáváme pole instancí třídy Vector2, počet segmentů a dva další parametry o který se můžete dozvědět v dokumentaci. Každý Vector2 určuje jakoby pozici bodu od středu tvaru (vlastnost x - tato hodnota musí být větší než 0) a pozici bodu výškově (vlastnost y). Nevím jak lépe to vysvětlit, asi si to budete muset ozkoušet a přijít na to sami.

/* ... */

// LATHE GEOMETRY
const latheGeometry = new THREE.LatheGeometry(
    [
        new THREE.Vector2(0.2, -0.5),
        new THREE.Vector2(0.5, -0.25),
        new THREE.Vector2(0.35, 0.25),
        new THREE.Vector2(0.2, 0.5)
    ],
    12
);
const lathe = new THREE.Mesh(latheGeometry, material);
lathe.position.x = 10.5;
scene.add(lathe);

Octahedron Geometry

Další geometrií je na řadě OctahedronGeometry. Nevím jak tento tvar popsat, takže se opět budete muset podívat na interaktivní ukázku v dokumentaci. Jako parametr předáváme poloměr a úroveň detailu.

/* ... */

// OCTAHEDRON GEOMETRY
const octahedronGeometry = new THREE.OctahedronGeometry(0.5, 1);
const octahedron = new THREE.Mesh(octahedronGeometry, material);
octahedron.position.x = 12;
scene.add(octahedron);

Plane Geometry

Jako další geometrii si ukážeme PlaneGeometry. Jedná se o jednoduchou čtvercovou/obdelníkovou plochu. Jako parametr předáváme šířku plochy, výšku a dva další parametry o kterých si můžete přečíst v dokumentaci.

/* ... */

// PLANE GEOMETRY
const planeGeometry = new THREE.PlaneGeometry(1, 1);
const plane = new THREE.Mesh(planeGeometry, material);
plane.position.x = 13.5;
scene.add(plane);

Polyhedron Geometry

Další geometrií je na řadě PolyhedronGeometry. Ta je trochu těžší na pochopení. Funguje to tak, že jako první parametr předáváme pole se souřadnicemi vertexů, jako druhý pole s polygony (vždy po trojicích indexů k vertexům), jako třetí poloměr a jako poslední úroveň detailu (na kolik úrovní geometrii rozdělit). Vertexy se naprojektují na kouli a poté rozdělí podle definovaného úrovně detailu. Z tohoto popisu pravděpodobně nic moc nechápete, možná se spíš podívejte do dokumentace. Hrozně špatně se to vysvětluje a navíc nevím jestli jsem to pochopil správně.

Třída PolyhedronGeometry je také použita geometriemi DodecahedronGeometry, IcosahedronGeometry, OctahedronGeometry a TetrahedronGeometry. Následující kód jsem zkopíroval z dokumentace. Když si jej přidáte do svého kódu, tak by jste měli na scéně vidět stejný tvar jako u OctahedronGeometry.

/* ... */

// POLYHEDRON GEOMETRY
// - vertexy kostky
const verticesOfCube = [
    -1,-1,-1,    1,-1,-1,    1, 1,-1,    -1, 1,-1,
    -1,-1, 1,    1,-1, 1,    1, 1, 1,    -1, 1, 1,
];
// - polygony (trojúhelníky) kostky
const indicesOfFaces = [
    2,1,0,    0,3,2,
    0,4,7,    7,3,0,
    0,1,5,    5,4,0,
    1,2,6,    6,5,1,
    2,3,7,    7,6,2,
    4,5,6,    6,7,4
];
const polyhedronGeometry = new THREE.PolyhedronGeometry( verticesOfCube, indicesOfFaces, 0.5, 1 );
const polyhedron = new THREE.Mesh(polyhedronGeometry, material);
polyhedron.position.x = 15;
scene.add(polyhedron);

Ring Geometry

Další geometrií, kterou si ukážeme, je RingGeometry. Slouží k vytvoření dvoudimenzionálního prstence. Jako parametr předáváme poloměr vnitřího kruhu, poloměr vnějšího kruhu, počet segmentů (jak moc bude prstenec kulatý) a pár dalších parametrů o kterých si můžete přečíst v dokumentaci.

/* ... */

// RING GEOMETRY
const ringGeometry = new THREE.RingGeometry(0.3, 0.5, 16);
const ring = new THREE.Mesh(ringGeometry, material);
ring.position.x = 16.5;
scene.add(ring);

Sphere Geometry

Jako další geometrie je na řadě SphereGeometry. Slouží k vytvoření koule. Jako parametr předáváme poloměr koule, počet šířkových segmentů, počet výškových segmentů a můžeme předat i spoustu dalších parametrů o kterých si můžete přečíst v dokumentaci.

/* ... */
        
// SPHERE GEOMETRY
const sphereGeometry = new THREE.SphereGeometry(0.5, 12, 8);
const sphere = new THREE.Mesh(sphereGeometry, material);
sphere.position.x = 18;
scene.add(sphere);

Tetrahedron Geometry

Jako další geometrii si ukážeme TetrahedronGeometry. Nevím jak bych tento tvar popsal, takže se podívejte na interaktivní ukázku v dokumentaci. Jako parametr předáváme poloměr a úroveň detailu.

/* ... */

// TETRAHEDRON GEOMETRY
const tetrahedronGeometry = new THREE.TetrahedronGeometry(0.5, 1);
const tetrahedron = new THREE.Mesh(tetrahedronGeometry, material);
tetrahedron.position.x = 19.5;
scene.add(tetrahedron);

Torus Geometry

Další geometrií, kterou si ukážeme, je TorusGeometry. Slouží k vytvoření tvaru torusu. Jako parametr předáváme poloměr torusu, poloměr trubky, počet radiálních segmentů pro trubku, počet radiálních segmentů a ještě jeden parametr o kterém se můžete dozvědět v dokumentaci.

/* ... */

// TORUS GEOMETRY
const torusGeometry = new THREE.TorusGeometry(0.5, 0.1, 8, 16);
const torus = new THREE.Mesh(torusGeometry, material);
torus.position.x = 21;
scene.add(torus);

Torus Knot Geometry

Další geometrie je na řadě TorusKnotGeometry. Vůbec nevím jak tento tvar popsat, takže se podívejte na interaktivní ukázku do dokumentace. Parametry tu také nebudu popisovat, najdete je v dokumentaci.

Po zkopírování následující kódu vám ve scéně přibyde nový tvar a můžete se na něj podívat.

/* ... */

// TORUS KNOT GEOMETRY
const torusKnotGeometry = new THREE.TorusKnotGeometry(0.5, 0.1);
const torusKnot = new THREE.Mesh(torusKnotGeometry, material);
torusKnot.position.x = 22.5;
scene.add(torusKnot);

Ostatní geometrie

Existuje ještě pár další geometrií, které jsme si zde neukázali. Bylo toho tolik, že již toho určitě musíte mít plné zuby. Možná stačilo ukázat jen pár základních geometrií, ale to už je jedno. Ono je to vlastně všechno podobné, jen je potřeba se vždy podívat do dokumentace, co máme pro danou geometrii předat za parametry. Třídy pro geometrii, které jsme si neukázali, jsou následující:

  • EdgesGeometry - Může být použita jako helper pro zobrazení hran geometrie.
  • WireframeGeometry - Může být použita jako helper pro zobrazení geometrie jako wireframe.
  • ExtrudeGeometry - Vytváří vytlačenou geometrii podle předaného tvaru cesty.
  • ShapeGeometry - Vytváří jednostrannou geometrii podle předaného tvaru cesty.
  • TubeGeometry - Vytváří trubkovou geometrii podle předané cesty.

Vlastní geometrie

Pokud nám typy geometrií, které nám Three.js nabízí nestačí, tak si můžeme vytvořit svoji vlastní. Můžeme k tomu využít třídu BufferGeometry. Můžeme vytvořit její instanci a pomocí metody setAttribute nastavit atributy, které se pošlou při vykreslování do shaderu pro jejich zpracování. To vám začne dávat smysl až si začneme programovat své vlastní shadery. Teď to chápejte tak, že shader přijímá atributy, které mají nějaký název a při vykreslování s nimi pracuje.

Abychom si zkusili vytvořit nějakou vlastní geometrii, tak si vytvoříme jen tak něco náhodně. Vytvoříme spoustu vertexů na náhodných pozicích pomocí for cyklu. Ty potom pomocí metody setAttribute nastavíme jako atribut jménem "position". Když nastavujeme atribut, tak musíme předat název atributu a instanci třídy BufferAttribute. Třída BufferAttribute ukládá data pro atributy spojené s BufferGeometry a umožňuje jejich efektivnější předávání do GPU. Při vytváření BufferAttributu předáváme typové pole a počet položek, které jsou spjaty s jedním vertexem. Neumím to moc vysvětlit, snad to alespoň trochu pochopíte, když se podíváte na následující okomentovanou ukázku. Jak jsem psal, tak vám to pravděpodobně bude jasnější až si budete vytvářet své vlastní shadery. V ukázce si můžete všimnout, že používáme třídu Float32Array. Jedná se o typové pole. Typová pole mají fixní velikost a ukládají jen jeden datový typ. Jsou ale rychlejší než klasická pole.

/* ... */

// vytvoření vlastní geometrie
const nahodnaGeometrie = new THREE.BufferGeometry();

// definování počtu vertexů v geometrii
const pocetVertexu = 100;

// vytvoření pole pozic pro vertexy
// - každý vertex má pro pozici 3 hodnoty (x, y, z)
const polePozic = new Float32Array(pocetVertexu * 3);

// naplnění pole pozic pro vertexy náhodnými hodnotami
for (let i = 0; i < polePozic.length; i++) {
    polePozic[i] = Math.random() - 0.5;
}

// vytvoření buffer attributu (každý vertex má pro pozici 3 hodnoty - x, y, z)
const positionAtribut = new THREE.BufferAttribute(polePozic, 3);
// nastavení position atributu na geometrii
nahodnaGeometrie.setAttribute("position", positionAtribut);

// vytvoření meshe podle vygenerované geometrie (materiál bude zobrazen jako wireframe)
const vlastniMesh = new THREE.Mesh(nahodnaGeometrie, new THREE.MeshBasicMaterial({ wireframe: true }));
vlastniMesh.position.x = -1.5;
// přidání meshe do scény
scene.add(vlastniMesh);

Pokud si kód z předchozí ukázky zkopírujete a umístíte do svého kódu, tak se vám na scéně vygeneruje náhodná geometrie.

Omlouvám se za své vyjadřovací schopnosti, chápu že to nechápete. U tohoto pro mě bylo velmi obtížné to nějak lépe popsat. Pokud vás vytváření vlastní geometrie zajímá, tak se o tom budete muset dozvědět více jinde. Lepší je ale ve většině případů vymodelovat si vlastní geometrii v nějakém 3D modelovacím programu a poté si ji do scény načíst.

Pro tuto část je to konečně vše. Doufám že jste se při jejím čtení moc nenudili. Příští část je o texturách, ta by pro vás mohla být o něco záživnější.