WEBGL学习笔记
一.基础入门
使用版本r0.153,下载压缩包后执行
npm start
,即可查看本地教程文档。创建项目:
npm init vite@latest
下载three:
npm install three
```js
// 导入three js
import * as THREE from ‘three’1
2
3
4
5. ```js
// 创建场景
const scene = new THREE.Scene();```js
// 创建透视相机
const camera = new THREE.PerspectiveCamera(45, // 视角window.innerWidth / window.innerHeight, // 宽高比 0.1, // 近平面 1000 //远平面
);
// 设置相机位置
camera.position.z = 5 // z轴 正对眼睛位置
camera.position.x = 2
camera.position.y = 2
camera.lookAt(0, 0, 0) // 相机看向原点 默认就是原点1
2
3
4
5
6
7. ```js
// 创建渲染器
let renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)```js
// 添加世界坐标辅助器
let axesHelper = new THREE.AxesHelper(5); // 线段长度. 默认为 1.
scene.add(axesHelper)1
2
3
4
5
6
7
8
9
10
9. ```js
// 添加轨道控制器
let controls = new OrbitControls(camera, renderer.domElement);
// 设置带阻尼的惯性
controls.enableDamping = true
// 设置阻尼系数
controls.dampingFactor = 0.05
// 设置自动旋转
controls.autoRotate = true```js
// 渲染函数
function animate() {controls.update() requestAnimationFrame(animate) { // 旋转 cube.rotation.x += 0.01 cube.rotation.y += 0.01 // 渲染 renderer.render(scene, camera) }
}
animate()
1
2
3
4
11. ```js
// 设置物体坐标
cube.position.set(3, 0, 0)```js
// 设置立方体的放大
// cube.scale.set(2, 2, 2) // 放大两倍
parentCube.scale.set(2,2,2) // 父元素放大子元素一起放大1
2
3
4
5
13. ```js
// 控制绕着x轴旋转
cube.rotation.x = Math.PI / 4 // 旋转45° (局部旋转会叠加父元素旋转 90°)
parentCube.rotation.x=Math.PI / 4```js
// 监听窗口变化
window.addEventListener(“resize”, () => {// 重置渲染器宽高比 renderer.setSize(window.innerWidth, window.innerHeight) // 重置相机宽高比 camera.aspect = window.innerWidth / window.innerHeight // 更新相机投影矩阵 camera.updateProjectionMatrix()
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
15. ```js
// 点击按钮事件实现全屏
let btn = document.createElement("button"); // 创建按钮元素
btn.innerHTML = "点击全屏"
// 设置按钮样式
btn.style.position = "absolute";
btn.style.top = '10px'
btn.style.left = '10px'
btn.style.zindex = '999' //数值越大显示在最上层
btn.onclick = function () {
// 全屏
renderer.domElement.requestFullscreen();
}
document.body.appendChild(btn)```js
// 使用GUI实现全屏
import {GUI} from “three/addons/libs/lil-gui.module.min.js”;
let eventObj = {// 全屏 fullscreen: function () { document.body.requestFullscreen(); }, // 退出全屏 exitFullscreen: function () { document.exitFullscreen(); }
}
// 创建GUI对象
let gui = new GUI();
// 添加按钮
gui.add(eventObj,’fullscreen’).name = “全屏”;
gui.add(eventObj,’exitFullscreen’).name = “退出全屏”;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
17. ```js
// 线框材质
parentMaterial.wireframe = true
// 控制立方体位置
// gui.add(cube.position, 'x', -5, 5).name('立方体x轴位置')
// gui.add(cube.position, 'x').min(-10).max(10).step(1).name('立方体x轴位置')
let folder = gui.addFolder('立方体位置');
folder.add(cube.position, 'x')
.min(-10)
.max(10)
.step(1)
.name('立方体x轴位置')
.onChange((val) => {
console.log('x轴位置:', val)
});
folder.add(cube.position, 'y')
.min(-10).max(10)
.step(1)
.name('立方体y轴位置')
.onFinishChange((val) => {
console.log('y轴位置:', val)
});
folder.add(cube.position, 'z').min(-10).max(10).step(1).name('立方体z轴位置');
//控制线框材质变化 单选框
gui.add(parentMaterial, 'wireframe').name('父元素线框模式')
// 控制立方体颜色
let colorParams = {
cubeColor: '#0x00ff00'
}
gui.addColor(colorParams, 'cubeColor')
.name('立方体颜色')
.onChange((val) => {
cube.material.color.set(val)
})
```js
// 创建几何体 所有的面都是由三角形构成的
let geometry = new THREE.BufferGeometry();
// 创建顶点数据 顶点是有序的 每三个为一个顶点 逆时针为正面
const vertices = new Float32Array([-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, -1.0, -1.0, 0.0
]);// 产生六个顶点
// 创建顶点属性
geometry.setAttribute(‘position’, new THREE.BufferAttribute(vertices, 3))
// 创建材质
const material = new THREE.MeshBasicMaterial({color: 0x00ff00, wireframe: true, // side: THREE.DoubleSide // 两面都可以看到
});
console.log(geometry); // count:6 有6个顶点// 使用索引绘制共用顶点 正方形四个顶点
const geometry2 = new THREE.BufferGeometry();
const vertices2 = new Float32Array([-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0,
]); // 平面正方形
// 创建顶点属性
geometry2.setAttribute(‘position’, new THREE.BufferAttribute(vertices2, 3))
// 创建索引
const indices = new Uint16Array([0, 1, 2, 2, 3, 0]);
// 创建索引属性
geometry2.setIndex(new THREE.BufferAttribute(indices, 1))
console.log(geometry2); // count:4 有4顶点
// 创建网格
const mesh = new THREE.Mesh(geometry2, material);
scene.add(mesh)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
19. ```js
// 创建索引属性
geometry2.setIndex(new THREE.BufferAttribute(indices, 1))
// 设置两个顶点组 形成两个材质
geometry2.addGroup(0, 3, 0) // 顶点索引位置 索引0开始,数量3
geometry2.addGroup(3, 3, 1) // 索引3开始(正方形的第四个点),数量3
console.log(geometry2); // count:4 有4顶点
// 创建网格
const mesh = new THREE.Mesh(geometry2, [material, material2]);
scene.add(mesh)
// 创建几合体
let cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
// // 创建材质
let material01 = new THREE.MeshBasicMaterial({color: 0x00ff00}); //
let material02 = new THREE.MeshBasicMaterial({color: 'blue'}); //
let material03 = new THREE.MeshBasicMaterial({color: 'red'}); //
let material04 = new THREE.MeshBasicMaterial({color: 'purple'}); //
let material05 = new THREE.MeshBasicMaterial({color: 'white'}); //
let material06 = new THREE.MeshBasicMaterial({color: 'yellow'}); //
let cube = new THREE.Mesh(cubeGeometry, [material01, material02, material03, material04, material05, material06]);
cube.position.x = 2
scene.add(cube)贴图下载
https://www.poliigon.com/
https://3dtextures.me/
https://www.arroway-textures.ch/```js
import {DoubleSide} from “three”;
// 导入hdr加载器
import {RGBELoader} from “three/examples/jsm/loaders/RGBELoader.js”// 创建文理加载器
let textureLoader = new THREE.TextureLoader();
// 加载文理效果
let texture = textureLoader.load(‘./texture/watercover/CityNewYork002_COL_VAR1_1K.png’);
// 加载ao贴图 明暗程度
let aoMap = textureLoader.load(‘./texture/watercover/CityNewYork002_COL_VAR1_1K.png’);
// 透明度贴图 黑:全透明 白:不透明 灰:半透明
let alphaMap = textureLoader.load(‘./texture/door/height.jpg’);
// 光照贴图
let lightMap = textureLoader.load(‘./texture/colors.png’);
// 高光贴图
let specularMap = textureLoader.load(‘./texture/watercover/CityNewYork002_GLOSS_1K.jpg’);
// rgbeLoader加载hdr贴图
let rgbeLoader = new RGBELoader();
rgbeLoader.load(“./texture/Alex_Hart-Nature_Lab_Bones_2k.hdr”, (envMap => {// 设置球形贴图 envMap.mapping = THREE.EquirectangularReflectionMapping // 设置环境贴图 scene.background = envMap //设置环境贴图-整个场景 scene.environment = envMap // 设置planeMaterial环境贴图 planeMaterial.envMap = envMap
}))
// 创建平面
const planeGeometry = new THREE.PlaneGeometry(1, 1);
// 创建平面材质
const planeMaterial = new THREE.MeshBasicMaterial({side: DoubleSide, color: 0xffffff, map: texture, // 允许透明 transparent: true, // 设置ao aoMap: aoMap, aoMapIntensity: 1, // 设置透明度贴图 alpha贴图是一张灰度纹理,用于控制整个表面的不透明度。(黑色:完全透明;白色:完全不透明)。 默认值为null。 // alphaMap: alphaMap, // 光照贴图 // lightMap: lightMap, // 设置反射值 reflectivity: 0.5, // 设置高光贴图 specularMap: specularMap
});
// planeMaterial.map = texture;
gui.add(planeMaterial, ‘aoMapIntensity’).min(0).max(1).name(‘ao强度’)const cube = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(cube);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
23. ```js
// 创建文理加载器
let textureLoader = new THREE.TextureLoader();
// 加载文理效果
let texture = textureLoader.load('./texture/watercover/CityNewYork002_COL_VAR1_1K.png');
texture.colorSpace = THREE.SRGBColorSpace // 更加真实的
// texture.colorSpace = THREE.LinearSRGBColorSpace // 默认线性空间
gui.add(texture, 'colorSpace', {
sRGB: THREE.SRGBColorSpace,
linear: THREE.LinearSRGBColorSpace
}).onChange(() => {
texture.needsUpdate = true //更改颜色空间
})```js
// 创建几何体
let boxGeometry = new THREE.BoxGeometry(1, 1, 100);
// 创建材质
let basicMaterial = new THREE.MeshBasicMaterial({color: 0x00ff00
});
let mesh = new THREE.Mesh(boxGeometry, basicMaterial);
scene.add(mesh)// 创建场景fog
// scene.fog = new THREE.Fog(0x999999, 0.1, 50);
// 创建指数fog
scene.fog = new THREE.FogExp2(0x999999, 0.1)
scene.background = new THREE.Color(0x999999)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<img src="../blog/coderyeah/source/_posts/img/image-20230830113331658.png" alt="image-20230830113331658" style="zoom:50%;" />
25. #### GLTF加载器(GLTFLoader)
[GlTF](https://www.khronos.org/gltf)(gl传输格式)是一种开放格式的规范 ([open format specification](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0)), 用于更高效地传输、加载3D内容。该类文件以JSON(.gltf)格式或二进制(.glb)格式提供, 外部文件存储贴图(.jpg、.png)和额外的二进制数据(.bin)。一个glTF组件可传输一个或多个场景, 包括网格、材质、贴图、蒙皮、骨架、变形目标、动画、灯光以及摄像机。
```js
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
// 实例化加载器gltf
let gltfLoader = new GLTFLoader();
gltfLoader.load(
// 模型路径
'./model/Duck.glb',
// 加载完成回调
(gltf => {
scene.add(gltf.scene)
console.log(gltf)
}))
// 加载环境贴图
let rgbeLoader = new RGBELoader();
rgbeLoader.load('./texture/Alex_Hart-Nature_Lab_Bones_2k.hdr', (envMap) => {
// 设置球形映射
envMap.mapping = THREE.EquirectangularReflectionMapping
// 设置环境贴图 四面八方的光照进来 小鸭子亮起来
scene.environment = envMap
})加载压缩过的grtl模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader.js" //解码器
// 实例化draco加载器
let dracoLoader = new DRACOLoader();
// 设置draco路径
dracoLoader.setDecoderPath('./draco/')
// 设置gltf加载器draco解码器
gltfLoader.setDRACOLoader(dracoLoader)
// 加载城市的glb文件 glb:二进制文件 gltf:json文件
gltfLoader.load(
'./model/city.glb',
(gltf) => {
scene.add(gltf.scene) //No DRACOLoader instance provided 因为是压缩文件 需要Draco解压
})光线投射3d场景交互事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60// 创建三个球
let sphere1 = new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 32),
new THREE.MeshBasicMaterial(
{color: 0x00ff00}
)
);
sphere1.position.x = -4
scene.add(sphere1);
let sphere2 = new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 32),
new THREE.MeshBasicMaterial({
color: 0x0000ff
})
);
scene.add(sphere2)
let sphere3 = new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 32),
new THREE.MeshBasicMaterial({
color: 0xff00ff
})
);
sphere3.position.x = 4
scene.add(sphere3)
// 创建射线
const raycaster = new THREE.Raycaster();
// 创建鼠标向量
const mouse = new THREE.Vector2();
// 监听点击事件
window.addEventListener('click', (event) => {
// 设置鼠标向量的x, y值
mouse.x = (event.clientX / window.innerWidth) * 2 - 1 // 这样鼠标点击的x值范围就是-1到1
//因为坐标相反 y轴上是正数,下方是负数 所以取相反
mouse.y = -((event.clientY / window.innerHeight) * 2 - 1)
// console.log(mouse.x, mouse.y)
// 通过摄像机和鼠标位置更新射线
raycaster.setFromCamera(mouse, camera)
// 计算物体和射线的焦点
const intersects = raycaster.intersectObjects([sphere1, sphere2, sphere3]);
console.log(intersects);
if (intersects.length > 0) { // 如果在一条直线上数值为3个 选择第一个距离相机位置最近
if (intersects[0].object._isSelected) {
// 改变为原来的颜色
intersects[0].object.material.color.set(intersects[0].object._originColor)
intersects[0].object._isSelected = false
return
}
// originColor自定义的属性值
intersects[0].object._originColor = intersects[0].object.material.color.getHex(); // 获取圆球形原有的rgb颜色值
intersects[0].object._isSelected = true // 已经选择
// 设置颜色为红色
intersects[0].object.material.color.set(0xff0000)
}
});补间动画Tween
1 | // 导入tween |
二.Geometry进阶
UV:二维纹理坐标
法向量:垂直于平面,作用计算反射光,可以给四个顶点设置法向量,使物体具有反射光。
物体转换,顶点转换。
包围盒,包围球
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// 加载小鸭子
// 实例化加载器gltf
let gltfLoader = new GLTFLoader();
gltfLoader.load(
// 模型路径
'./model/Duck.glb',
// 加载完成回调
(gltf => {
scene.add(gltf.scene)
console.log(gltf)
const duckMesh = gltf.scene.getObjectByName("LOD3spShape");
const duckGeometry = duckMesh.geometry
console.log(duckGeometry)
// 计算包围盒
duckGeometry.computeBoundingBox();
// 获取包围盒
const duckBox = duckGeometry.boundingBox;
// 更新世界矩阵
duckMesh.updateWorldMatrix(true, true)
// 更新包围盒
duckBox.applyMatrix4(duckMesh.matrixWorld)
// 创建包围盒辅助器
const box3Helper = new THREE.Box3Helper(duckBox, 0xffff00);
scene.add(box3Helper);
}))几何物体居中
1 | // 设置几合体剧中 |
获取多个物体包围盒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 获取多个物体包围盒
let box = new THREE.Box3();
let sphereArr = [sphere1, sphere2, sphere3]
for (let i = 0; i < sphereArr.length; i++) {
// // 后去当前物体包围盒
// sphereArr[i].geometry.computeBoundingBox()
// // 获取包围盒
// let box3 = sphereArr[i].geometry.boundingBox
// sphereArr[i].updateWorldMatrix(true, true)
// // 将包围盒转换到世界坐标系
// box3.applyMatrix4(sphereArr[i].matrixWorld)
// 第二种方式 更简单
let box3 = new THREE.Box3().setFromObject(sphereArr[i]);
// 合并包围盒
box.union(box3)
}
let box3Helper = new THREE.Box3Helper(box, 0xff0000);
scene.add(box3Helper)边缘几何体和线框几合体
1 | // 实例化draco加载器 |
三.材质与纹理
MatCap材质
MeshMatcapMaterial 由一个材质捕捉(MatCap,或光照球(Lit Sphere))纹理所定义,其编码了材质的颜色与明暗。
由于mapcap图像文件编码了烘焙过的光照,因此MeshMatcapMaterial 不对灯光作出反应。 它将会投射阴影到一个接受阴影的物体上(and shadow clipping works),但不会产生自身阴影或是接受阴影。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 创建渲染器
let renderer = new THREE.WebGLRenderer({
// 开启抗锯齿
antialias:true
});
gltfLoader.load(
'./model/Duck.glb',
(gltf) => {
console.log(gltf)
scene.add(gltf.scene) //No DRACOLoader instance provided 因为是压缩文件 需要Draco解压
// 获取3D物体
let duckMesh = gltf.scene.getObjectByName('LOD3spShape');
// 加载贴图
let matCapTexture = new THREE.TextureLoader()
.load('./texture/matcaps/50332C_D98D79_955F52_AA7C6C-512px.png');
// 获取材质
let preMaterial = duckMesh.material;
duckMesh.material = new THREE.MeshMatcapMaterial({
matcap: matCapTexture,
map: preMaterial.map
})
})Lambert材质-模拟粗超表面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67// 加载环境贴图
let rgbeLoader = new RGBELoader();
rgbeLoader.load("./texture/Alex_Hart-Nature_Lab_Bones_2k.hdr", (envMap => {
// 设置球形贴图
envMap.mapping = THREE.EquirectangularReflectionMapping
// 设置环境贴图
scene.background = envMap
//设置环境贴图-整个场景
scene.environment = envMap
// 反光
phongMaterial.envMap = envMap
}))
// 添加环境光
let ambientLight = new THREE.AmbientLightProbe(0xffffff, 0.3);
scene.add(ambientLight)
// 添加点光源
let pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(0, 5, 0)
scene.add(pointLight);
// 添加纹理
let textureLoader = new THREE.TextureLoader();
let colorTexture = textureLoader.load('./texture/watercover/CityNewYork002_COL_VAR1_1K.png');
colorTexture.colorSpace = THREE.SRGBColorSpace
// 高光贴图
let specularTexture = textureLoader.load('./texture/watercover/CityNewYork002_GLOSS_1K.jpg');
// 法线贴图
let normalTexture = textureLoader.load('./texture/watercover/CityNewYork002_NRM_1K.jpg');
// 凹凸贴图
let dumpTexture = textureLoader.load('./texture/watercover/CityNewYork002_DISP_1K.jpg');
// 环境光遮蔽贴图
let aoTexture = textureLoader.load('./texture/watercover/CityNewYork002_AO_1K.jpg');
// 创建平面
let planeGeometry = new THREE.PlaneGeometry(1, 1, 200, 200);
// let phongMaterial = new THREE.MeshPhongMaterial({
// map: colorTexture,
// transparent: true,
// // 高光贴图
// specularMap: specularTexture,
// // normalMap: normalTexture,
// bumpMap: dumpTexture,
// // 置换贴图
// displacementMap: dumpTexture,
// // 置换程度
// displacementScale: 0.02,
// aoMap: aoTexture
// });
let phongMaterial = new THREE.MeshLambertMaterial({
map: colorTexture,
transparent: true,
// 高光贴图
specularMap: specularTexture,
normalMap: normalTexture,
bumpMap: dumpTexture,
// 置换贴图
displacementMap: dumpTexture,
// 置换程度
displacementScale: 0.02,
aoMap: aoTexture
});
let plane = new THREE.Mesh(planeGeometry, phongMaterial);
// 旋转90度
plane.rotation.x = -Math.PI * 0.5
scene.add(plane);PhongMaterial
用于表面光滑,模拟水晶鸭子
1 | // 实例化draco加载器 |
标准网格材质-模拟真实物理光照效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// 实例化draco加载器
let dracoLoader = new DRACOLoader();
// 设置draco路径
dracoLoader.setDecoderPath('./draco/')
// 设置gltf加载器draco解码器
let gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader)
let params = {
aoMap: true
}
// 加载环境贴图
let rgbeLoader = new RGBELoader();
rgbeLoader.load("./texture/Alex_Hart-Nature_Lab_Bones_2k.hdr", (envMap => {
// 设置球形贴图
// envMap.mapping = THREE.EquirectangularReflectionMapping
envMap.mapping = THREE.EquirectangularRefractionMapping
// 设置环境贴图
scene.background = envMap
//设置环境贴图-整个场景
scene.environment = envMap
// 加载城市的glb文件 glb:二进制文件 gltf:json文件
gltfLoader.load(
'./model/sword/sword.gltf',
(gltf) => {
// 添加环境光
let ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
console.log(gltf);
scene.add(gltf.scene) //No DRACOLoader instance provided 因为是压缩文件 需要Draco解压
let mesh = gltf.scene.getObjectByName('Pommeau_Plane001');
let aopMap = mesh.material.aoMap
gui.add(params, 'aoMap').onChange((value) => {
mesh.material.aoMap = value ? aopMap : null
mesh.material.needsUpdate = true
})
})
}))物理网格材质