定義
陰影是光線照射到物體上,被物體遮擋后形成的效果。在 Three.js 中,可以通過設(shè)置光源的 castShadow 屬性來開啟陰影效果。
步驟
想要實(shí)現(xiàn)陰影效果涉及到以下幾個(gè)步驟:
- 光源: 開啟光源的 castShadow 屬性。
- 渲染器: 開啟渲染器的允許在場景中使用陰影貼圖(shadowMapEnabled)屬性。
- 物體: 設(shè)置物體的材質(zhì)是否接收陰影(receiveShadow)屬性為 true,表示該物體可以接收陰影。
// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 允許渲染器生成陰影
document.body.appendChild(renderer.domElement);
//創(chuàng)建平行光
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(-5, 5, 0);
directionalLight.castShadow = true; // 開啟投影
scene.add(directionalLight);
//創(chuàng)建平面
const plane = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshStandardMaterial());
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true; // 接收陰影
scene.add(plane);
//創(chuàng)建球體
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 32),
new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
sphere.position.set(0, 1, 0);
sphere.castShadow = true; // 開啟投影
scene.add(sphere);

shadow.png
- mapSize: 陰影貼圖的大小,默認(rèn)值為 512。值越大,陰影越清晰,但計(jì)算量也越大。較高的值會(huì)以計(jì)算時(shí)間為代價(jià)提供更好的陰影質(zhì)量。
- radius: 將此值設(shè)置為大于1的值將模糊陰影的邊緣。較高的值會(huì)在陰影中產(chǎn)生不必要的條帶效果 - 更大的mapSize將允許在這些效果變得可見之前使用更高的值。
透視相機(jī)
陰影相機(jī)(ShadowCamera)是用于生成陰影貼圖的相機(jī)。在 Three.js 中,光源的 shadowCamera 屬性可以用來設(shè)置陰影相機(jī)的參數(shù)。
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 10, 7.5);
light.castShadow = true;
light.shadow.mapSize.width = 1024; // 陰影貼圖的寬度
light.shadow.mapSize.height = 1024; // 陰影貼圖的高度
light.shadow.camera.near = 0.5; // 陰影相機(jī)最近距離
light.shadow.camera.far = 500; // 陰影相機(jī)最遠(yuǎn)距離
light.shadow.camera.left = -50; // 陰影相機(jī)左邊界
light.shadow.camera.right = 50; // 陰影相機(jī)右邊界
light.shadow.camera.top = 50; // 陰影相機(jī)上邊界
light.shadow.camera.bottom = -50; // 陰影相機(jī)下邊界
scene.add(light);

shadowCamera.png
示例
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 創(chuàng)建場景
const scene = new THREE.Scene();
// 創(chuàng)建相機(jī)
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(3, 3, -20);
camera.lookAt(0, 0, 0);
// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 允許渲染器生成陰影
document.body.appendChild(renderer.domElement);
//創(chuàng)建環(huán)境光
const ambientLight = new THREE.AmbientLight(0x101010);
scene.add(ambientLight);
//創(chuàng)建平行光
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(-5, 5, 0);
directionalLight.castShadow = true; // 開啟投影
scene.add(directionalLight);
//創(chuàng)建平行光輔助
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 1);
scene.add(directionalLightHelper);
//創(chuàng)建點(diǎn)光源
const pointLight = new THREE.PointLight(0xffffff, 10, 1000);
pointLight.position.set(-5, 3, 0);
pointLight.castShadow = true; // 開啟投影
scene.add(pointLight);
//創(chuàng)建點(diǎn)光源輔助
const pointLightHelper = new THREE.PointLightHelper(pointLight, 1);
scene.add(pointLightHelper);
//創(chuàng)建平面
const plane = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshStandardMaterial());
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true; // 接收陰影
scene.add(plane);
//創(chuàng)建球體
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 32),
new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
sphere.position.set(0, 1, 0);
sphere.castShadow = true; // 開啟投影
scene.add(sphere);
//創(chuàng)建控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 啟用阻尼效果
controls.enableDamping = true;
let angle = 0
function animate() {
angle += 0.01
pointLight.position.x = Math.sin(angle) * 3
pointLight.position.z = Math.cos(angle) * 3
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
//監(jiān)聽窗口大小變化
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});

shadow1.gif
射線
射線(Raycaster)是三維空間中的一條線,它從一點(diǎn)出發(fā),沿著一個(gè)方向無限延伸。射線通常用于檢測物體之間的碰撞、光線投射等。
構(gòu)造器
const raycaster = new THREE.Raycaster(origin, direction, near, far);
-
origin:射線的起點(diǎn),通常是一個(gè)三維向量。 -
direction:射線的方向,通常是一個(gè)歸一化的三維向量。 -
near:可選參數(shù),表示射線開始的位置,near不能為負(fù)值,默認(rèn)為0。 -
far:可選參數(shù),表示射線結(jié)束的位置,far不能小于near,默認(rèn)為無窮大。
方法
-
set(origin, direction):設(shè)置射線的起點(diǎn)和方向。 -
setFromCamera(coords, camera):從屏幕坐標(biāo)設(shè)置射線的起點(diǎn)和方向。 -
intersectObject(object, recursive):檢測射線與物體之間的碰撞,返回一個(gè)包含碰撞結(jié)果的數(shù)組。 -
intersectObjects(objects, recursive):檢測射線與多個(gè)物體之間的碰撞,返回一個(gè)包含碰撞結(jié)果的數(shù)組。
//創(chuàng)建射線
const raycaster = new THREE.Raycaster();
// 設(shè)置射線的起點(diǎn)和方向
const origin = new THREE.Vector3(-6, 0, 0);
const direction = new THREE.Vector3(1, 0, 0).normalize(); // 將方向向量標(biāo)準(zhǔn)化
raycaster.set(origin, direction);
const meshs = [sphere1, sphere2, sphere3];
function animate() {
// 使用raycaster對象與meshs數(shù)組中的對象進(jìn)行相交檢測
const intersects = raycaster.intersectObjects(meshs);
sphere1.position.y = 4 * Math.sin(Date.now() * 0.001);
sphere2.position.y = 4 * Math.sin(Date.now() * 0.001 + (2 * Math.PI) / 3);
sphere3.position.y = 4 * Math.sin(Date.now() * 0.001 + (4 * Math.PI) / 3);
// 遍歷meshs數(shù)組中的每個(gè)對象
//由于射線是一瞬間完成的,所以需要將每個(gè)對象的材質(zhì)顏色重置
for (const mesh of meshs) {
// 將每個(gè)對象的材質(zhì)顏色設(shè)置為白色
mesh.material.color.set(0xffffff);
}
// 遍歷相交檢測結(jié)果數(shù)組
for (const intersectObject of intersects) {
// 將相交的對象的材質(zhì)顏色設(shè)置為紅色
intersectObject.object.material.color.set(0xff0000);
}
requestAnimationFrame(animate);
renderer.render(scene, camera);
}

Raycaster.gif