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 і викликайте
renderer.shadowMap.needsUpdate = true лише коли об'єкти
рухаються.
Обмежуйте кількість активних тіньових джерел світла — кожне тіньове джерело — це
повний рендер сцени на GPU. Тримайте їх щонайбільше 1–2, якщо не використовуєте
оптимізовані для мобільних прийоми.
Використовуйте менший mapSize для точкових джерел —
PointLight рендерить 6 тіньових карт (по одній на грань куба).
Навіть 512×512 = 6 проходів рендерингу тіней.
Вимикайте тіні на малих/віддалених об'єктах — установіть
obj.castShadow = false для об'єктів, що ніколи не відкидають
видимої тіні.
VSM з половинною точністю (half-float) — Three.js за замовчуванням використовує RGB-пакування
для VSM. Вмикайте
renderer.shadowMap.type = THREE.VSMShadowMap лише якщо
вам потрібні дуже м'які тіні, придатні для розмиття.
// Приклад статичної сцени — рендеримо тіні заново лише за потреби
renderer.shadowMap.autoUpdate = false; function makeDirty() {
renderer.shadowMap.needsUpdate = true; // запускає один повторний
рендер тіней } // Викликайте makeDirty() лише коли об'єкти справді рухаються