Skip navigation

Első példaprogram

Példaprogramok beüzemelése

Első lépésként a jegyzet példatárát töltsük le és a importáljuk a WebStorm fejlesztőkörnyezetbe.

  • A példatár a jegyzet nyitóoldalán található linken érhető el egy ZIP fájlként. Ezt töltsük le. Tartalma időnként frissül, így érdemes időről időre ellenőrizni.
  • Indítsuk el a WebStorm-ot és készítsünk egy üres projektet. A lépések részletes leírása itt található.
  • Az üres projekt mappába csomagoljuk ki a ZIP fájl tartalmát.
  • A WebStorm Project nézetében megjelennek a programjaink.

Modellezési terület elhelyezése a weboldalon

HTML 5 vászon elem

A 3D modellezésünk a HTML 5 vászon (canvas) elemén belül jelenik meg. A vászon egy normál HTML 5 elem, így méretét, elhelyezkedését, megjelenési stílusát a szokásos módon adhatjuk meg a HTML kódban. Kétféle stratégiát követhetünk.

  • Ha a weboldal elsődleges célja a modellezés, akkor célszerű a rendelkezésre álló teljes ablakterületet felhasználni. Ebben az esetben fontos, hogy reagáljunk az ablakméret megváltozására!
  • Ha a 3D modellezés mint interaktív ábra jelenik meg egy szöveget, képeket tartalmazó oldalon, akkor célszerű rögzített méretű vászon objektumot létrehozni. Rögzített vászonméret esetén előfordulhat, hogy kis ablakméret esetén nem fog a vászon minden része egyszerre látszani.

A vászon objektum létrehozásának kétféle módja

  • A HTML kódban csak egy névvel ellátott helyfoglaló elemet (pl. div) helyezünk el (vagy akár azt sem), a canvas objektumot dinamikusan hozzuk létre és adjuk hozzá a DOM-hoz a JavaScript kódunkban. Ez a teljes ablakos megjelenítés esetén szokásos.
  • A HTML kódban definiálunk névvel ellátott vásznat, a JavaScript kódból ehhez csatlakozunk.

Először a teljes ablakos megoldást nézzük meg, amin keresztül megismerkedünk a Three.js modellezés alap objektumaival.

Teljes ablakos modellezés

Először a teljes forráskódot megadjuk, utána részekre bontva mutatjuk be, melyik rész mit és hogyan csinál. A program futásának eredménye megtekinthető külső ablakban (01_01_c_ThreeJsHello_importmap.html).

<!DOCTYPE html>
<html lang="hu">

<head>
<meta charset=utf-8>
<title>Three.js Hello app</title>
<style>
body { margin: 0; overflow: hidden; }

canvas { width: 100%; height: 100% }
</style>
</head>

<body>

<script async src="./dist/es-module-shims.js"></script>
<script type="importmap">
{

"imports": {
"three": "./js-r154/build/three.module.js"
}
}
</script>

<script type="module">
import * as THREE from 'three';

// Globális változók
let WIDTH, HEIGHT, aspectRatio;
let renderer;
let scene, camera;
let geometry, material, mesh;

init();

// Csak egyszeri állókép rajzolása
// render();

// Animáció indítása
animate();

function init() {

// Böngésző ablakméret lekérése és méretarány számítása
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
aspectRatio = WIDTH / HEIGHT;

// Renderer létrehozása és DOM-hoz adása
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( WIDTH, HEIGHT );
renderer.setClearColor( 0x000000 );
document.body.appendChild( renderer.domElement );

// Színtér létrehozása
scene = new THREE.Scene();

// Kamera létrehozása és vetítési paramétereinek beállítása
camera = new THREE.PerspectiveCamera( 75, aspectRatio, 0.1, 1000 );
camera.position.z = 15;
camera.lookAt( scene.position.x, scene.position.y, scene.position.z );

// 3D felszínháló létrehozása: geometria és anyag összerendelése
geometry = new THREE.SphereGeometry( 8, 50, 30);
// geometry = new THREE.BoxGeometry( 10, 10, 10 );
material = new THREE.MeshBasicMaterial( { color: 0x00ff00, wireframe: true } );
mesh = new THREE.Mesh( geometry, material );
mesh.rotation.z = 0.2;

// Tárgy színtérhez adása
scene.add( mesh );

// Az ablak későbbi átméretezése esetén visszahívható függvény megadása
window.addEventListener( 'resize', handleWindowResize, false );
}


