threejs要点你值得学习

·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有关:

  1. 纹理对象Texture翻转属性.flipY默认值是true;
  2. 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;

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KV_T

您的鼓励会激发我的创作热情笑脸

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值