-
场景(Scene):场景是 Three.js 中所有对象的容器,包括灯光、相机和几何体等。
-
相机(Camera):Three.js 提供了几种类型的相机,包括透视相机(PerspectiveCamera)和正交相机(OrthographicCamera)。相机决定了观察场景的视角。
-
几何体(Geometry):几何体定义了 3D 对象的形状,可以是简单的形状如立方体、球体或更复杂的自定义形状。
-
网格(Mesh):网格是几何体和材质的组合,用于在场景中显示 3D 对象。
-
材质(Material):材质定义了网格的外观,可以是基本的材质如基本材质(MeshBasicMaterial)或更高级的材质如着色器材质(ShaderMaterial)。
-
灯光(Light):Three.js 支持多种类型的灯光,包括环境光(AmbientLight)、点光源(PointLight)、聚光灯(SpotLight)和定向光(DirectionalLight)。
-
渲染器(Renderer):Three.js 使用 WebGLRenderer 来渲染场景。渲染器负责将场景中的所有对象转换为浏览器可以理解的格式。
-
动画(Animation):Three.js 允许你通过 JavaScript 来控制对象的位置、旋转和缩放,从而创建动画效果。
-
纹理(Texture):纹理可以应用于材质上,给 3D 对象添加更丰富的视觉效果。
-
控制(Controls):Three.js 提供了多种控制类,如 OrbitControls,允许用户通过鼠标或触摸操作来浏览场景。
-
物理引擎(Physics):虽然 Three.js 本身不包含物理引擎,但可以与像 Cannon.js 或 Ammo.js 这样的物理引擎库结合使用,来模拟真实世界的物理效果。
-
扩展和插件:Three.js 有一个活跃的社区,提供了许多扩展和插件,用于添加额外的功能,如粒子系统、动画库等。
import * as Three from 'three';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
import './main.css';
let scene,camera,render,cube,geometry,material,controls;
// 创建射线
const raycaster = new Three.Raycaster();
// 创建二维平面
const pointer = new Three.Vector2();
// 鼠标移动
function onPointerMove(event){
pointer.x = (event.clientX/window.innerWidth)*2-1;
pointer.y = -(event.clientY/window.innerHeight)*2+1;
raycaster.setFromCamera(pointer,camera);
const intersects = raycaster.intersectObjects(scene.children);
if(intersects.length>0){
intersects[0].object.material.color.set(0xff0000);
}
}
window.addEventListener("pointermove",onPointerMove);// 鼠标移动事件
// 点击屏幕时判断是否光线命中物体 点击几何体使其旋转
window.addEventListener("click",function(){
const intersects = raycaster.intersectObjects([cube]);
if(intersects.length>0){
cube.rotation.x += 0.1;
cube.rotation.y += 0.1;
cube.rotation.z += 0.1;
}
})
// 初始化渲染器
function initRenderer(){
render = new Three.WebGLRenderer();
render.setSize(window.innerWidth,window.innerHeight);
document.body.appendChild(render.domElement);
}
// 初始化场景
function initScene(){
scene = new Three.Scene();
// 设置背景色
render.setClearColor("#ccc");
// 设置坐标轴
const axesHelper = new Three.AxesHelper(100);
scene.add(axesHelper);
// 添加光源 平行光
const directionalLight = new Three.DirectionalLight("lightblue");
// 设置光源位置
directionalLight.position.set(0,30,0);
scene.add(directionalLight);
// 添加光源 环境光
// const ambientLight = new Three.AmbientLight("lightblue");
// ambientLight.position.set(40,0,0);
// scene.add(ambientLight);
// 添加光源 点光源
// const pointLight = new Three.PointLight("lightblue");
// pointLight.position.set(0,30,0);
// scene.add(pointLight);
// 添加光源 聚光灯
// const spotLight = new Three.SpotLight("red");
// spotLight.position.set(30,0,0);
// scene.add(spotLight);
}
// 初始化相机
function initCamera(){
camera = new Three.PerspectiveCamera(45,window.innerWidth/window.innerHeight,1,1000);
camera.position.z = 10;
// 加入滚轮控制器
//controls = new Three.OrbitControls(camera,render.domElement);
controls = new TrackballControls(camera,render.domElement);
}
// 初始化几何体
function initGeometry(){
geometry = new Three.BoxGeometry(2,2,2);
// 创建材质
// 使用指定材质才能看见光源效果
// material = new Three.MeshBasicMaterial({color:(125, 182, 191)});
material = new Three.MeshBasicMaterial({color:(125, 182, 191)});
// 创建纹理
const texture = new Three.TextureLoader().load('https://threejs.org/examples/textures/crate.gif');
// 纹理和材质结合
material = new Three.MeshPhongMaterial({map:texture,side:Three.DoubleSide});
// 材质和几何体结合
cube = new Three.Mesh(geometry,material);
scene.add(cube);
}
// 创建动画旋转立方体
function animate(){
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
cube.rotation.z += 0.01;
render.render(scene,camera);
controls.update();
render.render(scene,camera);
requestAnimationFrame(animate);
}
function init(){
initRenderer();
initScene();
initCamera();
initGeometry();
animate();
}
init();
// 车模型:灯光一切调整好终极版本
import * as THREE from 'three'
import { BoxGeometry, DoubleSide } from 'three';
import { Mesh } from 'three';
import { AxesHelper } from 'three';
import { TextureLoader } from 'three';
import { GridHelper } from 'three';
import { MeshBasicMaterial } from 'three';
import { WebGLRenderer } from 'three';
import { PerspectiveCamera } from 'three';
import { Scene } from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import pkq from './public/pkq1.jpg'
import Lamborghini from './public/Lamborghini.glb'
import { AmbientLight } from 'three';
import { PlaneGeometry } from 'three';
import { MeshPhysicalMaterial } from 'three';
import { SpotLight } from 'three';
import { CylinderGeometry } from 'three';
import GUI from 'lil-gui';
import { Vector2 } from 'three';
import { Raycaster } from 'three';
import messi from './public/messi.JPG'
import { RectAreaLightHelper } from 'three/examples/jsm/helpers/RectAreaLightHelper'
import { RectAreaLightUniformsLib } from 'three/examples/jsm/lights/RectAreaLightUniformsLib.js';
const TWEEN = require('@tweenjs/tween.js')
let scene, camera, renderer, controls, mesh;
let doors = []
let carStatus;
// 车身材质
let bodyMaterial = new THREE.MeshPhysicalMaterial({
color: "#6e2121",
metalness: 1,
roughness: 0.5,
clearcoat: 1.0,
clearcoatRoughness: 0.03,
});
// 玻璃材质
let glassMaterial = new THREE.MeshPhysicalMaterial({
color: "#793e3e",
metalness: 0.25,
roughness: 0,
transmission: 1.0 //透光性.transmission属性可以让一些很薄的透明表面,例如玻璃,变得更真实一些。
});
// 初始化场景
function initScene() {
scene = new Scene()
RectAreaLightUniformsLib.init();
// scene.add(new AxesHelper(3))
}
function initCamera() {
camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(4.25, 1.4, -4.5);
}
function initRenderer() {
renderer = new WebGLRenderer({
antialias: true
})
renderer.setSize(window.innerWidth, window.innerHeight)
// 支持阴影
renderer.shadowMap.enabled = true
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
document.body.appendChild(renderer.domElement)
}
function loadCarModal() {
new GLTFLoader().load(Lamborghini, function (gltf) {
console.log(gltf)
const carModel = gltf.scene
carModel.rotation.y = Math.PI
carModel.traverse(obj => {
if (obj.name === 'Object_103' || obj.name == 'Object_64' || obj.name == 'Object_77') {
// 车身
obj.material = bodyMaterial
} else if (obj.name === 'Object_90') {
// 玻璃
obj.material = glassMaterial
} else if (obj.name === 'Empty001_16' || obj.name === 'Empty002_20') {
// 门
doors.push(obj)
} else {
}
obj.castShadow = true;
})
scene.add(carModel)
})
}
function initAmbientLight() {
var ambientLight = new AmbientLight('#fff', 0.5)
scene.add(ambientLight)
}
function initGripHelper() {
let grid = new GridHelper(20, 40, 'red', 0xffffff)
grid.material.opacity = 0.2
grid.material.transparent = true
scene.add(grid)
}
function initFloor() {
const floorGeometry = new PlaneGeometry(20, 20)
const material = new MeshPhysicalMaterial({
side: DoubleSide,
color: 0x808080,
metalness: 0,
roughness: 0.1
})
const floorMesh = new Mesh(floorGeometry, material)
floorMesh.rotation.x = Math.PI / 2
floorMesh.receiveShadow = true;
scene.add(floorMesh)
}
function initSpotLight() {
// 添加头顶聚光灯
const bigSpotLight = new SpotLight("#ffffff", 0.5);
bigSpotLight.angle = Math.PI / 8; //散射角度,跟水平线的家教
bigSpotLight.penumbra = 0.2; // 聚光锥的半影衰减百分比
bigSpotLight.decay = 2; // 纵向:沿着光照距离的衰减量。
bigSpotLight.distance = 30;
bigSpotLight.shadow.radius = 10;
// 阴影映射宽度,阴影映射高度
bigSpotLight.shadow.mapSize.set(4096, 4096);
bigSpotLight.position.set(-5, 10, 1);
// 光照射的方向
bigSpotLight.target.position.set(0, 0, 0);
bigSpotLight.castShadow = true;
// bigSpotLight.map = bigTexture
scene.add(bigSpotLight);
}
function initCylinder() {
const geometry = new CylinderGeometry(10, 10, 20, 20)
const material = new MeshPhysicalMaterial({
color: 0x6c6c6c,
side: DoubleSide
})
const cylinder = new Mesh(geometry, material)
scene.add(cylinder)
}
function initController() {
controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.maxDistance = 9
controls.minDistance = 1
controls.minPolarAngle = 0
controls.maxPolarAngle = 80 / 360 * 2 * Math.PI
// controls.target.set(0, 0.5, 0)
}
function initGUI() {
var obj = {
bodyColor: '#6e2121',
glassColor: '#aaaaaa',
carOpen,
carClose,
carIn,
carOut
};
const gui = new GUI();
gui.addColor(obj, "bodyColor").name('车身颜色').onChange((value) => {
bodyMaterial.color.set(value)
})
gui.addColor(obj, "glassColor").name('玻璃颜色').onChange((value) => {
glassMaterial.color.set(value)
})
gui.add(obj, "carOpen").name('打开车门')
gui.add(obj, "carClose").name('关门车门')
gui.add(obj, "carIn").name('车内视角')
gui.add(obj, "carOut").name('车外视角')
}
function carOpen() {
carStatus = 'open'
for (let i = 0; i < doors.length; i++) {
setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[i])
}
}
function carClose() {
carStatus = 'close'
for (let i = 0; i < doors.length; i++) {
setAnimationDoor({ x: Math.PI / 3 }, { x: 0 }, doors[i])
}
}
function carIn() {
setAnimationCamera({ cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 }, { cx: -0.27, cy: 0.83, cz: 0.60, ox: 0, oy: 0.5, oz: -3 });
}
function carOut() {
setAnimationCamera({ cx: -0.27, cy: 0.83, cz: 0.6, ox: 0, oy: 0.5, oz: -3 }, { cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 });
}
function setAnimationDoor(start, end, mesh) {
const tween = new TWEEN.Tween(start).to(end, 1000).easing(TWEEN.Easing.Quadratic.Out)
tween.onUpdate((that) => {
mesh.rotation.x = that.x
})
tween.start()
}
function setAnimationCamera(start, end) {
const tween = new TWEEN.Tween(start).to(end, 3000).easing(TWEEN.Easing.Quadratic.Out)
tween.onUpdate((that) => {
// camera.postition 和 controls.target 一起使用
camera.position.set(that.cx, that.cy, that.cz)
controls.target.set(that.ox, that.oy, that.oz)
})
tween.start()
}
function createSpotlight(color) {
const newObj = new THREE.SpotLight(color, 2);
newObj.castShadow = true;
newObj.angle = Math.PI / 6;;
newObj.penumbra = 0.2;
newObj.decay = 2;
newObj.distance = 50;
return newObj;
}
function initMessiLight() {
const spotLight1 = createSpotlight('#ffffff');
const texture = new TextureLoader().load(messi)
spotLight1.position.set(0, 3, 0);
spotLight1.target.position.set(-10, 3, 10)
spotLight1.map = texture
lightHelper1 = new THREE.SpotLightHelper(spotLight1);
scene.add(spotLight1);
}
function initMutilColor() {
//创建三色光源
rectLight1 = new THREE.RectAreaLight(0xff0000, 50, 1, 10);
rectLight1.position.set(15, 10, 15);
rectLight1.rotation.x = -Math.PI / 2
rectLight1.rotation.z = -Math.PI / 4
scene.add(rectLight1);
rectLight2 = new THREE.RectAreaLight(0x00ff00, 50, 1, 10);
rectLight2.position.set(13, 10, 13);
rectLight2.rotation.x = -Math.PI / 2
rectLight2.rotation.z = -Math.PI / 4
scene.add(rectLight2);
rectLight3 = new THREE.RectAreaLight(0x0000ff, 50, 1, 10);
rectLight3.position.set(11, 10, 11);
rectLight3.rotation.x = -Math.PI / 2
rectLight3.rotation.z = -Math.PI / 4
scene.add(rectLight3);
scene.add(new RectAreaLightHelper(rectLight1));
scene.add(new RectAreaLightHelper(rectLight2));
scene.add(new RectAreaLightHelper(rectLight3));
startColorAnim()
}
function startColorAnim() {
const carTween = new TWEEN.Tween({ x: -5 }).to({ x: 25 }, 2000).easing(TWEEN.Easing.Quadratic.Out);
carTween.onUpdate(function (that) {
rectLight1.position.set(15 - that.x, 10, 15 - that.x)
rectLight2.position.set(13 - that.x, 10, 13 - that.x)
rectLight3.position.set(11 - that.x, 10, 11 - that.x)
});
carTween.onComplete(function (that) {
rectLight1.position.set(-15, 10, 15);
rectLight2.position.set(-13, 10, 13);
rectLight3.position.set(-11, 10, 11);
rectLight1.rotation.z = Math.PI / 4
rectLight2.rotation.z = Math.PI / 4
rectLight3.rotation.z = Math.PI / 4
})
carTween.repeat(10)
const carTween2 = new TWEEN.Tween({ x: -5 }).to({ x: 25 }, 2000).easing(TWEEN.Easing.Quadratic.Out);
carTween2.onUpdate(function (that) {
rectLight1.position.set(-15 + that.x, 10, 15 - that.x)
rectLight2.position.set(-13 + that.x, 10, 13 - that.x)
rectLight3.position.set(-11 + that.x, 10, 11 - that.x)
});
carTween2.onComplete(function (that) {
rectLight1.position.set(15, 10, 15);
rectLight2.position.set(13, 10, 13);
rectLight3.position.set(11, 10, 11);
rectLight1.rotation.z = - Math.PI / 4
rectLight2.rotation.z = - Math.PI / 4
rectLight3.rotation.z = - Math.PI / 4
})
carTween.start();
}
function init() {
initScene()
initCamera()
initRenderer()
loadCarModal()
initAmbientLight()
initFloor()
initSpotLight()
initMessiLight()
initCylinder()
initController()
initGUI()
initMutilColor()
}
init()
function render(time) {
// if (mesh.position.x > 3) {
// } else {
// mesh.position.x += 0.01
// }
renderer.render(scene, camera)
requestAnimationFrame(render)
TWEEN.update(time)
controls.update()
}
render()
window.addEventListener('resize', function () {
// camera
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
// renderer
renderer.setSize(window.innerWidth, window.innerHeight)
})
window.addEventListener('click', onPointClick);
function onPointClick(event) {
let pointer = {}
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;
var vector = new Vector2(pointer.x, pointer.y)
var raycaster = new Raycaster()
raycaster.setFromCamera(vector, camera)
let intersects = raycaster.intersectObjects(scene.children);
intersects.forEach((item) => {
if (item.object.name === 'Object_64' || item.object.name === 'Object_77') {
if (!carStatus || carStatus === 'close') {
carOpen()
} else {
carClose()
}
console.log(intersects)
}
})
}