THREE 学习记录
这里项目搭建的打包工具使用的不是 webpack
也不是 vite
更不是 gulp
,而是 parcel
一个极速零配置 web 应用打包工具。
首先需要全局安装这个工具
$ npm install -g parcel-bundler
搭建项目环境
新建一个项目文件夹,然后初始化项目目录
两种初始化项目的方式,一种是开发依赖安装 parcel-bundler
,一种是开发依赖安装 parcel
。
$ npm init -y
$ npm install parcel-bundler -D
$ npm install three
然后修改 package.json
文件的脚本
{
"scripts": {
"dev": "parcel src/index.html",
"build": "parcel build src/index.html"
}
}
然后再根目录新建若干文件:
- node_modules
- src
- assets
- css
- style.css
- images
- main
- main.js
- index.html
- package.json
然后在 index.html 文件中写入:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./assets/css/style.css">
<title>Three</title>
</head>
<body>
</body>
<script src="./main/main.js"></script>
</html>
然后运行 npm run dev
这个时候项目回启动一个本地服务:http://localhost:1234/ 并且在根目录会生成一个 dist 文件夹——打包后的文件。
然后安装 gsap
和 dat.gui
$ npm install gsap dat.gui
开始一个简单的3D效果
制作一个简单的3D动画效果,需要5部分组成
- 场景,类似于 canvas 画布
- 相机,类似于人的视角
- 几何物体,三维空间的物体
- 控制器,三维空间物体的控制
- 渲染器,顾名思义让3D动画在视图中显示的工具
声明一个场景对象
// main/maim.js
import * as THREE from 'three'
const scene = new THREE.Scene()
声明一个相机
/**
* 参数1:相机的可视区域夹角度数
* 参数2:相机的宽高比
* 参数3:相机的近景距离
* 参数4:相机的远景距离
*/
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000)
// 把相机添加到场景当中
scene.add(camera)
声明一个几何体
// 创建一个几何体
const cubeGeometry = new THREE.BoxGeometry()
// 设置几何体的材质(基础网格材质)
const cubMaterial = new THREE.MeshBasicMaterial({ color: 0xffff000 })
// 根据几何体和材质创建一个物体
const cube = new THREE.Mesh(cubeGeometry, cubMaterial)
// 将几何体添加到场景中
scene.add(cube)
声明一个控制器
// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement)
controls.update()
设置阻尼器
// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement)
// 设置阻尼器,让控制器更有真实效果,但是必须要在动画循环中调用 update()
controls.enableDamping = true
function animation () {
controls.update()
// 进入下一帧的时候触发
requestAnimationFrame(animation)
renderer.render(scene, camera)
}
声明一个渲染器
// 初始化渲染器
const renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight)
// 将渲染的canvas内容添加到body中
document.body.appendChild(renderer.domElement)
// 使用渲染器将相机和场景渲染进来
renderer.render(scene, camera)
让物体动起来
修改几何体的位置
// 修改几何物体的位置,两种方法,一中调用方法,一种修改属性值
cube.position.set(5, 0, 0)
cube.position.x = 5
让物体沿着X轴水平移动
function animation () {
cube.position.x += 0.05
if (cube.position.x === 5) {
// 物体位置回到原点
cube.position.x = 0
}
// 进入下一帧的时候触发
requestAnimationFrame(animation)
renderer.render(scene, camera)
}
使用补间动画工具来处理动画(Gasp)
import gsap from 'gsap'
// 设置动画
var animation_1 = gsap.to(cube.position, {
x: 5, // 沿着X轴移动的距离
duration: 5, // 动画持续时间
ease: 'power1.inOut', // 动画撒贝尔曲线
repeat: 2, // 动画重复次数,值为-1时为无限循环
yoyo: true, // 往返的运动
delay: 2, // 延迟时间
onStart: () => {
console.log('动画开始')
},
onComplete: () => {
console.log('动画完成')
}
})
// 双击让动画停下来
window.addEventListener('dblclick', () => {
// 判断动画是否是激活状态
if (animation_1.isActive()) {
// 暂停动画
animation_1.pause()
} else {
// 恢复动画
animation_1.resume()
}
})
通过 Clock 时钟来跟踪时间、处理时间
const clock = new THREE.Clock()
// 获取时钟运行总时长
let dateTime = clock.getDelta()
通过用户操作界面更改物体属性(dat.gui)
import * as dat from 'dat.gui'
const gui = new dat.GUI()
添加物体水平移动的用户可操作项
gui.add(cube.position, 'x')
.min(0)
.max(5)
.step(0.01)
.name('水平移动')
.onChange(value => {
console.log('物体水平移动的距离:' + value)
})
.onFinishChange(value => {
console.log('完成改变时的物体水平移动的距离:' + value)
})
添加改变物体颜色的用户可操作项
const params = {
color: '#FFFFFF'
}
gui.addColor(params, 'color').name('改变颜色')
.onChange(value => {
console.log('物体改变的颜色是:' + value)
cube.material.color.set(value)
})
添加物体是否显示的用户可操作项
gui.add(cube, 'visible').name('是否显示')
将多个用户操作项归纳到一个折叠框中
const folder = gui.addFolder('设置物体的属性')
const params = {
color: '#ffff00',
fn: () => {
gsap.to(cube.position, { x: 5, duration: 2, yoyo: true, repeat: -1 })
}
}
folder.add(cube.material, 'wireframe')
folder.addColor(params, 'color').name('改变颜色')
folder.add(params, 'fn').name('开启动画')
纹理贴图
// 导入纹理
const textureLoader = new THREE.TextureLoader()
// 图片的位置在 /dist/texture/bg1.jpg
const doorColorTexture = textureLoader.load('./texture/bg1.jpg')
// 贴图偏移量设置
doorColorTexture.offset.x = 0.5
doorColorTexture.offset.set(0.5, 0)
// 设置贴图旋转
// 设置贴图的旋转中心
doorColorTexture.center.set(0.5,0.5)
// 设置贴图旋转 45deg
doorColorTexture.rotation = Math.PI / 4
// 设置贴图重复
// 水平重复2次,垂直重复3次
doorColorTexture.repeat.set(2, 3)
// 设置水平方向贴图重复的模式(贴边重复)
doorColorTexture.wrapS = THREE.RepeatWarppering
// 设置垂直方向贴图重复的模式(贴边重复)
doorColorTexture.wrapT = THREE.MirrepeatWarppering
// 设置几何体的材质(基础网格材质)
const cubMaterial = new THREE.MeshBasicMaterial({
// 这里的 color 颜色会叠加到纹理图上
// color: 0xffff000,
map: doorColorTexture
})
设置透明材质
// 导入纹理
const textureLoader = new THREE.TextureLoader()
// 图片的位置在 /dist/texture/alpha-bg.jpg
// 图片中黑色区域会被渲染成透明状,白色区域会被渲染成不透明,灰色区域会被渲染成半透明状
const doorColorTexture = textureLoader.load('./texture/alpha-bg.jpg')
// 设置几何体的材质(基础网格材质)
const cubMaterial = new THREE.MeshBasicMaterial({
map: doorColorTexture,
alphaMap: doorAlphaTexture,
transparent: true // 设置为 true 透明贴图才会起效
})
添加平面
// 添加平面
const basicColorTexture = textureLoader.load('./texture/bg4.jpg')
const plane = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1,1),
// cubMaterial
new THREE.MeshBasicMaterial({
map: basicColorTexture
})
)
plane.position.set(3, 0, 0)
scene.add(plane)
AO 环境遮挡
const cubeGeometry = new THREE.BoxBufferGeometry(1,1,1)
// 这是一张白色背景,黑色线条的图,白色在渲染时,会被忽略,黑色线条被作为环境贴图的描边效果,如阴影
const doorAotexture = textureLoader.load('./texture/bg5.jpg')
const cubMaterial = new THREE.MeshBasicMaterial({
map: doorColorTexture,
alphaMap: doorAlphaTexture,
transparent: true,
aoMap: doorAotexture
})
cubeGeometry.setAttribute(
'uv2',
new THREE.BufferAttribute(cubeGeometry.attributes.uv.array, 2)
)
贴图加载管理
单个贴图加载
const textureLoader = new THREE.TextureLoader()
const doorColorTexture = textureLoader.load('./texture/bg1.jpg')
textureLoader.onLoad = () => {
console.log('加载完成')
}
贴图加载包管理器批量处理
第一种方式:
const manager = new THREE.LoadingManager()
manager.onStart = function (url, itemsLoaded, itemsTotal) {
console.log('开始加载文件:', { url, itemsLoaded, itemsTotal })
}
manager.onLoad = function () {
console.log('加载完成')
}
manager.onProgress = function (url, itemsLoaded, itemsTotal) {
console.log({ url, itemsLoaded, itemsTotal })
}
manager.onError = function (url) {
console.error('加载失败')
console.error(url)
}
第二种方式:
const event = {}
event.onLoad = function () {
console.log('图片加载完成')
}
event.onProgress = function (url, num, total) {
console.log('图片加载完成:', url)
console.log('图片加载进度:', num)
console.log('图片总数:', total)
console.log('图片加载进度的百分比:', ((num / total) * 100).toFixed(2) + '%')
}
event.onError = function () {
console.log('图片加载失败')
}
const manager = new THREE.LoadingManager(
event.onLoad,
event.onProgress,
event.onError
)
// 把包加载管理器传递给贴图加载器,然后每次加载贴图资源的时候都会触发包加载管理器
const textureLoader = new THREE.TextureLoader(manager)
const doorColorTexture = textureLoader.load('./texture/bg1.jpg')
物理光线
环境光线
// 环境光线(从四面八方照射过来的光线)
const light = new THREE.AmbientLight(0xffffff, 0.1)
scene.add(light)
平行光线
// 平行光线(相当于太阳光照射的光线)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5)
directionalLight.position.set(0, 10, 10)
scene.add(directionalLight)
设置置换贴图和贴图厚度
const cubeGeometry = new THREE.BoxBufferGeometry(1,1,1, 100, 100, 100)
// 导入置换贴图
const doorHeightTexture = textureLoader.load('./texture/bg_5.jpg')
// 设置材质
const cubeMaterial = new THREE.MeshBasicMaterial({
map: doorColorTexture,
alphaMap: doorAlphaTexture,
transparent: true,
aoMap: doorAotexture,
aoMapIntensity: 1,
displacementMap: doorHeightTexture // 置换贴图
displacementScale: 0.1, // 置换贴图厚度
})
设置置换贴图粗糙度和光滑度
// 设置材质
const cubeMaterial = new THREE.MeshBasicMaterial({
map: doorColorTexture,
alphaMap: doorAlphaTexture,
transparent: true,
aoMap: doorAotexture,
aoMapIntensity: 1,
displacementMap: doorHeightTexture // 门面贴图
displacementScale: 0.1, // 门面厚度
roughness: 0, // 粗糙度
})
给门上的铆钉设置贴图
// 加载贴图
const door_6_texture = textureLoader.load('./texture/bg_6.jpg')
// 设置材质
const cubeMaterial = new THREE.MeshBasicMaterial({
map: doorColorTexture,
alphaMap: doorAlphaTexture,
transparent: true,
aoMap: doorAotexture,
aoMapIntensity: 1,
displacementMap: doorHeightTexture // 门面贴图
displacementScale: 0.1, // 门面厚度
roughness: 0, // 粗糙度
roughnessMap: door_6_texture, // 铆钉贴图
})
设置金属度效果和金属厚度效果
// 金属贴图
const door_7_texture = textureLoader.load('./texture/bg_7.jpg')
// 设置材质
const cubeMaterial = new THREE.MeshBasicMaterial({
map: doorColorTexture,
alphaMap: doorAlphaTexture,
transparent: true,
aoMap: doorAotexture,
aoMapIntensity: 1,
displacementMap: doorHeightTexture // 门面贴图
displacementScale: 0.1, // 门面厚度
roughness: 1, // 粗糙度
roughnessMap: door_6_texture, // 铆钉贴图
metalness: 1, // 金属度
metalnessMap: door_7_texture, // 铆钉金属贴图
})
设置门面的法线贴图和凹凸效果
// 导入法线贴图
const door_8_texture = textureLoader.load('./texture/bg_8.jpg')
// 设置材质
const cubeMaterial = new THREE.MeshBasicMaterial({
map: doorColorTexture,
alphaMap: doorAlphaTexture,
transparent: true,
aoMap: doorAotexture,
aoMapIntensity: 1,
displacementMap: doorHeightTexture // 门面贴图
displacementScale: 0.1, // 门面厚度
roughness: 1, // 粗糙度
roughnessMap: door_6_texture, // 铆钉贴图
metalness: 1, // 金属度
metalnessMap: door_7_texture, // 铆钉金属贴图
normalMap: door_8_texture, // 门面法线贴图
})