Урок · Середній рівень · ~40 хв
Three.js · Тіньові карти · PCF · VSM · CSM

Тіні у Three.js — PCF, VSM, CSM та налаштування

Three.js реалізує тіньове відображення (shadow mapping): сцена рендериться з точки зору джерела світла у текстуру глибини, яка потім порівнюється під час основного проходу рендерингу. Цей посібник охоплює увімкнення тіней, вибір типів тіней, усунення тіньового шуму та масштабування на великі сцени за допомогою каскадних тіньових карт.

1Увімкнення тіней для рендерера, джерел світла та об'єктів

Рендеринг тіней потребує трьох прапорців увімкнення — усі три мають бути встановлені:

// 1. Рендерер renderer.shadowMap.enabled = true; // 2. Кожне джерело світла, яке має ВІДКИДАТИ тіні const sun = new THREE.DirectionalLight(0xffffff, 2); sun.castShadow = true; scene.add(sun); // 3. Кожен меш, який ВІДКИДАЄ або ПРИЙМАЄ тіні mesh.castShadow = true; // цей об'єкт відкидає тінь ground.receiveShadow = true; // тіні інших об'єктів видно на цьому // Об'єкти можуть одночасно і ВІДКИДАТИ, і ПРИЙМАТИ mesh.castShadow = true; mesh.receiveShadow = true;
Джерела світла, що відкидають тіні та підтримуються Three.js: DirectionalLight, SpotLight та PointLight. AmbientLight та HemisphereLight не відкидають тіней — вони не мають єдиного напрямку.

2Типи тіньових карт — від BasicShadowMap до VSM

Тип Якість Вартість Найкраще для
BasicShadowMap Жорсткі, зі сходинками Найнижча Налагодження / стилізація
PCFShadowMap М'який PCF-фільтр Низька За замовчуванням; більшість ігор
PCFSoftShadowMap М'якіші, ширші Середня Архітектурна візуалізація
VSMShadowMap Дуже м'які, білінійні Середня+ Відкриті ландшафти
import * as THREE from 'three'; renderer.shadowMap.type = THREE.PCFSoftShadowMap; // за замовчуванням PCFShadowMap // VSM використовує текстуру depth + depth², тож може кешувати м'якість через міпмапи: renderer.shadowMap.type = THREE.VSMShadowMap; // VSM Примітка: також задайте light.shadow.blurSamples для кращої якості sun.shadow.blurSamples = 25;

3Налаштування піраміди видимості тіньової камери

DirectionalLight використовує ортографічну тіньову камеру. Її піраміда видимості має бути якомога щільнішою навколо видимої сцени для найкращої щільності тіньових текселів:

sun.shadow.camera.near = 0.5; sun.shadow.camera.far = 100; sun.shadow.camera.left = -20; sun.shadow.camera.right = 20; sun.shadow.camera.top = 20; sun.shadow.camera.bottom = -20; sun.shadow.camera.updateProjectionMatrix(); // Збільшуємо кількість текселів для деталізації sun.shadow.mapSize.set(2048, 2048); // за замовчуванням 512×512 // Лише степені 2: 512, 1024, 2048, 4096 // Візуалізуємо піраміду видимості камери під час налаштування import { CameraHelper } from 'three'; scene.add(new CameraHelper(sun.shadow.camera));
Удвічі більша піраміда видимості удвічі зменшує щільність тіньових текселів. Надавайте перевагу щільному прилеганню перед великим mapSize — пам'ять текстур обмежена, а карта 4096² використовує у 64× більше пам'яті, ніж 512².

4Усунення тіньового шуму за допомогою bias

Тіньовий шум (смуги самозатінення) виникає тому, що точність порівняння глибини обмежена. Виправте його за допомогою bias та normalBias:

// bias зміщує глибину тіньової карти — відсуває її від поверхні sun.shadow.bias = -0.0001; // від'ємне значення зсуває тінь до джерела світла // normalBias зміщує вздовж нормалі поверхні — краще для викривлених поверхонь sun.shadow.normalBias = 0.05; // Типові початкові значення: // Плоскі поверхні: bias = -0.0001, normalBias = 0 // Викривлені меші: bias = -0.00005, normalBias = 0.02..0.05 // Peter Panning (тінь відривається від основи) означає, що bias завеликий // → зменшуйте, доки тінь не з'єднається знову з об'єктом

5Каскадні тіньові карти (CSM) для великих сцен

CSM розбиває піраміду видимості на ближній/середній/дальній каскади, кожен зі своєю тіньовою картою. Близькі об'єкти отримують більше текселів; віддалені — менше. Доступно через three/examples/jsm:

import { CSM } from 'three/examples/jsm/csm/CSM.js'; const csm = new CSM({ maxFar: camera.far, cascades: 4, // зазвичай 1–8 shadowMapSize: 1024, lightDirection: new THREE.Vector3(1, -1, 1).normalize(), camera: camera, parent: scene, mode: 'practical', // 'uniform' | 'logarithmic' | 'practical' lightIntensity: 1.5, fade: true, // плавний перехід між каскадами }); // Оновлюємо кожен кадр (CSM слідує за камерою) renderer.setAnimationLoop(() => { csm.update(); // переобчислюємо поділ на каскади csm.updateFrustums(); // для рухомих об'єктів / камери renderer.render(scene, camera); }); // Матеріали мають увімкнути ін'єкцію шейдера CSM: csm.setupMaterial(material); // додає define для вибору каскаду CSM
CSM необхідний для відкритих світів чи зовнішніх сцен. Без нього одна тіньова карта має покривати кілометри ландшафту й дає дуже низьку роздільність тіней поблизу камери.

6Поради щодо продуктивності

// Приклад статичної сцени — рендеримо тіні заново лише за потреби renderer.shadowMap.autoUpdate = false; function makeDirty() { renderer.shadowMap.needsUpdate = true; // запускає один повторний рендер тіней } // Викликайте makeDirty() лише коли об'єкти справді рухаються