Skip navigation

Árnyékolás

Működése

Igen látványos, bár nagyon számításigényes funkció a tárgyak által vetett árnyék generálása. A Three.js az úgynevezett árnyéktérképes algoritmust használja.

A gyorsabb működés végett nekünk kell jeleznünk a forráskódban, hogy mely fényforrások fényei vetnek árnyékot és melyek nem (ez az alapértelmezés), valamint mely tárgyak vetnek árnyékot, és mely tárgyak felszínen jelenik meg árnyék. Ha mindent bekapcsolunk, sok tárgy esetén akadozhat az animáció.

A sebességet növelhetjük az árnyéktérkép méretének csökkentésével, a minőség rovására.

Vigyázzunk!

Az árnyékoló kódrészt a 2015. októberi r73 kiadással jelentősen átalakították. A Learning Three.js (2. kiadás) könyv, és nagyon sok interneten található kódrészlet a régebbi kódbázishoz tartozik, így azok nem mindig fognak megfelelően működni!

Használata

Az árnyékolás használatához az alábbi lépésekre van szükség.

A renderer-nél kapcsoljuk be az árnyéktérkép generálást:

renderer.shadowMap.enabled = true;

Ha szeretnénk, hogy egy tárgy vessen árnyékot, akkor kapcsoljuk ezt be a castShadow kapcsolóval. Ha szeretnénk árnyékot megjeleníteni egy tárgy felszínén, akkor a receiveShadow kapcsolót kell beállítani. Példaprogramunkban egy kockát világítunk meg, amely vet árnyékot, viszont rajta árnyék nem jelenik meg. Egy objektum természetesen vethet árnyékot, és ugyanakkor rajta is jelenhet meg árnyék.

geometry = new THREE.BoxGeometry( 4, 4, 4 );
material = new THREE.MeshLambertMaterial( { color: 0xffffff, wireframe: false } );
cubeMesh = new THREE.Mesh( geometry, material );
cubeMesh.castShadow = true;
cubeMesh.receiveShadow = false;
scene.add( cubeMesh );

Azon tárgyak esetén, amelyekre árnyékot szeretnénk vetíteni, Lambert vagy Phong anyagot kell választani. Példánkban egy síkot modellezünk, amit a kocka alá pozicionálunk.

planeGeometry = new THREE.PlaneGeometry( 30, 30, 30, 30 );
let material2 = new THREE.MeshPhongMaterial( { color: 0xffffff } );
planeMesh = new THREE.Mesh( planeGeometry, material2 );
planeMesh.rotation.x = -1.0 * Math.PI / 2.0;
planeMesh.position.y = -8;
planeMesh.receiveShadow = true;
planeMesh.castShadow = false;
scene.add( planeMesh );

Az árnyékot akkor látjuk jól, ha a kamera a fényforráshoz és a tárgyakhoz képest megfelelő helyen van. Példánkban a kamerát oldal irányból a kockára irányítjuk.

camera = new THREE.PerspectiveCamera( 75, aspectRatio, 0.1, 1000 );
camera.position.z = 30;
camera.lookAt( cubeMesh.position );

A következő lépés a fényforrás definiálása és elhelyezése a térben. A castShadow-t engedélyezzük, valamint figyeljünk arra, hogy a fényforrás hatása elérjen azokig a tárgyakig, amelyekre az árnyék vetül (distance)!

let sLight = new THREE.SpotLight( 0x00ffff, 1 );
sLight.position.set( 0, 15, 0 );
sLight.angle = Math.PI / 6;
sLight.target = cubeMesh;
sLight.penumbra = 0.8;
sLight.distance = 80;
sLight.castShadow = true;
scene.add( sLight );

A fényforrás paramétereinek jobb átláthatósága végett hozzáadunk egy segéd geometriát.

let spotLightHelper = new THREE.SpotLightHelper( sLight );
scene.add( spotLightHelper );

Árnyék megjelenésének javítása

A Three.js alapértelmezésként az árnyéktérképes módszert alkalmazza. Ennek lényege, hogy az árnyékhatás képét egy bittérképen számítja ki. Ha ennek a mérete a tárgyak felszínméretéhez képest kicsi, akkor darabos lesz az árnyék széle. Ezen javíthatunk, ha nagyobb árnyéktérkép méretet adunk meg.

Figyeljünk arra, hogy a méret kettőnek egész kitevős hatványa legyen! Az alapméret 512x512. Figyeljünk arra, hogy túl nagy méret megadása esetén már esetleg nem fog elférni a GPU memóriájában! A nagyobb méret emellett még jóval több számolást is igényel! Gyengébb hardver esetén gyorsíthatunk kisebb méret megadásával, viszont a látvány romlani fog.

A méretet a fényforrás létrehozásakor adhatjuk meg annak shadow.mapsize.width és shadow.mapsize.height attribútumaival.

let sLight = new THREE.SpotLight( 0xffffff, 1 );
sLight.position.set( 20, 10, 3);
sLight.angle = Math.PI / 3;
sLight.target = boxMesh;
sLight.castShadow = true;
sLight.shadow.mapSize.width = 2048;
sLight.shadow.mapSize.height = 2048;
scene.add( sLight );

Példaprogramok

  • Az oldal alján megjelenő demó elérhető külső ablakban megjelenítve is. A kamera az egérrel interaktívan körbemozgatható, zoomolható!
  • Egy egyszerű, de nagyon látványos példaprogram érhető el a hivatalos Three.js példák között webgl_shadowmap_pointlight.html néven. Itt is változtatható a kamera elhelyezkedése.

Megjegyzés

Az árnyéktérképes módszer lényege, hogy minden fényforrás pozícióba egy kamera kerül elhelyezésre, ami síkra vetíti a színtérben lévő síkidomokat, így állapítva meg a láthatóságot. Az irányfény használatakor párhuzamos vetítésű kamerát használ a Three.js, ami esetében be kell állítanunk azt a tértartományt, amelyet figyelembe akarunk venni. Az alapbeállítás esetenként túl kis térfogat lehet!

A javasolt megoldás ilyen esetben:

const d = 100;

dLight.shadow.camera.left = -d;
dLight.shadow.camera.right = d;
dLight.shadow.camera.top = d;
dLight.shadow.camera.bottom = -d;

A d paraméter értékvel szabályozhatjuk az árnyékolás távolságtartományát.