·webgl兼容性判断
if (WebGL.isWebGLAvailable()) {
// Initiate function or other initializations here
animate();
} else {
const warning = WebGL.getWebGLErrorMessage();
document.getElementById('container').appendChild(warning);
}
·gltf-viewer,在线预览gltf模型:
https://gltf-viewer.donmccurdy.com
·threejs论坛
论坛:https://discourse.threejs.org
stackoverflow:http://stackoverflow.com/tags/three.js/info
·材质变化时注意事项
材质的texture、fog、vertex、colors、morphing、shadow map、alpha test、transparent改变时需要设置
material.needsUpdate = true
·纹理改变注意事项:
纹理中图像,画布,视频和数据改变需要设置:
texture.needsUpdate = true;
·相机改变注意事项
透视相机模型:
改变相机:fov、aspect、near、far是需要重新计算相机投影矩阵:
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
相机的viewport进行多相机设置,可以达到一屏显示多个模型效果
·对象销毁:
几何体销毁:Geometry.dispose()
材质销毁:Material.dispose()
纹理的销毁:Texture.dispose()
·矩阵更新
静态的object3d对象在改变属性(position,quaternion和scale属性),希望手动更新矩阵时
object.matrixAutoUpdate = false;
object.updateMatrix();
Three.js使用matrix编码3D变换 —— 平移(位置),旋转和缩放
·立方体贴图:
const loader = new THREE.CubeTextureLoader();
loader.setPath( 'textures/cube/pisa/' );
const textureCube = loader.load( [
'px.png', 'nx.png',
'py.png', 'ny.png',
'pz.png', 'nz.png'
] );
const material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube } );
renderer = new THREE.WebGLRenderer( { antialias: true } );
·窗口改变适配
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
controls.update( clock.getDelta() );
renderer.render( scene, camera );
}
·材质:
https://zhuanlan.zhihu.com/p/100241366?utm_id=0
MeshBasicMaterial:不考虑光照的影响
alphaMap:透明度贴图。灰度纹理,用于控制表面的不透明度,黑色为完全透明,白色为完全不透明。对于RGB和RGBA使用绿色通道。使用此贴图时需将材质的transparent属性设置为true。
1、MeshBasicMaterial:基础网格材质。 不受光照影响。
2、MeshStandardMaterial:标准网格材质。 受到光照影响。
3、MeshLambertMaterial:非光泽表面,没有高光。
4、MeshPhongMaterial:具有镜面高光的材质。
5、PointsMaterial:点材质。 size属性设置粒子大小。 lights属性控制是否受光照影响,默认不受光照影响。 depthWrite是否开启深度写入,默认开启。 blending属性设置材质的混合模式。 sizeAttenuation属性设置是否受近大远小影响。
https://blog.csdn.net/qq_45902692/article/details/128394703
环境贴图添加
在Three.js中设置环境贴图的方式如下:
scene.background
= new THREE.CubeTextureLoader().setPath('相对目录文件夹,里边包含6张贴图/').load( [
'posx.jpg',
'negx.jpg',
'posy.jpg',
'negy.jpg',
'posz.jpg',
'negz.jpg'
] );
图片的排放顺序依次是:x轴正方向-x轴负方向-y轴正方向-y轴负方向-z轴正方向-z轴负方向;
按照Three.js创建的默认坐标系中,图片对应的方位是:右侧-左侧-上边-下边-前边-后边;将背景设置成以上的贴图即可显示效果
·加载管理器
THREE.DefaultLoadingManager.onStart = function ( url, itemsLoaded, itemsTotal ) {
console.log( 'Started loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' );
};
THREE.DefaultLoadingManager.onLoad = function ( ) {
console.log( 'Loading Complete!');
};
THREE.DefaultLoadingManager.onProgress = function ( url, itemsLoaded, itemsTotal ) {
console.log( 'Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' );
};
THREE.DefaultLoadingManager.onError = function ( url ) {
console.log( 'There was an error loading ' + url );
};
·启用缓存:
THREE.Cache.enabled = true;
·光源:
AmbientLight:环境光会均匀的照亮场景中的所有物体。环境光不能用来投射阴影,因为它没有方向。
HemisphereLight:不能投射阴影
·gltf模型颜色空间
renderer.outputColorSpace = THREE.SRGBColorSpace;
texture.colorSpace = THREE.SRGBColorSpace;
texture.flipY = false;
// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
文档一
7. 三维坐标系-加强三维空间认识 | Three.js中文网
·受光影响的材质:
·光源:
·全屏布局注意CSS的设置:
<style>
body{
overflow: hidden;
margin: 0px;
}
</style>
·画布宽度变化:
// onresize 事件会在窗口被调整大小时发生
window.onresize = function () {
// 重置渲染器输出画布canvas尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
// 全屏情况下:设置观察范围长宽比aspect为窗口宽高比
camera.aspect = window.innerWidth / window.innerHeight;
// 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
// 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源)
// 如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
camera.updateProjectionMatrix();
};
·渲染器设置:
1)抗锯齿设置
const renderer = new THREE.WebGLRenderer({
antialias:true,
});
2)设备像素比
renderer.setPixelRatio(window.devicePixelRatio)
·旋转、平移、缩放:
·几何体居中:
已经偏移的几何体居中,执行.center(),你可以看到几何体重新与坐标原点重合
geometry.center();
·沿着自定义方向移动:
//向量Vector3对象表示方向
const axis = new THREE.Vector3(1, 1, 1);
axis.normalize(); //向量归一化
//沿着axis轴表示方向平移100
mesh.translateOnAxis(axis, 100);
·绕某个方向旋转:
const axis = new THREE.Vector3(0,1,0);//向量axis
mesh.rotateOnAxis(axis,Math.PI/8);//绕axis轴旋转π/8
·颜色改变:
// 查看Color对象设置0x00ff00对应的的.r、.g、.b值
const color = new THREE.Color(0x00ff00);
color.setRGB(0,1,0);//RGB方式设置颜色
color.setHex(0x00ff00);//十六进制方式设置颜色
color.setStyle('#00ff00');//前端CSS颜色值设置颜色
color.set(0x00ff00);//十六进制方式设置颜色
color.set('#00ff00');//前端CSS颜色值设置颜色
material.color.set(0x00ffff);
·材质透明度设置:
material.transparent = true;//开启透明
material.opacity = 0.5;//设置透明度
·材质面:
material.side = THREE.BackSide;//背面可以看到
material.side = THREE.DoubleSide;//双面可见
·材质或几何体共享
const mesh = new THREE.Mesh(geometry, material);
const mesh2 = new THREE.Mesh(geometry, material);
mesh2.position.x = 100;
// 两个mesh共享一个材质,改变一个mesh的颜色,另一个mesh2的颜色也会跟着改变
// mesh.material和mesh2.material都指向同一个material
// 三者等价:mesh.material、mesh2.material、material
mesh.material.color.set(0xffff00);
// 三者等价:mesh.geometry、mesh2.geometry、geometry
mesh.geometry.translate(0,100,0);
·克隆和复制:
克隆是复制并创建一个与原对象一样的新对象:
const v1 = new THREE.Vector3(1, 2, 3);
console.log('v1',v1);
//v2是一个新的Vector3对象,和v1的.x、.y、.z属性值一样
const v2 = v1.clone();
console.log('v2',v2);
复制是把原对象的属性值赋值给另一个对象:
const v1 = new THREE.Vector3(1, 2, 3);
console.log('v1',v1);
//v2是一个新的Vector3对象,和v1的.x、.y、.z属性值一样
const v2 = v1.clone();
console.log('v2',v2);
克隆位置:
mesh.position.copy(mesh2.position);//1. 第1步位置重合
mesh.position.y += 100;//1. 第2步mesh在原来y的基础上增加100
克隆几何体:
const mesh2 = mesh.clone();
// 克隆几何体和材质,重新设置mesh2的材质和几何体属性
mesh2.geometry = mesh.geometry.clone();
mesh2.material = mesh.material.clone();
// 改变mesh2颜色,不会改变mesh的颜色
mesh2.material.color.set(0xff0000);
两个模型的姿态角度始终保持一样:
// 渲染循环
function render() {
mesh.rotateY(0.01);// mesh旋转动画
// 同步mesh2和mesh的姿态角度一样,不管mesh姿态角度怎么变化,mesh2始终保持同步
mesh2.rotation.copy(mesh.rotation);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
·分组Group:
//把mesh1型插入到组group中,mesh1作为group的子对象
group.add(mesh1);
//把mesh2型插入到组group中,mesh2作为group的子对象
group.add(mesh2);
或
group.add(mesh1,mesh2);
//把group插入到场景中作为场景子对象
scene.add(group);
console.log('查看group的子对象',group.children);
父对象旋转缩放平移变换,子对象跟着变化
//沿着Y轴平移mesh1和mesh2的父对象,mesh1和mesh2跟着平移
group.translateY(100);
//父对象缩放,子对象跟着缩放
group.scale.set(4,4,4);
//父对象旋转,子对象跟着旋转
group.rotateY(Math.PI/6)
mesh也能添加mesh子对象:
mesh1.add(mesh2);
·按名字查询模型组件:
/ 返回名.name为"4号楼"对应的对象
const nameNode = scene.getObjectByName ("4号楼");
nameNode.material.color.set(0xff0000);
·本地坐标与世界坐标:
本地(局部)坐标就是模型的position属性;
世界坐标是模型自身.position和所有父对象.position累加的坐标,通过getWorldPosition获取。
读取一个模型的世界坐标,并把读取结果存储到参数Vector3中:
mesh.getWorldPosition(Vector3)
给子对象添加一个局部坐标系:
const meshAxesHelper = new THREE.AxesHelper(50);
mesh.add(meshAxesHelper);
·移除对象:
// 删除父对象group的子对象网格模型mesh1:
group.remove(mesh1);
scene移除:
scene.remove(ambient);//移除场景中环境光
scene.remove(model);//移除场景中模型对象
查看移除后的对象:
console.log('查看group的子对象',group.children);
一次移除多个:
group.remove(mesh1,mesh2);
·模型隐藏与显示:
模型属性.visible隐藏或显示:
mesh.visible =false;// 隐藏一个网格模型,visible的默认值是true
group.visible =false;// 隐藏一个包含多个模型的组对象group
mesh.visible =true;// 使网格模型mesh处于显示状态
材质属性.visible隐藏或显示:
mesh.material.visible =false;
·顶点UV坐标的作用:
顶点UV坐标的作用是从纹理贴图上提取像素映射到网格模型Mesh的几何体表面上。
顶点UV坐标可以在0~1.0之间任意取值,纹理贴图左下角对应的UV坐标是(0,0),右上角对应的坐标(1,1)。
/**纹理坐标0~1之间随意定义*/
const uvs = new Float32Array([
0, 0, //图片左下角
1, 0, //图片右下角
1, 1, //图片右上角
0, 1, //图片左上角
]);
// 设置几何体attributes属性的位置normal属性
geometry.attributes.uv = new THREE.BufferAttribute(uvs, 2); //2个为一组,表示一个顶点的纹理坐标
·纹理对象Texture的阵列功能:
// .load()方法加载图像,返回一个纹理对象Texture
const texture = texLoader.load('./瓷砖.jpg');
// 设置阵列模式
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// uv两个方向纹理重复数量
texture.repeat.set(12,12);//注意选择合适的阵列数量
·旋转对象影响:
注意旋转方向影响矩形平面背面还是正面朝上,threejs默认渲染正面,不渲染背面。
·png贴图:
注意选择背景透明的.png图像作为颜色贴图,同时材质设置transparent: true,这样png图片背景完全透明的部分不显示。
·各种helper:
网格地面辅助观察:
// 添加一个辅助网格地面
const gridHelper = new THREE.GridHelper(300, 25, 0x004444, 0x004444);
// AxesHelper:辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
// 骨骼关节可以和普通网格模型一样作为其他模型子对象,添加到场景中
const group = new THREE.Group();
group.add(Bone1);
// SkeletonHelper会可视化参数模型对象所包含的所有骨骼关节
const skeletonHelper = new THREE.SkeletonHelper(group);
group.add(skeletonHelper);
// 聚广源辅助对象,可视化聚广源
const spotLightHelper = new THREE.SpotLightHelper(spotLight,0xffffff)
scene.add(spotLightHelper);
//引入性能监视器stats.js
import Stats from 'three/addons/libs/stats.module.js';
//创建stats对象
const stats = new Stats();
//stats.domElement:web页面上输出计算结果,一个div元素,
document.body.appendChild(stats.domElement);
// 渲染函数
function render() {
//requestAnimationFrame循环调用的函数中调用方法update(),来刷新时间
stats.update();
renderer.render(scene, camera); //执行渲染操作
requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
}
render();
GUI可视化改变三维场景:
// 引入dat.gui.js的一个类GUI
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
// 实例化一个gui对象
const gui = new GUI();
//改变交互界面style属性
gui.domElement.style.right = '0px';
gui.domElement.style.width = '300px';
gui.add(mesh.position, 'x', 0, 180);
gui.add(mesh.position, 'y', 0, 180);
gui.add(mesh.position, 'z', 0, 180);
matFolder.add(mesh.material,'metalness',0,1);
matFolder.add(mesh.material,'roughness',0,1);
matFolder.add(mesh.material,'clearcoat',0,1);
matFolder.add(mesh.material,'clearcoatRoughness',0,1);
matFolder.add(mesh.material,'envMapIntensity',0,10);
const obj = {
color: mesh.material.color, // 材质颜色
};
// 材质颜色color
matFolder.addColor(obj, 'color').onChange(function (value) {
mesh.material.color.set(value);
});
// 范围可以参考文档
matFolder.add(mesh.material,'metalness',0,1);
matFolder.add(mesh.material,'roughness',0,1);
matFolder.add(mesh.material,'transmission',0,1);
matFolder.add(mesh.material,'ior',0,3);
matFolder.add(mesh.material,'envMapIntensity',0,10);
·画布居中:
camera.lookAt(0, 0, 0);
注意相机控件OrbitControls会影响lookAt设置,注意手动设置OrbitControls的目标参数
camera.lookAt(100, 0, 0);
// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
// 相机控件.target属性在OrbitControls.js内部表示相机目标观察点,默认0,0,0
// console.log('controls.target', controls.target);
controls.target.set(100, 0, 0);
controls.update();//update()函数内会执行camera.lookAt(controls.targe)
·纹理贴图颜色偏差解决(旧版):
three.js加载gltf模型的时候,可能会遇到three.js渲染结果颜色偏差,对于这种情况,你只需要修改WebGL渲染器默认的编码方式.outputEncoding即可:
//解决加载gltf格式模型纹理贴图和原图不一样问题
renderer.outputEncoding = THREE.sRGBEncoding;
单独加载的纹理贴图的.encoding和webgl渲染器的.outputEncoding保持一致:
const texLoader = new THREE.TextureLoader();
const texture = texLoader.load('./黑色.png');// 加载手机mesh另一个颜色贴图
texture.encoding = THREE.sRGBEncoding; //和渲染器.outputEncoding一样值
新版是正确的不用设置:
//新版本,加载gltf,不需要执行下面代码解决颜色偏差
renderer.outputColorSpace = THREE.SRGBColorSpace;//设置为SRGB颜色空间
注意:如果你直接给gltf模型材质设置.map属性更换贴图,会出现纹理贴图错位的问题,这主要和纹理对象Texture的翻转属性.flipY有关:
- 纹理对象Texture翻转属性.flipY默认值是true;
- gltf的贴图翻转属性.flipY默认值是false。
故如果单独更换纹理贴图记得设置
texture.flipY = false
·遍历对象:
//循环遍历
const obj = gltf.scene.getObjectByName('洋房');
console.log('obj', obj); //控制台查看返回结果
console.log('obj.children', obj.children);
// obj.children的所有子对象都是Mesh,改变Mesh对应颜色
obj.children.forEach(function (mesh) {
mesh.material.color.set(0xffff00);
})
递归遍历:
gltf.scene.traverse(function(obj) {
console.log('所有模型节点的名称',obj.name);
// obj.isMesh:if判断模型对象obj是不是网格模型'Mesh'
if (obj.isMesh) {//判断条件也可以是obj.type === 'Mesh'
obj.material.color.set(0xffff00);
}
});
·代码方式解决多个mesh共享材质的问题:
//用代码方式解决mesh共享材质问题
gltf.scene.getObjectByName("小区房子").traverse(function (obj) {
if (obj.isMesh) {
// .material.clone()返回一个新材质对象,和原来一样,重新赋值给.material属性
obj.material = obj.material.clone();
}
});
·PBR材质:
金属度属性.metalness表示材质像金属的程度, 非金属材料,如木材或石材,使用0.0,金属使用1.0;
threejs的PBR材质,.metalness默认是0.5,0.0到1.0之间的值可用于生锈的金属外观
new THREE.MeshStandardMaterial({
metalness: 1.0,//金属度属性
})
或
mesh.material.metalness = 1.0;//金属度
粗糙度roughness
表示模型表面的光滑或者说粗糙程度,越光滑镜面反射能力越强,越粗糙,表面镜面反射能力越弱,更多地表现为漫反射。生活中例如地面比较粗糙,比如镜子表面就非常非常光滑。
new THREE.MeshStandardMaterial({
roughness: 0.5,//表面粗糙度
})
或
mesh.material.roughness = 0.5;//表面粗糙度
obj.material.roughness = 0.0;//完全镜面反射,像镜子一样
环境贴图.envMap(金属效果):环境贴图对PBR材质渲染效果影响还是比较大,一般渲染PBR材质的模型,最好设置一个合适的环境贴图。
// 加载环境贴图
const textureCube = new THREE.CubeTextureLoader()
.setPath('./环境贴图/环境贴图0/')
.load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
new THREE.MeshStandardMaterial({
metalness: 1.0,
roughness: 0.5,
envMap: textureCube, //设置pbr材质环境贴图
})
obj.material.envMap = textureCube; //设置环境贴图
MeshStandardMaterial的.envMapIntensity属性相当于环境贴图的系数,环境贴图像素值乘以该系数后,在用于影响模型表面
// envMapIntensity:控制环境贴图对mesh表面影响程度
//默认值1, 设置为0.0,相当于没有环境贴图
obj.material.envMapIntensity = 1.0;
如果一个gltf模型中所有的Mesh都要设置环境贴图就需要递归遍历gltf模型,给里面每个Mesh的材质设置.envMap:
loader.load("../工厂.glb", function (gltf) {
// 递归遍历批量设置环境贴图
gltf.scene.traverse(function (obj) {
if (obj.isMesh) { //判断是否是网格模型
obj.material.envMap = textureCube; //设置环境贴图
}
});
})
如果你希望环境贴图影响场景中scene所有Mesh,可以通过Scene的场景环境属性.environment实现,把环境贴图对应纹理对象设置为.environment的属性值即可:
// 环境贴图纹理对象textureCube作为.environment属性值,影响所有模型
scene.environment = textureCube;
环境贴图需要保持一致(旧版)
//如果renderer.outputEncoding=THREE.sRGBEncoding;
textureCube.encoding = THREE.sRGBEncoding;
MeshPhysicalMaterial:
MeshPhysicalMaterial和MeshStandardMaterial都是拥有金属度metalness、粗糙度roughness属性的PBR材质,MeshPhysicalMaterial是在MeshStandardMaterial基础上扩展出来的子类,除了继承了MeshStandardMaterial的金属度、粗糙度等属性,还新增了清漆.clearcoat、透光率.transmission、反射率.reflectivity、光泽.sheen、折射率.ior等等各种用于模拟生活中不同材质的属性
清漆层属性.clearcoat:以用来模拟物体表面一层透明图层,就好比你在物体表面刷了一层透明清漆,喷了点水。.clearcoat的范围0到1,默认0。
const material = new THREE.MeshPhysicalMaterial( {
clearcoat: 1.0,//物体表面清漆层或者说透明涂层的厚度
} );
const material = new THREE.MeshPhysicalMaterial( {
clearcoat: 1.0,//物体表面清漆层或者说透明涂层的厚度
clearcoatRoughness: 0.1,//透明涂层表面的粗糙度
} );
车外壳PBR材质设置:
const mesh = gltf.scene.getObjectByName('外壳01');
mesh.material = new THREE.MeshPhysicalMaterial({
color: mesh.material.color, //默认颜色
metalness: 0.9,//车外壳金属度
roughness: 0.5,//车外壳粗糙度
envMap: textureCube, //环境贴图
envMapIntensity: 2.5, //环境贴图对Mesh表面影响程度
})
车外壳油漆效果:
车外壳油漆效果,你可以通过PBR材质的清漆层属性.clearcoat和清漆层粗糙度.clearcoatRoughness属性模拟。
const mesh = gltf.scene.getObjectByName('外壳01');
mesh.material = new THREE.MeshPhysicalMaterial( {
clearcoat: 1.0,//物体表面清漆层或者说透明涂层的厚度
clearcoatRoughness: 0.1,//透明涂层表面的粗糙度
} );
透光率(透射度).transmission
模拟玻璃、半透明塑料一类的视觉效果
const mesh = gltf.scene.getObjectByName('玻璃01')
mesh.material = new THREE.MeshPhysicalMaterial({
transmission: 1.0, //玻璃材质透光率,transmission替代opacity
})
折射率.ior
非金属材料的折射率从1.0到2.333。默认值为1.5
new THREE.MeshPhysicalMaterial({
ior:1.5,//折射率
})
玻璃效果
const mesh = gltf.scene.getObjectByName('玻璃01')
mesh.material = new THREE.MeshPhysicalMaterial({
metalness: 0.0,//玻璃非金属
roughness: 0.0,//玻璃表面光滑
envMap:textureCube,//环境贴图
envMapIntensity: 1.0, //环境贴图对Mesh表面影响程度
transmission: 1.0, //玻璃材质透光率,transmission替代opacity
ior:1.5,//折射率
})
·canvas背景透明度:
若要canvas画布完全透明,可以透过canvas画布看到画布后面叠加的HTML元素图文,呈现出来一种三维模型悬浮在网页上面的效果
//改变背景透明度值
renderer.setClearAlpha(0.8);
//完全透明
renderer.setClearAlpha(0.0);
// 在构造函数参数中设置alpha属性的值
var renderer = new THREE.WebGLRenderer({
alpha: true
});
//设置背景颜色和透明度
renderer.setClearColor(0xb9d3ff, 0.4);
·canvas下载为图片:
// 1.WebGL渲染器设置
const renderer = new THREE.WebGLRenderer({
//想把canvas画布上内容下载到本地,需要设置为true
preserveDrawingBuffer:true,
});
// 2.鼠标单击id为download的HTML元素,threejs渲染结果以图片形式下载到本地
document.getElementById('download').addEventListener('click',function(){
// 创建一个超链接元素,用来下载保存数据的文件
const link = document.createElement('a');
// 通过超链接herf属性,设置要保存到文件中的数据
link.href = renderer.domElement.toDataURL("image/png");
link.download = 'threejs.png'; //下载文件名
link.click(); //js代码触发超链接元素a的鼠标点击事件,开始下载文件到本地
})
·深度冲突(模型闪烁):
// WebGL渲染器设置
const renderer = new THREE.WebGLRenderer({
// 设置对数深度缓冲区,优化深度冲突问题
logarithmicDepthBuffer: true
});
有一点要注意,当两个面间隙过小,或者重合,你设置webgl渲染器对数深度缓冲区也是无效的。
·颜色插值用于渐变渲染:
loader.load("../地形.glb", function (gltf) {
model.add(gltf.scene);
const mesh = gltf.scene.children[0];
const pos = mesh.geometry.attributes.position;
const count = pos.count;
// 1. 计算模型y坐标高度差
const yArr = [];//顶点所有y坐标,也就是地形高度
for (let i = 0; i < count; i++) {
yArr.push(pos.getY(i));//获取顶点y坐标,也就是地形高度
}
yArr.sort();//数组元素排序,从小到大
const miny = yArr[0];//y最小值
const maxy = yArr[yArr.length - 1];//y最大值
const height = maxy - miny; //山脉整体高度
// 2. 计算每个顶点的颜色值
const colorsArr = [];
const c1 = new THREE.Color(0x0000ff);//山谷颜色
const c2 = new THREE.Color(0xff0000);//山顶颜色
for (let i = 0; i < count; i++) {
//当前高度和整体高度比值
const percent = (pos.getY(i) - miny) / height;
const c = c1.clone().lerp(c2, percent);//颜色插值计算
colorsArr.push(c.r, c.g, c.b);
}
const colors = new Float32Array(colorsArr);
// 设置几何体attributes属性的颜色color属性
mesh.geometry.attributes.color = new THREE.BufferAttribute(colors, 3);
// 3. 设置材质,使用顶点颜色渲染
mesh.material = new THREE.MeshLambertMaterial({
vertexColors:true
});
})
·计算模型最小包围盒:
//包围盒设置
const box3 = new THREE.Box3()
console.log('box3',box3);
box3.min = new THREE.Vector3(-10, -10,0);
box3.max = new THREE.Vector3(100, 20,50);
//计算模型最小包围盒.expandByObject()
const box3 = new THREE.Box3();
box3.expandByObject(mesh); // 计算模型最小包围盒
console.log('查看包围盒',box3);
//包围盒尺寸.getSize()
const scale = new THREE.Vector3()
// getSize()计算包围盒尺寸
// 获得包围盒长宽高尺寸,结果保存在参数三维向量对象scale中
box3.getSize(scale)
console.log('模型包围盒尺寸', scale);
//包围盒几何中心.getCenter()
// 计算包围盒中心坐标
const center = new THREE.Vector3()
box3.getCenter(center)
console.log('模型中心坐标', center);
·相机position改变后要重新设置lookAt:
// 通过UI按钮改变相机观察角度
document.getElementById('x').addEventListener('click', function () {
camera.position.set(500, 0, 0); //x轴方向观察
camera.lookAt(0, 0, 0); //重新计算相机视线方向
})
·OrbitControls:
controls.enablePan = false; //禁止右键拖拽
controls.enableZoom = false;//禁止缩放
controls.enableRotate = false; //禁止旋转
controls.enableDamping false; //将其设置为true以启用阻尼(惯性)
相机控件OrbitControls.target属性对应的就是相机的.lookAt()观察目标。
执行controls.update();,相机控件内部会执行camera.lookAt(controls.target)。
// controls.target默认值是坐标原点
controls.target.set(x, y, z);
//update()函数内会执行camera.lookAt(x, y, z)
controls.update();
//相机位置与观察目标点最小值
controls.minDistance = 200;
//相机位置与观察目标点最大值
controls.maxDistance = 500;
//相机位置与目标观察点距离
const dis = controls.getDistance();
console.log('dis',dis);
// 缩放范围(正投影相机)
controls.minZoom = 0.5;
controls.maxZoom = 2;
辅助计算相机位置与目标观察点距离
controls.addEventListener('change',function(){
//相机位置与目标观察点距离
const dis = controls.getDistance();
console.log('dis',dis);
})
// 上下旋转范围
controls.minPolarAngle = 0;//默认值0
controls.maxPolarAngle = Math.PI;//默认值Math.PI
// 左右旋转范围
controls.minAzimuthAngle = -Math.PI/2; //范围[-2 * Math.PI,2 * Math.PI]
controls.maxAzimuthAngle = Math.PI/2; //范围[-2 * Math.PI,2 * Math.PI]
.autoRotate:Boolean将其设为true,以自动围绕目标旋转
.autoRotateSpeed : Float围绕目标旋转的速度将有多快,默认值为2.0,相当于在60fps时每旋转一周需要30秒
.dampingFactor : Float当.enableDamping设置为true的时候,阻尼惯性有多大。 默认0.05,请注意,要使得这一值生效,你必须在你的动画循环里调用.update()。
.panSpeed : Float位移的速度,其默认值为1。
.rotateSpeed : Float旋转的速度,其默认值为1。
·灯光设置:
//1.聚光灯
// 聚光源
// 0xffffff:光源颜色
// 1.0:光照强度intensity
const spotLight = new THREE.SpotLight(0xffffff,1.0);
scene.add(spotLight);//光源添加到场景中
spotLight.intensity = 1.0;//光照强度
// 设置聚光光源发散角度
spotLight.angle = Math.PI / 6;//光锥角度的二分之一
// 设置聚光光源位置
spotLight.position.set(0, 50, 0);
// spotLight.target是一个模型对象Object3D,默认在坐标原点
spotLight.target.position.set(50,0,0);
//spotLight.target添加到场景中.target.position才会起作用
scene.add(spotLight.target);
平行光DirectionalLight阴影步骤
// 1.设置产生投影的网格模型
mesh.castShadow = true;
// 2.设置产生投影的光源
//平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// 平行光设置产生阴影的光源对象,开启光源阴影的计算功能
directionalLight.castShadow = true;
//3.设置接收阴影的投影面
planeMesh.receiveShadow = true;
// 4.设置渲染器,允许光源阴影渲染
renderer.shadowMap.enabled = true;
// 5.设置三维场景计算阴影的范围
directionalLight.shadow.camera.left = -50;
directionalLight.shadow.camera.right = 50;
directionalLight.shadow.camera.top = 200;
directionalLight.shadow.camera.bottom = -100;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 600;
// 如果阴影边缘锯齿感的时候,可以适当提升像素
directionalLight.shadow.mapSize.set(1024,1024);
directionalLight.shadow.mapSize.set(2048,2048);
适当提升.shadow.radius,你可以感到阴影边缘与非阴影区域是渐变过渡,或者说阴影边缘逐渐弱化或模糊化,没有很明显的边界感。
directionalLight.shadow.radius = 3;
// 模型表面产生条纹影响渲染效果,可以改变.shadowMap.type默认值优化
renderer.shadowMap.type = THREE.VSMShadowMap;
·动画:
// 给需要设置关键帧动画的模型命名
mesh.name = "Box";
const times = [0, 3, 6]; //时间轴上,设置三个时刻0、3、6秒
// times中三个不同时间点,物体分别对应values中的三个xyz坐标
const values = [0, 0, 0, 100, 0, 0, 0, 0, 100];
// 0~3秒,物体从(0,0,0)逐渐移动到(100,0,0),3~6秒逐渐从(100,0,0)移动到(0,0,100)
const posKF = new THREE.KeyframeTrack('Box.position', times, values);
// 从2秒到5秒,物体从红色逐渐变化为蓝色
const colorKF = new THREE.KeyframeTrack('Box.material.color', [2, 5], [1, 0, 0, 0, 0, 1]);
// 1.3 基于关键帧数据,创建一个clip关键帧动画对象,命名"test",持续时间6秒。
const clip = new THREE.AnimationClip("test", 6, [posKF, colorKF]);
//包含关键帧动画的模型对象作为AnimationMixer的参数创建一个播放器mixer
const mixer = new THREE.AnimationMixer(mesh);
//AnimationMixer的`.clipAction()`返回一个AnimationAction对象
const clipAction = mixer.clipAction(clip);
//.play()控制动画播放,默认循环播放
clipAction.play();
const clock = new THREE.Clock();
function loop() {
requestAnimationFrame(loop);
//clock.getDelta()方法获得loop()两次执行时间间隔
const frameT = clock.getDelta();
// 更新播放器相关的时间
mixer.update(frameT);
}
loop();
其他播放控制
//不循环播放
clipAction.loop = THREE.LoopOnce;
上面播放一次后模型会回到关键帧动画开头状态,若想停留在最后一帧执行
// 物体状态停留在动画结束的时候
clipAction.clampWhenFinished = true;
//动画停止结束,回到开始状态
clipAction.stop();
// AnimationAction.paused默认值false,设置为true,可以临时暂停动画
if (clipAction.paused) {//暂停状态
clipAction.paused = false;//切换为播放状态
} else {//播放状态
clipAction.paused = true;//切换为暂停状态
}
倍速播放.timeScale
clipAction.timeScale = 1;//默认
clipAction.timeScale = 2;//2倍速
关键帧时长
clip.duration
//AnimationAction设置开始播放时间:从1秒时刻对应动画开始播放
clipAction.time = 1;
//AnimationClip设置播放结束时间:到5秒时刻对应的动画状态停止
clip.duration = 5;
//不循环播放
clipAction.loop = THREE.LoopOnce;
// 物体状态停留在动画结束的时候
clipAction.clampWhenFinished=true;
外部模型动画:
//包含关键帧动画的模型对象作为AnimationMixer的参数创建一个播放器mixer
const mixer = new THREE.AnimationMixer(mesh);
THREE.LoopOnce - 只执行一次
THREE.LoopRepeat - 重复次数为repetitions的值, 且每次循环结束时候将回到起始动作开始下一次循环。
THREE.LoopPingPong - 重复次数为repetitions的值, 且像乒乓球一样在起始点与结束点之间来回循环。
.timeScale : Number
时间(time)的比例因子. 值为0时会使动画暂停。值为负数时动画会反向执行。默认值是1。
const IdleAction = mixer.clipAction(gltf.animations[0]);
const RunAction = mixer.clipAction(gltf.animations[1]);
const WalkAction = mixer.clipAction(gltf.animations[3]);
IdleAction.play();
let ActionState = IdleAction;//当前处于播放状态的动画动作对象
// 通过UI按钮控制,切换动画运动状态
document.getElementById('Idle').addEventListener('click', function () {
ActionState.stop();//播放状态动画终止
IdleAction.play();
ActionState = IdleAction;
})
document.getElementById('Run').addEventListener('click', function () {
ActionState.stop();//播放状态动画终止
RunAction.play();
ActionState = RunAction;
})
document.getElementById('Walk').addEventListener('click', function () {
ActionState.stop();//播放状态动画终止
WalkAction.play();
ActionState = WalkAction;
})
// 向量的x、y、z坐标分别在pos基础上增加30
const pos2 = pos.clone().addScalar(30);
// 切换到设备A预览状态
document.getElementById('A').addEventListener('click', function () {
const A = model.getObjectByName('设备A标注');
const pos = new THREE.Vector3();
A.getWorldPosition(pos); //获取三维场景中某个对象世界坐标
// 相机飞行到的位置和观察目标拉开一定的距离
const pos2 = pos.clone().addScalar(30);//向量的x、y、z坐标分别在pos基础上增加30
// 相机从当前位置camera.position飞行三维场景中某个世界坐标附近
new TWEEN.Tween({
// 相机开始坐标
x: camera.position.x,
y: camera.position.y,
z: camera.position.z,
// 相机开始指向的目标观察点
tx: 0,
ty: 0,
tz: 0,
})
.to({
// 相机结束坐标
x: pos2.x,
y: pos2.y,
z: pos2.z,
// 相机结束指向的目标观察点
tx: pos.x,
ty: pos.y,
tz: pos.z,
}, 2000)
.onUpdate(function (obj) {
// 动态改变相机位置
camera.position.set(obj.x, obj.y, obj.z);
// 动态计算相机视线
camera.lookAt(obj.tx, obj.ty, obj.tz);
})
.start();
})
动画结束更新OrbitControls
.onUpdate(function (obj) {
...
camera.lookAt(obj.tx, obj.ty, obj.tz);
})
.onComplete(function(obj){
controls.target.set(obj.tx, obj.ty, obj.tz);
controls.update();
})
封装一个相机动画函数
// 相机动画函数,从A点飞行到B点,A点表示相机当前所处状态
// pos: 三维向量Vector3,表示动画结束相机位置
// target: 三维向量Vector3,表示相机动画结束lookAt指向的目标观察点
function createCameraTween(endPos,endTarget){
new TWEEN.Tween({
// 不管相机此刻处于什么状态,直接读取当前的位置和目标观察点
x: camera.position.x,
y: camera.position.y,
z: camera.position.z,
tx: controls.target.x,
ty: controls.target.y,
tz: controls.target.z,
})
.to({
// 动画结束相机位置坐标
x: endPos.x,
y: endPos.y,
z: endPos.z,
// 动画结束相机指向的目标观察点
tx: endTarget.x,
ty: endTarget.y,
tz: endTarget.z,
}, 2000)
.onUpdate(function (obj) {
// 动态改变相机位置
camera.position.set(obj.x, obj.y, obj.z);
// 动态计算相机视线
// camera.lookAt(obj.tx, obj.ty, obj.tz);
controls.target.set(obj.tx, obj.ty, obj.tz);
controls.update();//内部会执行.lookAt()
})
.start();
}
设置设备A、设备B、停车场、整体预览四个按钮对应的相机动画,这样你可以在4个按钮之间,随意切换相机的观察状态
// 切换到设备A预览状态
document.getElementById('A').addEventListener('click', function () {
const A = model.getObjectByName('设备A标注');
const pos = new THREE.Vector3();
A.getWorldPosition(pos); //获取三维场景中某个对象世界坐标
// 相机飞行到的位置和观察目标拉开一定的距离
const pos2 = pos.clone().addScalar(30);
createCameraTween(pos2, controls.target)
})
// 切换到设备B的预览状态
document.getElementById('B').addEventListener('click', function () {
const B = model.getObjectByName('设备B标注');
const pos = new THREE.Vector3();
B.getWorldPosition(pos); //获取三维场景中某个对象世界坐标
// 相机飞行到的位置和观察目标拉开一定的距离
const pos2 = pos.clone().addScalar(30);
// 相机从当前位置camera.position飞行三维场景中某个世界坐标附近
createCameraTween(pos2, controls.target)
})
// 切换到设备停车场的预览状态
document.getElementById('car').addEventListener('click', function () {
const car = model.getObjectByName('停车场标注');
const pos = new THREE.Vector3();
car.getWorldPosition(pos); //获取三维场景中某个对象世界坐标
// 相机飞行到的位置和观察目标拉开一定的距离
const pos2 = pos.clone().addScalar(30);
// 相机从当前位置camera.position飞行三维场景中某个世界坐标附近
createCameraTween(pos2, pos)
})
// 相机整体预览对应的位置和观察目标
const cameraPos0 = new THREE.Vector3(202, 123, 125)
const target0 = new THREE.Vector3(0, 0, 0);
// 切换整体预览状态
document.getElementById('all').addEventListener('click', function () {
// 相机从当前位置camera.position回到整体预览状态
createCameraTween(cameraPos0, target0)
})
// 动画开始缓动方式(类比加速启动)
tween.easing(TWEEN.Easing.Sinusoidal.In);
// 动画结束缓动方式(类比减速刹车)
tween.easing(TWEEN.Easing.Sinusoidal.Out);
// 同时设置In和Out
tween.easing(TWEEN.Easing.Sinusoidal.InOut);
·贴图颜色矫正:
注意贴图颜色矫正:
materialClouds.map.colorSpace = THREE.SRGBColorSpace;