function handleWindowResize() {
// Az ablak átméretezése esetén a kamera vetítési paraméterek újraszámolása
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
// A konzolra kiírt szöveget a böngészpben is megnézhetjük:
// CTRL + Shift + i
// Console fülre kattintás
// A hibaüzenetek is itt láthatók (ha vannak)
console.log( 'WIDTH=' + WIDTH + '; HEIGHT=' + HEIGHT );
renderer.setSize( WIDTH, HEIGHT );
aspectRatio = WIDTH / HEIGHT;
camera.aspect = aspectRatio;
camera.updateProjectionMatrix();

render();
}


function animate() {
// Újabb képkocka rajzolásának kérése.
// Maximálisan 60 FPS-t biztosít a rendszer.
requestAnimationFrame( animate );

// Objektum elforgatási paraméterének módosítása
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;

// Új képkocka rajzolása
render();
}


function render() {
// 3D -> 2D vetített kép kiszámítása.
// scene 3D színtér képe a camera kamera szemszögéből.
renderer.render( scene, camera );
}


</script>
</body>
</html>

Fejléc

A fejlécben beállítjuk az ablak nevét, valamint megadjuk a body és a canvas elemek stílusát (keret nélküli, teljes ablakterület).

<head>
<meta charset=utf-8>
<title>Three.js Hello app</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100% }
</style>
</head>

Törzs

Az oldal törzse jelen esetben csak JavaScript kódokat fog tartalmazni, nincsenek külön HTML elemek.

Először elkészítjük az importmap szekciót, amiben most csak a three modul kerül definiálásra. (az importmap magyarázatát lásd az előző szekcióban.) Figyeljünk az elérési útvonalra! A jegyzetben az egyszerűség kedvéért ./js/ néven hivatkozunk rá, a példatárban a js után megadásra kerül meg az aktuálisan használt verzió száma is (pl. js-r154)!

Ezután a saját programunk script tag-jének megadása következik, ahol beillesztjük a three néven megadott modul definícióit a THREE névtér alá. Ezután következhetnek majd a saját programunk kódsorai.

<body>

<script async src="./dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js"
}
}
</script>

<script type="module">
import * as THREE from 'three';

// Ide kerül a saját kódunk.
</script>

</body>

Globális változók

A szkriptünkben globális változók fogják a modellező objektumokat és a modellezést vezérlő számértékeket tárolni.

let WIDTH, HEIGHT, aspectRatio;
let renderer;

let scene, camera;
let geometry, material, mesh;

Programunk vezérlése

A JavaScript modulok esetén nincs belépő függvény, az értelmezés a szkript elejéről indul. Innen tudjuk a függvényeinket hívni.

  • Példaprogramunkban először az init() függvényt hívjuk, amely felépíti a modellezéshez szükséges objektumokat.
  • A render() függvényünk hívásával kirajzolhatjuk a modellezés eredményét. A modellezés egyszer fog megtörténni. Ez jó megközelítés, ha statikus, vagy ritkán, külső behatásra változó modellünk van. A példaprogramunkban ez ki van kommentározva, mert folyamatos animációt kérünk.
  • Az animate() függvényünk folyamatos modellezést indít. Programjainkban jellemzően ezt használjuk.
init();

// Csak egyszeri állókép rajzolása
// render();

// Animáció indítása
animate();

Ablak méret és méretarány számítás

Az init() függvényünkben inicializáljuk a modellezést. Első lépésként lekérjük a böngészőtől a tartalom megjelenítésre rendelkezésre álló területét méretét (képpontban kapjuk meg) és kiszámítjuk a szélesség és magasság arányát. Ez utóbbi a kamera vetítési paraméterek számításához lesz szükséges (lásd később).

// Böngésző ablakméret lekérése és méretarány számítása
HEIGHT = window.innerHeight;

WIDTH = window.innerWidth;
aspectRatio = WIDTH / HEIGHT;

Renderelő létrehozása és az oldalhoz adása

A renderelő az összekötő kapocs a 2D vászon és a 3D modellterünk között: egy olyan objektum, amely gondoskodik a 2D területen megjelenítendő grafika elkészítéséről és megjelenítéséről. A renderer egy Three.js objektum, amit példányosítunk. A példányosítás során létrejön az eredményt megjeleníteni képes, új canvas objektumpéldány is.

Megjegyezzük, hogy a Three.js háromféle renderelő típussal rendelkezik, mi ezek közül a legtöbb funkcionalitást biztosító WebGLRenderer-t használjuk. Választhatunk még CSS-3D, valamint HTML 5 vászon megjelenítőt is. Ezekben viszont nem minden Three.js funkció támogatott!

