Threejs 阴影秘籍
约 1244 字大约 4 分钟
2025-02-25
定义
阴影是光线照射到物体上,被物体遮挡后形成的效果。在 Three.js 中,可以通过设置光源的 castShadow 属性来开启阴影效果。
步骤
想要实现阴影效果涉及到以下几个步骤:
- 光源: 开启光源的 castShadow 属性。
- 渲染器: 开启渲染器的允许在场景中使用阴影贴图(shadowMapEnabled)属性。
- 物体: 设置物体的材质是否接收阴影(receiveShadow)属性为 true,表示该物体可以接收阴影。
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 允许渲染器生成阴影
document.body.appendChild(renderer.domElement);
//创建平行光
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(-5, 5, 0);
directionalLight.castShadow = true; // 开启投影
scene.add(directionalLight);
//创建平面
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);
//创建球体
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);
- mapSize: 阴影贴图的大小,默认值为 512。值越大,阴影越清晰,但计算量也越大。较高的值会以计算时间为代价提供更好的阴影质量。
- radius: 将此值设置为大于1的值将模糊阴影的边缘。较高的值会在阴影中产生不必要的条带效果 - 更大的mapSize将允许在这些效果变得可见之前使用更高的值。
透视相机
阴影相机(ShadowCamera)是用于生成阴影贴图的相机。在 Three.js 中,光源的 shadowCamera 属性可以用来设置阴影相机的参数。
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; // 阴影相机最近距离
light.shadow.camera.far = 500; // 阴影相机最远距离
light.shadow.camera.left = -50; // 阴影相机左边界
light.shadow.camera.right = 50; // 阴影相机右边界
light.shadow.camera.top = 50; // 阴影相机上边界
light.shadow.camera.bottom = -50; // 阴影相机下边界
scene.add(light);
示例
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(3, 3, -20);
camera.lookAt(0, 0, 0);
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 允许渲染器生成阴影
document.body.appendChild(renderer.domElement);
//创建环境光
const ambientLight = new THREE.AmbientLight(0x101010);
scene.add(ambientLight);
//创建平行光
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(-5, 5, 0);
directionalLight.castShadow = true; // 开启投影
scene.add(directionalLight);
//创建平行光辅助
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 1);
scene.add(directionalLightHelper);
//创建点光源
const pointLight = new THREE.PointLight(0xffffff, 10, 1000);
pointLight.position.set(-5, 3, 0);
pointLight.castShadow = true; // 开启投影
scene.add(pointLight);
//创建点光源辅助
const pointLightHelper = new THREE.PointLightHelper(pointLight, 1);
scene.add(pointLightHelper);
//创建平面
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);
//创建球体
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);
//创建控制器
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();
//监听窗口大小变化
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
射线
射线(Raycaster)是三维空间中的一条线,它从一点出发,沿着一个方向无限延伸。射线通常用于检测物体之间的碰撞、光线投射等。
构造器
const raycaster = new THREE.Raycaster(origin, direction, near, far);
origin
:射线的起点,通常是一个三维向量。direction
:射线的方向,通常是一个归一化的三维向量。near
:可选参数,表示射线开始的位置,near不能为负值,默认为0。far
:可选参数,表示射线结束的位置,far不能小于near,默认为无穷大。
方法
set(origin, direction)
:设置射线的起点和方向。setFromCamera(coords, camera)
:从屏幕坐标设置射线的起点和方向。intersectObject(object, recursive)
:检测射线与物体之间的碰撞,返回一个包含碰撞结果的数组。intersectObjects(objects, recursive)
:检测射线与多个物体之间的碰撞,返回一个包含碰撞结果的数组。
//创建射线
const raycaster = new THREE.Raycaster();
// 设置射线的起点和方向
const origin = new THREE.Vector3(-6, 0, 0);
const direction = new THREE.Vector3(1, 0, 0).normalize(); // 将方向向量标准化
raycaster.set(origin, direction);
const meshs = [sphere1, sphere2, sphere3];
function animate() {
// 使用raycaster对象与meshs数组中的对象进行相交检测
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数组中的每个对象
//由于射线是一瞬间完成的,所以需要将每个对象的材质颜色重置
for (const mesh of meshs) {
// 将每个对象的材质颜色设置为白色
mesh.material.color.set(0xffffff);
}
// 遍历相交检测结果数组
for (const intersectObject of intersects) {
// 将相交的对象的材质颜色设置为红色
intersectObject.object.material.color.set(0xff0000);
}
requestAnimationFrame(animate);
renderer.render(scene, camera);
}