目录
灯光
在Three.js中,灯光(Lights)是模拟光照效果的关键元素,它们能够极大地增强场景的真实感和深度。
环境光(AmbientLight):
javascript
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 白色环境光,强度为0.5
scene.add(ambientLight);
环境光均匀地照亮整个场景,没有方向性,适合为场景提供基础照明。
点光源(PointLight):
javascript
const pointLight = new THREE.PointLight(0xffffff, 1, 100); // 白色,强度1,最大距离100
pointLight.position.set(10, 10, 10); // 设置光源位置
scene.add(pointLight);
点光源从一个点向所有方向发射光线,随着距离增加,光照强度逐渐减弱。
聚光灯(SpotLight):
javascript
const spotLight = new THREE.SpotLight(0xffffff, 1, 100, Math.PI / 4); // 白色,强度1,最大距离100,锥角45度
spotLight.position.set(0, 10, 10);
spotLight.target.position.set(0, 0, 0); // 设置目标位置,灯光指向该点
scene.add(spotLight);
scene.add(spotLight.target); // 注意也要添加目标到场景
聚光灯有方向性,模拟手电筒或舞台灯的效果,具有内锥和外锥,可以设置其方向和焦点。
平行光(DirectionalLight):
javascript
const directionalLight = new THREE.DirectionalLight(0xffffff, 1); // 白色,强度1
directionalLight.position.set(0, 1, 1).normalize(); // 设置光源方向(向量需要归一化)
scene.add(directionalLight);
平行光模拟太阳光,所有光线都视为平行的,没有衰减,常用于户外场景。
半球光(HemisphereLight):
javascript
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 0.5); // 上部颜色(天空),下部颜色(地面),强度
hemisphereLight.position.set(0, 10, 0); // 通常放在场景上方
scene.add(hemisphereLight);
半球光模拟天空和地面的自然光照,上半部分和下半部分可以设置不同颜色。
在使用灯光时,还需要考虑以下几个方面:
- 衰减:某些光源(如点光源和聚光灯)可以设置衰减属性,模拟真实世界中光随距离减弱的现象。
- 阴影:通过设置light.castShadow = true并配置相关阴影属性,可以让灯光投射阴影,增加场景的真实感。
- 强度:灯光的强度可以通过构造函数的第二个参数或.intensity属性调整。
- 颜色:灯光颜色由构造函数的第一个参数确定,使用十六进制颜色值。
平行光阵列(DirectionalLightShadow):
javascript
const dLight = new THREE.DirectionalLight(0xffffff, 1);
dLight.castShadow = true; // 开启阴影
dLight.shadow.mapSize.width = 1024; // 阴影贴图大小
dLight.shadow.mapSize.height = 1024;
dLight.shadow.camera.top = 10;
dLight.shadow.camera.bottom = -10;
dLight.shadow.camera.left = -10;
dLight.shadow.camera.right = 10;
scene.add(dLight);
这种灯光可以用于创建更高质量的阴影,通过调整阴影相机的属性控制阴影的可见区域。
自定义光源(LightProbe):
javascript
const lightProbe = new THREE.LightProbe();
lightProbe.intensity = 1;
lightProbe.environment = cubeEnvMap; // 使用立方体贴图作为环境光源
scene.add(lightProbe);
光源探针用于全局光照计算,可以捕捉环境中的光照信息,适用于环境映射和物理渲染。
光晕效果(LightHalo):
javascript
const halo = new THREE.Halo(light, 100, 100, 100);
halo.color.copy(light.color);
scene.add(halo);
光晕效果通常用于模拟光源周围的光环,不是内置的灯光类型,而是通过第三方库如three-halo实现。
光跟踪器(TrackballControls):
javascript
const controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.update();
虽然这不是灯光类型,但TrackballControls可以帮助你交互式地调整光源的位置和方向,这对于调试和预览光照效果很有帮助。
光强度渐变(Fog):
javascript
scene.fog = new THREE.Fog(0x000000, 1, 100); // 黑色雾,近裁剪平面1,远裁剪平面100
阴影
在Three.js中,阴影的实现涉及阴影投射和阴影接收。阴影投射是指光源能否投射出阴影,而阴影接收则指物体是否能显示投射在其上的阴影。
开启光源的阴影投射:
对于支持阴影的光源类型(如点光源、聚光灯或平行光),需要设置castShadow
属性为true
。
javascript
const light = new THREE.DirectionalLight(0xffffff, 1);
light.castShadow = true;
light.shadow.mapSize.width = 1024; // 阴影贴图大小
light.shadow.mapSize.height = 1024;
// ... 其他光源设置
设置阴影参数:
阴影贴图的大小会影响阴影的质量和性能,可以根据需要调整。
开启阴影接收:
为了使物体能够接收阴影,需要设置其材质的receiveShadow
属性为true
。
javascript
const material = new THREE.MeshStandardMaterial({ color: 0x000000, receiveShadow: true });
const geometry = new THREE.BoxGeometry(1, 1, 1);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
阴影相机和投影矩阵:
阴影的计算需要一个单独的相机,通常由光源对象管理。光源的shadow.camera
属性可以进一步调整,以适应场景的需要。
javascript
light.shadow.camera.left = -10;
light.shadow.camera.right = 10;
light.shadow.camera.top = 10;
light.shadow.camera.bottom = -10;
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 50;
阴影贴图更新:
在每一帧渲染之前,确保阴影贴图被正确更新。
javascript
light.shadow.map.needsUpdate = true;
渲染器设置:
渲染器也需要开启阴影支持。
javascript
const renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 可选,可以使用更平滑的阴影
阴影贴图类型:
默认情况下,Three.js使用PCF(Percentage Closest Filtering)阴影贴图。还可以选择其他方法,如THREE.PCFSoftShadowMap
(软阴影)或THREE.VSMShadowMap
(垂直偏移阴影贴图)。
javascript
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
阴影偏移:
有时阴影可能会出现错位,可以通过调整光源的shadow.bias属性来微调。
javascript
light.shadow.bias = -0.001;
阴影模糊:
通过设置renderer.shadowMapblur
,可以调整阴影边缘的模糊程度。
javascript
renderer.shadowMap.blur = true;
阴影贴图大小:
阴影贴图的大小影响阴影的清晰度。更大的贴图意味着更高的质量,但也会消耗更多的GPU资源。
javascript
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
阴影距离:
可以调整光源的阴影距离,以限制阴影的可见范围。
javascript
light.shadow.camera.far = 50;
阴影深度测试:
默认情况下,Three.js使用深度贴图进行阴影计算。如果场景中存在复杂的几何结构,可以尝试使用深度纹理(Depth Texture)来提高性能。
javascript
light.shadow.map = new THREE.WebGLRenderTarget(1024, 1024, {
format: THREE.DepthFormat,
type: THREE.FloatType,
});
优化阴影:
- 分层阴影:如果场景中有大量物体,可以考虑将物体分组,为每组使用独立的光源和阴影贴图,以减少计算量。
- 阴影可见性:只在需要的时候开启阴影,例如,当相机靠近某个物体时才开启其阴影。
自定义阴影着色器:
如果需要更复杂的阴影效果,可以编写自定义的着色器来处理阴影纹理。
基本动画
在Three.js中,创建基本动画通常涉及以下几个步骤。
创建场景(Scene):
javascript
const scene = new THREE.Scene();
添加相机(Camera):
javascript
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5; // 设置相机位置
创建渲染器(Renderer):
javascript
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
加载模型或几何体(Geometry):
javascript
const geometry = new THREE.BoxGeometry(1, 1, 1); // 创建一个立方体
应用材质(Material):
javascript
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // 绿色材质
创建网格(Mesh):
javascript
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh); // 将网格添加到场景
动画部分:
创建AnimationMixer用于管理动画:
javascript
const mixer = new THREE.AnimationMixer(mesh);
如果你有一个 AnimationClip
,你可以将其添加到mixer中:
javascript
const clip = ...; // 获取或加载 AnimationClip
mixer.clipAction(clip).play(); // 播放动画
更新循环以每帧更新mixer:
javascript
function animate() {
requestAnimationFrame(animate);
mixer.update(delta); // delta是时间差,通常从THREE.Clock获取
renderer.render(scene, camera);
}
animate();
窗口大小改变事件:
为了响应窗口大小变化,通常会调整渲染器的大小:
javascript
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
创建关键帧动画:
Three.js允许你使用关键帧动画来控制对象的属性随时间变化。首先,你需要创建一个KeyframeTrack
,然后用它来构建AnimationClip。
javascript
// 定义关键帧
const times = [0, 1, 2]; // 时间轴上的关键帧时刻
const values = [0, 1, 0]; // 对应的关键帧值,例如旋转角度
const track = new THREE.NumberKeyframeTrack(
'.rotation.x', // 路径,这里是对象的rotation.x属性
times,
values
);
// 创建动画片段
const clip = new THREE.AnimationClip('rotateX', -1, [track]); // -1表示无限循环
应用关键帧动画:
如之前所述,将clip添加到mixer并播放。
javascript
mixer.clipAction(clip).play();
动态更新动画:
如果需要在运行时修改动画,可以使用clipAction的方法。例如,如果你想改变动画的速度,可以这样做:
javascript
mixer.clipAction(clip).setEffectiveTimeScale(2); // 放慢速度
停止或重置动画:
当你想停止或重置动画时,可以使用以下方法:
javascript
mixer.clipAction(clip).stop(); // 停止动画
mixer.clipAction(clip).reset(); // 重置到初始状态
在Three.js中,还可以使用Object3D
的内置方法来创建简单的动画效果,例如平移、旋转或缩放。这里是一个使用Object3D
的translateOnAxis()
、rotateOnAxis()
和scale()
方法的例子:
javascript
function animate(time) {
requestAnimationFrame(animate);
// 平移
mesh.position.x += 0.01;
// 旋转
const axis = new THREE.Vector3(0, 1, 0); // 围绕Y轴旋转
const angle = time * 0.01; // 旋转角度与时间成比例
mesh.rotateOnAxis(axis, angle);
// 缩放
mesh.scale.set(1 + (time % 2) * 0.1, 1 + (time % 2) * 0.1, 1); // 在X和Y轴上交替放大缩小
renderer.render(scene, camera);
}
animate();
在这个例子中,我们没有使用AnimationClip
和AnimationMixer
,而是直接在animate函数中更新了对象的属性。这种方法对于简单的动画效果来说足够了,但如果你需要更复杂的交互或序列动画,那么使用AnimationMixer
会更有优势,因为它提供了更多的控制和混合选项。
Tween.js动画
Tween.js是一个独立的JavaScript库,用于创建平滑的过渡效果,它可以与Three.js结合使用来制作对象的动画。
Tween.js动画基础
javascript
// 引入Tween.js
import { Tween } from 'three/examples/jsm/libs/tween.module.js';
// 创建一个Three.js对象,例如一个立方体
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
scene.add(cube);
// Tween.js动画
const tween = new TWEEN.Tween(cube.position) // 选择要动画化的属性
.to({ x: 5, y: 5, z: 5 }, 2000) // 动画的目标位置,持续时间2秒
.easing(TWEEN.Easing.Quadratic.InOut) // 选择缓动函数,这里是InOut二次方
.onUpdate(() => { // 更新回调,在每一帧时调用
scene.remove(cube);
scene.add(cube); // 重新添加对象以触发渲染
})
.start(); // 开始动画
// 更新循环
function animate() {
requestAnimationFrame(animate);
TWEEN.update(); // 更新Tween.js动画
renderer.render(scene, camera);
}
animate();
在这个例子中:
- 首先,我们创建了一个立方体,并将其添加到场景中。
- 接下来,我们创建了一个
Tween
实例,指定要动画化的对象(这里是立方体的位置)和目标位置。to方法接收一个目标属性的对象和动画的持续时间。 - 我们选择了
Quadratic.InOut
缓动函数,这是一种常见的缓动模式,使得动画在开始和结束时慢速,中间快速。 onUpdate
回调函数会在每一帧动画更新时调用。在这里,我们通过移除并重新添加立方体来强制Three.js重新渲染,因为仅改变对象的位置并不会自动触发渲染。- 最后,我们设置了动画循环,每帧调用
TWEEN.update()
来更新所有活动的Tween对象。 - 请注意,如果你使用的是最新的Three.js版本,可能需要确保
Tween.js
与Three.js的模块系统兼容,因此可能需要导入tween.module.js
而不是旧版的tween.min.js
。
旋转动画(Rotation)
对于旋转动画,我们可以使用Quaternion
来控制对象的旋转。Quaternion
是一种数学概念,用于表示3D空间中的旋转。以下是如何动画化旋转的示例:
javascript
// 初始旋转
const startRotation = new THREE.Quaternion();
// 目标旋转
const endRotation = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI * 2); // 顺时针旋转一周
// Tween.js动画
new TWEEN.Tween(startRotation)
.to(endRotation, 2000) // 持续2秒
.easing(TWEEN.Easing.Linear.None) // 使用线性缓动
.onUpdate(() => {
cube.quaternion.copy(startRotation); // 将新的旋转应用于立方体
scene.remove(cube);
scene.add(cube); // 强制渲染
})
.start();
颜色动画(Color)
动画化颜色可以使用Color
对象。以下是如何改变对象颜色的示例:
javascript
// 初始颜色
const startColor = new THREE.Color(0x00ff00); // 绿色
cube.material.color.copy(startColor);
// 目标颜色
const endColor = new THREE.Color(0xff0000); // 红色
// Tween.js动画
new TWEEN.Tween(startColor)
.to(endColor, 2000) // 持续2秒
.easing(TWEEN.Easing.Linear.None) // 使用线性缓动
.onUpdate(() => {
cube.material.color.copy(startColor); // 将新的颜色应用于材质
scene.remove(cube);
scene.add(cube); // 强制渲染
})
.start();
动画颜色时,需要确保材质支持颜色更改。在这个例子中,我们假设cube
的材质是可变颜色的,如MeshBasicMaterial
或MeshStandardMaterial
。
鼠标与键盘控制
在Three.js中,为了实现鼠标和键盘控制,通常会使用OrbitControls
来处理鼠标的旋转、平移和缩放,以及自定义事件监听器来处理键盘输入。
1. 引入和设置 OrbitControls
首先,确保你的项目中包含了OrbitControls.js
,这通常是Three.js的一个额外文件,位于examples/jsm/controls/OrbitControls.js
。
javascript
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);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建 OrbitControls 实例并关联到相机和渲染器的DOM元素
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼效果,让旋转更加平滑
controls.enablePan = true; // 允许平移
controls.enableZoom = true; // 允许缩放
2. 键盘控制
为了处理键盘输入,可以使用window.addEventListener
来监听键盘按下和释放事件。下面是一个简单的示例,演示如何在按下箭头键时改变立方体的位置:
javascript
let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;
document.addEventListener('keydown', onKeyDown, false);
document.addEventListener('keyup', onKeyUp, false);
function onKeyDown(event) {
switch (event.keyCode) {
case 87: // W
moveForward = true;
break;
case 83: // S
moveBackward = true;
break;
case 65: // A
moveLeft = true;
break;
case 68: // D
moveRight = true;
break;
}
}
function onKeyUp(event) {
switch (event.keyCode) {
case 87: // W
moveForward = false;
break;
case 83: // S
moveBackward = false;
break;
case 65: // A
moveLeft = false;
break;
case 68: // D
moveRight = false;
break;
}
}
// 在渲染循环中应用键盘控制逻辑
function animate() {
requestAnimationFrame(animate);
if (moveForward) camera.position.z -= 1; // 向前移动
if (moveBackward) camera.position.z += 1; // 向后移动
if (moveLeft) camera.position.x -= 1; // 向左移动
if (moveRight) camera.position.x += 1; // 向右移动
controls.update(); // 必须在每次渲染前更新 controls
renderer.render(scene, camera);
}
animate();
这段代码中,onKeyDown
和onKeyUp
函数分别处理键盘按键按下和释放的事件,而animate
函数在每一帧更新时检查这些标志并相应地移动相机。注意,为了使OrbitControls
与自定义的键盘控制协同工作,需要在每次渲染前调用controls.update()
。
3. 平滑移动效果
为了使相机移动更加平滑,我们可以引入Tween.js来动画化相机的位置变化,替代直接修改相机位置。
javascript
import { Tween, Easing } from 'gsap'; // 或者使用 'tween.js',但推荐使用gsap的TweenMax/TimelineMax,功能更强大
function updateCameraPosition(newPosition, duration = 900) {
const currentPos = { x: camera.position.x, y: camera.position.y, z: camera.position.z };
const targetPos = { x: newPosition.x, y: newPosition.y, z: newPosition.z };
new Tween(currentPos)
.to(targetPos, duration)
.easing(Easing.Linear.None)
.onUpdate(() => {
camera.position.set(currentPos.x, currentPos.y, currentPos.z);
})
.start();
}
然后在键盘事件处理中使用这个函数来平滑移动相机:
javascript
if (moveForward) updateCameraPosition(new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z - 5));
// 类似地处理其他方向
4. 限制相机移动范围
为了防止相机超出预设区域,可以在updateCameraPosition函数中添加边界检查:
javascript
function updateCameraPosition(newPosition, duration = 900) {
// 添加边界检查
newPosition.x = Math.max(minX, Math.min(maxX, newPosition.x));
newPosition.y = Math.max(minY, Math.min(maxY, newPosition.y));
newPosition.z = Math.max(minZ, Math.min(maxZ, newPosition.z));
// 剩余代码保持不变
}
5. 处理鼠标滚轮事件
为了处理鼠标滚轮缩放,可以通过监听wheel事件来实现:
javascript
document.addEventListener('wheel', onWheel, { passive: false });
function onWheel(event) {
event.preventDefault(); // 阻止默认的滚动行为
const delta = -event.deltaY * 0.01; // 调整缩放速度
camera.position.z += delta; // 根据滚轮方向调整相机位置
}
6. 综合示例
将以上改进整合,最终的动画循环和事件监听部分可能看起来像这样:
javascript
function animate() {
requestAnimationFrame(animate);
controls.update(); // 更新OrbitControls
renderer.render(scene, camera);
}
function onWheel(event) {
event.preventDefault();
const delta = -event.deltaY * 0.01;
camera.position.z += delta;
// 可能需要更新OrbitControls的缩放参数
}
// 初始化事件监听和开始动画循环
document.addEventListener('keydown', onKeyDown, false);
document.addEventListener('keyup', onKeyUp, false);
document.addEventListener('wheel', onWheel, { passive: false });
animate();
上述代码示例中使用了gsap
库的Tween
和Easing
,这是一个强大的动画库,可以替代原生的Tween.js
,提供更多的动画控制选项。根据个人偏好和项目需求,可以选择适合的动画库。