A renderer létrehozásakor paraméterként CSS stílus paramétereket adhatunk át a kapcsos zárójelen belül. Jelen esetben a vonalak szebb megjelenítését kérjük az antialias bekapcsolásával. Meg kell adnunk a 2D rajzterület méretét (képpontban kapjuk meg a böngészőtől), valamint beállíthatjuk a háttér törlőszínét (setClearColor). A színbeállítási lehetőségekről itt olvashatunk bővebben.

Végül az új vászon objektumot hozzáadjuk a DOM-hoz.

// Renderer létrehozása és DOM-hoz adása
renderer = new THREE.WebGLRenderer( { antialias: true } );

renderer.setSize( WIDTH, HEIGHT );
renderer.setClearColor( 0x000000 );
document.body.appendChild( renderer.domElement );

Színtér és a kamera létrehozása

A színtér (Scene) reprezentálja a 3D világot, az ehhez hozzáadott objektumok jelenhetnek meg az eredményképen.

A kamera (Camera) paraméterei határozzák meg, hogy a 3D világ hogyan kerüljön levetítésre a 2D eredményképre. A Three.js kétféle kamerát biztosít: párhuzamos és perspektív vetítést végrehajtót. Ezek közül a perspektív az, amely az emberi látáshoz közelebb van, a párhuzamos inkább a mérnöki alkalmazásokban fontosabb. A kamerának megadhatjuk a térbeli pozícióját, a vetítés irányát (hová néz), valamint a kamera felfelé mutató irányát (ugyanis a vetítési tengely körül is el lehet forgatni). Ezekkel részletesebben később foglalkozunk.

Jelen példában egy 75 fokos látószögű, a 2D vászon méretarányának megfelelő perspektív kamerát készítünk. Meg kell adni még, hogy a kamerához képest milyen távolságtartományban (közeli - távoli) lévő objektumok jelenjenek meg. A kamera alapesetben az origóban van, a példában a Z koordinátát 15-re módosítjuk. A kamera alapértelmezetten a Z-tengely negatív irányába néz, vagyis az origó környékén elhelyezett objektumokat fogja látni a kamera. A lookAt() függvénnyel tudjuk a kamera célpontját beállítani. A jelen esetben ez a színtér origója lesz.

// Színtér létrehozása
scene = new THREE.Scene();


// Kamera létrehozása és vetítési paramétereinek beállítása
camera = new THREE.PerspectiveCamera( 75, aspectRatio, 0.1, 1000 );

camera.position.z = 15;
camera.lookAt( scene.position );

Felszínháló létrehozása és színtérhez adása

A színtérben megjelenő tárgyaknak meg kell adnunk a geometriáját, valamint az anyagtulajdonságait.

  • A modellezendő tárgyak geometriáját, felszínét háromszög síkidomokkal adhatjuk meg, görbülettel rendelkező objektumok esetén ezekkel közelíthetjük. A Three.js számos beépített geometria objektummal rendelkezik. Ezek közül itt a gömböt (SphereGeometry) választjuk. Paraméterként meg kell adnunk a gömb sugarát, valamint szélességi és hosszúsági irányok menti felbontás számát. Ezek szabályozzák a felszínt közelítő háromszögek számát és méretét. Kevés háromszög esetén "darabos" lesz a modell. Túl nagyra sem érdemes állítani, mert egy ponton túl javulást már nem fogunk látni, másrészt a háromszögek számának növekedésével a modellezés lelassulhat.
  • A tárgyak anyagjellemzője definiálja, hogyan reagál a felszín a megvilágításra. A tárgyak színét az anyagjellemző és a fényforrás kölcsönhatása határozza meg. A bevezető példában nem használunk fényforrást, így olyan anyag kell, amely fénytől függetlenül is biztosít színt a tárgynak. Ez a MeshBasicMaterial, amelynek zöld színt és drótvázas megjelenést állítunk be.

A geometria és az anyag ismeretében létrehozhatjuk a felszínhálót (Mesh), amit a színtérhez adhatunk. A példában meg egy Z-tengely menti elforgatást is megadunk az objektumhoz.

Próbáljuk ki a SphereGeometry helyet a BoxGeometry használatát a sor eleji kommentárjelek (//) felcserélésével!

// 3D felszínháló létrehozása: geometria és anyag összerendelése
geometry = new THREE.SphereGeometry( 8, 50, 30);
//geometry = new THREE.BoxGeometry( 10, 10, 10 );
material = new THREE.MeshBasicMaterial( { color: 0x00ff00, wireframe: true } );
mesh = new THREE.Mesh( geometry, material );
mesh.rotation.z = 0.2;

// Tárgy színtérhez adása
scene.add( mesh );

Eseménykezelő beállítások

Az inicializálás zárásaként megadjuk, hogy a böngészőablak átméretezésekor melyik függvényünk hívódjon meg (handleWindowResize).

// Az ablak későbbi átméretezése esetén visszahívható függvény megadása
window.addEventListener( 'resize', handleWindowResize, false );

Ablakátméretezés kezelése

Ha teljes ablakos módban modellezünk, akkor fel kell készülnünk arra, hogy a felhasználó módosítja az ablak méretét. Ez a méretarány változásával jár(hat). Mivel a méretet a renderelőnek, a méretarányt pedig a kamera paramétereként is megadtuk korábban, annak változását követni kell, egyébként torz megjelenítést fogunk látni!

Az init() függvényben megadtuk, hogy méretváltozás esetén a handleWindowResize függvényünk hívódjon meg. Itt lekérjük az aktuális méreteket, amit tájékoztatásul ki is írunk a konzolra, beállítjuk a renderelőnek, kiszámítjuk az új arányt, és frissítjük a kamera kapcsolódó paraméterét. A kamera objektumot értesíteni kell a vetítési paraméterek változásáról (updateProjectionMatrix())!

A változások figyelembe vétele érdekében meghívjuk a render() függvényünket.

function handleWindowResize() {
// Az ablak átméretezése esetén a kamera vetítési paraméterek újraszámolása
HEIGHT = window.innerHeight;

WIDTH = window.innerWidth;
console.log( 'WIDTH=' + WIDTH + '; HEIGHT=' + HEIGHT );

renderer.setSize( WIDTH, HEIGHT );
aspectRatio = WIDTH / HEIGHT;
camera.aspect = aspectRatio;
camera.updateProjectionMatrix();

render();
}

Renderelés, animáció

A render() függvényünk gondoskodik a színtér objektumainak a kiválasztott kamera szerinti 2D vetítéséről és az eredmény megjelenítéséről. Ez egy képkockát jelenít meg.

Amennyiben a színterünk dinamikus, időben változó, gondoskodnunk kell a folyamatos frissítésről. Erre célszerű egy különálló animate() függvényt írni, amely első hívása után a JavaScript requestAnimationFrame() függvényét használva folyamatosan újrahívódik. Paraméterként az animáló függvényünk nevét kell átadni, ezt a hívást az animáló függvényben elhelyezve biztosítja a folyamatos működést.

A függvény használatával a rendszer gondoskodni fog a megfelelő sebességű időzítésről. A dokumentáció szerint ez jellemzően 60 képkocka másodpercenként, illetve amennyiben a böngésző támogatja, a képernyő frissítési frekvenciájának megfelelő érték (pl. 75 vagy 100 is lehet). Vagyis ennyiszer hívódik meg az animate() függvényünk. Ha a modellünk túl komplex, túl sok számítást igényel a megjelenítése, a megjelenítési ráta természetesen ennél kisebbé is válhat, "szaggatni" fog az eredmény. Ha a frissítési időközt nem vesszük figyelembe, akkor a számítógépenként esetlegesen különböző frissítési időköz miatt más sebességű animációkat fogunk látni! A probléma megoldását később tárgyaljuk, egyelőre ezzel nem foglalkozunk a modellezéseink során.

Amennyiben az oldal háttérbe kerül, energiatakarékossági és rendszer hatékonysági okokból az animáció szüneteltetésre kerül(het) (ne rajzoljunk, ha úgysem látszik), ezt a viselkedést a böngésző szabályozza.

Az animate() függvényben lehetőségünk van a modellezést befolyásoló globális változóink figyelembe vételére, a tárgyak, kamerák paramétereinek módosítására. Jelen példában a tárgyat reprezentáló mesh X- és Y-tengelyek menti elforgatási paramétereit módosítjuk.

Az animate() a render() függvényünket hívja az új képkocka megjelenítéséhez.

A render() függvényünkben a scene színtér camera kamera szerinti 2D vetített képét a renderelő objektum render függvényhívásával állíthatjuk elő.

function animate() {
// Újabb képkocka rajzolásának kérése.
// Maximálisan 60 FPS-t biztosít a rendszer.
requestAnimationFrame( animate );

// Objektum elforgatási paraméterének módosítása
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;

// Új képkocka rajzolása
render();
}

function render() {

// 3D -> 2D vetített kép kiszámítása.
// scene 3D színtér képe a camera kamera szemszögéből.
renderer.render( scene, camera );

}

Feladatok

  • Módosítsuk a gömb modellezés paramétereit! Elsősorban a felosztás hatását vizsgáljuk. Figyeljük meg a gömbfelszínt közelítő háromszögeket!
  • Gömb helyett modellezünk kockát!
  • Próbáljuk ki a böngésző nyomkövető funkcióit! (CTRL + Shift + i, majd Console fül)!