threejs学习

重要概念(场景、相机、渲染器)

如下图所示,我们最终看到浏览器上生成的内容是通过虚拟场景和虚拟相机被渲染器渲染后的结果,下面首先介绍这三个概念,将贯穿所有简单复杂的threejs项目。

场景 Scene

虚拟的3D场景,用来表示模拟生活中的真实三维场景,或者说三维世界

js 复制代码
    const scene = new THREE.Scene() //创建场景

	//添加元素如模型、灯光等
	scene.add(元素)

相机 Camera

透视投影相机,如果渲染远小近大--透视投影相机,不需要远小近大--正投影相机 ,常用透视投影相机

正投影相机:OrthographicCamera

透视投影相机:PerspectiveCamera PerspectiveCamera( fov, aspect, near, far )

  • fov:相机视锥体竖直方向视野角度 ,默认50
  • aspect:相机视锥体水平方向和竖直方向长度比,一般设置为Canvas画布宽高比width / height ,默认1
  • near:相机视锥体近裁截面相对相机距离,默认0.1
  • far:相机视锥体远裁截面相对相机距离,far-near构成了视锥体高度方向,默认2000

具体使用:

js 复制代码
      const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000); 
      // 设置相机位置
      camera.position.set(15, 20, 50); 
      //控制相机的拍照目标,具体说相机镜头对准哪个物体或说哪个坐标
	  camera.lookAt(0, 0, 0)//0,0,0是坐标圆点,(x,y,z)

注意: 相机实际形成一个视锥体,在这个范围内的物体才会被渲染出来,之外的物体不会被渲染到canvas上

在我们开发的时候,如果将far设置的比较小,在放大物体的时候,可以看到部分模型消失,只渲染了一部分,那么就是模型超出视锥体了,所以不被渲染。

渲染器 WebGLRenderer

有了相机和物体,则需要完成拍照,渲染器其实可以理解为拍照。
renderer=new THREE.WebGLRenderer();进行创建 .domElement可以获取到对应的元素。
renderer.render(场景, 相机);:执行渲染的操作,类似我们按下相机快门的操作。

js 复制代码
//创建一个渲染器,一个canvas场景
const renderer = new THREE.WebGLRenderer();

renderer.setPixelRatio(window.devicePixelRatio);  //设置像素比,如果你遇到你的canvas画布输出模糊问题一定要设置

renderer.setSize(window.innerWidth, window.innerHeight); //设置canvas的大小
//锯齿属性
//renderer.antialias = true

//插入html中,这里即添加到id为container的标签下
const container = document.getElementById('container')
container.appendChild(renderer.domElement);

//最重要的!!!!渲染
renderer.render(scene, camera); //执行渲染操作

画布自适应

js 复制代码
   window.addEventListener("resize", this.resize);
    resize() {
      //   console.log("画面变化了");
      // 更新摄像头
      let camera = this.camera
      camera.aspect = window.innerWidth / window.innerHeight;
      //   更新摄像机的投影矩阵,如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
      camera.updateProjectionMatrix();
      let renderer = this.renderer
      //   更新渲染器
      renderer.setSize(window.innerWidth, window.innerHeight);
  
      //   设置渲染器的像素比
      renderer.setPixelRatio(window.devicePixelRatio);
    },

光源和材质

首先介绍几个相关的概念:

  1. 外观-材质Material:想定义物体的外观效果,比如颜色,就需要通过材质Material相关的API实现。
  2. 物体-网格模型Mesh:在threejs中可以通过网格模型Mesh (opens new window)表示一个虚拟的物体,比如一个箱子、一个鼠标。
  3. 模型位置position
js 复制代码
const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
//设置网格模型在三维空间中的位置坐标,默认是坐标原点
mesh.position.set(0,10,0);

光源

光源Light:影响物体的明暗效果

不同材质对光照的影响不同,如图(材质不止下图,可到官网查看)

光源也有很多,具体的API推荐看官网(太多啦!)

材质

基础介绍,具体内容看官网

几何体

Three.js提供了各种各样的几何体API,用来表示三维物体的几何形状。

因为太多了就不在此赘述,可以到官网去查看对应的api和属性

引入外部三维模型

引入glb\gltf模型

我们在实际工作中,总是需要另外的引入模型,这里介绍引入模型的方法

js 复制代码
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; //加载gltf模型的加载器
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'  //解压用,不一定需要使用它

   const dracoLoader = new DRACOLoader()
   //设置解压工具位置
   dracoLoader.setDecoderPath('resources/draco/') //从node_modules\three\examples\jsm\libs\draco 中复制到public下resources/draco/  注意要从自己的项目node_modules中复制,版本不对可能会报错
   dracoLoader.setDecoderConfig({ type: "js" });
   
  const loader = new GLTFLoader()
  loader.setDRACOLoader(dracoLoader)
//在vue中默认路径是public下的
  loader.load("model/city-v1.glb", function (gltf) {  //
        const model = gltf.scene //拿到模型
        model.name = 'city' 
        model.position.set(0, 0, 0) //设置模型的位置
        model.scale.set(0.7, 0.7, 0.7)
        scene.add(model)//添加到场景中
      })

绘制天空的效果

js 复制代码
    const loaderbox = new THREE.CubeTextureLoader() //加载CubeTexture的一个类。 内部使用ImageLoader来加载文件
      let path = 'night/', format = ".jpg"
      const cubeTexture = loaderbox.load([  //一定是六张图!!!
        path + 'posx' + format,
        path + 'negx' + format,
        path + 'posy' + format,
        path + 'negy' + format,
        path + 'posz' + format,
        path + 'negz' + format
      ])

      scene.background = cubeTexture

更改引入模型的材质

js 复制代码
        //traverse  threejs中深度遍历的方法,会遍历所有的元素
  model.traverse(child => {
   child.material = new THREE.MeshPhongMaterial({   //只能修改mesh的外观
       color: new THREE.Color('#123ca8'),
       transparent: true,
       opacity: 0.5,
       emissiveMap: Mesh.material.map,
    })
 })

//或者,如果没有跟上一级有关系的,推荐用第一种
   model.children.forEach(item => {
      if (item.name !== selectTagName) {
       item.children.forEach(mesh => {
                mesh.material = new THREE.MeshPhongMaterial({
                  color: new THREE.Color('#123ca8'),
                  transparent: true,
                  opacity: 0.5,
                  emissiveMap: mesh.material.map,
                })
              })
		}   
	})

给模型添加点击事件

js 复制代码
document.addEventListener('click', this.handleClick, false);

handleClick(e){
     // 射线交叉计算拾取模型
      const raycaster = new THREE.Raycaster()
      const mouse = new THREE.Vector2()
      let tag = this.tagBox
      mouse.x = (e.offsetX / this.renderer.domElement.clientWidth) * 2 - 1
      mouse.y = -(e.offsetY / this.renderer.domElement.clientHeight) * 2 + 1
      raycaster.setFromCamera(mouse, this.camera); //一定要写,不然获取的就是空

      // 获取点击到的模型的数组,从近到远排列
      const intersects = raycaster.intersectObjects(this.scene.children, true); 
       if (intersects.length > 0) {
       //intersects为点击的模型数组,可以在这里写后续的逻辑
       
       }
}

添加文字标签

可以有多种方式,这里介绍css2drender渲染

在animate中记得要写labelRenderer.render(scene, camera)

js 复制代码
import {
  CSS2DObject, CSS2DRenderer
} from "three/examples/jsm/renderers/CSS2DRenderer";

  // 创建一个CSS2渲染器CSS2DRenderer
      var labelRenderer = new CSS2DRenderer();
      labelRenderer.setSize(window.innerWidth, window.innerHeight);
      labelRenderer.domElement.style.position = 'absolute';
      // 相对标签原位置位置偏移大小
      labelRenderer.domElement.style.top = '0px';
      labelRenderer.domElement.style.left = '0px';
      // //设置.pointerEvents=none,以免模型标签HTML元素遮挡鼠标选择场景模型
      labelRenderer.domElement.style.pointerEvents = 'none';
      document.body.appendChild(labelRenderer.domElement);
  

   function animate() {
        // 通过相机 场景 将结果渲染出来
        let delta = clock.getDelta();
        controls.update(delta);
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        labelRenderer.render(scene, camera); //渲染HTML标签对象
      }
      
	//如果有写resize记得在resize中也要写上
	//labelRenderer.setSize(window.innerWidth, window.innerHeight)
	
      animate()


   // 创建标签
  function tag(name) {
      var div = document.createElement('div');
      div.innerHTML = name;
      div.classList.add('lable-text');
      //div元素包装为CSS2模型对象CSS2DObject
      var label = new CSS2DObject(div);
      div.style.pointerEvents = 'none';//避免HTML标签遮挡三维场景的鼠标事件
      gsap.to(label.position, {
        y: 2,
        repeat: -1,
        duration: 2,
        yoyo: true,
        ease: "Bounce.inOut",
      })

      return label;//返回CSS2模型标签
    }

//在想要创建标签的地方
 var label = tag('办公楼');//把粮仓名称obj.name作为标签
 var pos = new THREE.Vector3();
 model.getWorldPosition(pos);//获取obj世界坐标
 label.position.copy(pos);//位置设置为pos
 model.add(label); //添加到某模型中

ThreeJs内置工具

AxesHelper

辅助坐标系,THREE.AxesHelper()的参数表示坐标系坐标轴线段尺寸大小,你可以根据需要改变尺寸。
默认 y轴向上,x向右,z轴正对我们

红色:x轴

绿色:y轴

蓝色:z轴

js 复制代码
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);

DirectionalLightHelper

辅助查看DirectionalLight光源的位置

js 复制代码
 const dirHelper = new THREE.DirectionalLightHelper(dirLight, 5);  //辅助查看光源在哪里
 scene.add(dirHelper);

如下:

CameraHelper

投影相机,用于模拟相机视锥体的辅助对象.它使用 LineSegments 来模拟相机视锥体.

js 复制代码
 //方向光,常常用来表现太阳光照的效果。(颜色,强度)
   const dirLight = new THREE.DirectionalLight("rgb(253,253,253)", 10);
   dirLight.position.set(200, 200, 10);
   dirLight.castShadow = true;
   const cam = dirLight.shadow.camera;
   const cameraHelper = new THREE.CameraHelper(cam);
   scene.add(cameraHelper);
   cameraHelper.visible = true;
   scene.add(dirLight);

遇到问题描述:

在设置阴影时,只显示部分模型的阴影?

答:因为剩余模型没有在光源的视锥体内,所以没显示阴影,我们可以更改视锥体的大小位置来保证所有元素都显示阴影

js 复制代码
//不一定都设置,根据需要设置
    dirLight.shadow.mapSize.width = 1024; // default
      dirLight.shadow.mapSize.height = 1024; // default
      dirLight.shadow.camera.near = 0.05; // default
      dirLight.shadow.camera.far = 400; // default
      dirLight.shadow.camera.top = 50
      dirLight.shadow.camera.right = 50
      dirLight.shadow.camera.left = -50
      dirLight.shadow.camera.bottom = -50

OrbitControls

平时开发调试代码,或者展示模型的时候,可以通过相机控件OrbitControls实现旋转缩放预览效果。

  • 旋转:拖动鼠标左键
  • 缩放:滚动鼠标中键
  • 平移:拖动鼠标右键

通过鼠标滚轮、左右拖拽可以放大缩小、旋转查看模型,实际上是更改相机的位置

可以根据实际的需要设置,可以放大倍数,旋转的限制等。

因为在过程中一直更改模型,所以调用animate或者监听change保证更新界面

js 复制代码
 // #引入扩展库OrbitCo
     import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";

     let controls = new OrbitControls(camera, renderer.domElement); //创建控件对象
      controls.target.set(0, -1, 0); //相机控件.target属性在OrbitControls.js内部表示相机目标观察点,默认0,0,0
      controls.enableZoom = true;
      controls.update()
      controls.maxPolarAngle = Math.PI / 2  // 最大轨道高度为Π
      controls.minPolarAngle = 0  // 最大轨道高度为Π
      controls.autoRotate = true; //是否开启自动旋转


    const clock = new THREE.Clock();  //初始化时钟
    function animate() {
        // 通过相机 场景 将结果渲染出来
         let delta = clock.getDelta();
         controls.update(delta);
         requestAnimationFrame(animate);
         renderer.render(scene, camera);
      }

   animate()


 //如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
controls.addEventListener('change', function () {
    renderer.render(scene, camera); //执行渲染操作
});//监听鼠标、键盘事件

stats

监听性能,计算three.js的渲染帧率(FPS),所谓渲染帧率(FPS)

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.js

就是一个前端js库,调试用,可以在gui面板更改值,具体的使用的时候可以再深入的了解,这里只是简单的介绍

js 复制代码
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

const gui = new GUI();
//改变交互界面style属性
gui.domElement.style.right = '0px';
gui.domElement.style.width = '300px';

// 通过GUI改变mesh.position对象的xyz属性
gui.add(ambient, 'intensity', 0, 2.0);

//更改模型位置
gui.add(mesh.position, 'x', 0, 180);
gui.add(mesh.position, 'y', 0, 180);
gui.add(mesh.position, 'z', 0, 180);

工具

模型查看

地址:gltf模型查看

可以查看图层、选中部分模型拖拽和模型一些属性值

在线Threejs地址

地址:国内地址

加载比官网快很多,不过是第三方培训机构的,不知道维护到什么时候,暂且用着。

gsap

地址:gsap官网

网上也有很多用tween的,看个人喜欢

动画库

例如:

js 复制代码
   gsap.to(this.door.scale, {
        x: this.door.scale.x * 8, //scale拿到门的缩放值
        duration: 5,
        ease: "power1.inOut",
        onComplete: () => {

        }
      });

连续的动画

js 复制代码
 const t1 = gsap.timeline();
    t1.to(that.door.scale, {
        x: that.door.scale.x / 8, //scale拿到门的缩放值
        duration: 5,
        ease: "power1.inOut",
        onComplete: () => {
          that.carMove = 'z'
        }
      });

      t1.to(model.position, {
        x: 8,
        y: 0,
        z: 24,
        duration: 5,
        ease: "linear",
        onComplete: () => {
          that.carMove = 'x'
          that.closeDoor()
          model.rotateY(Math.PI / 2) //旋转90度
        },
      })
      // 设置循环次数  -1为无限循环
      t1.repeat(-1);

      // 开始播放动画
      t1.play();
相关推荐
gis分享者2 天前
学习threejs,将多个网格合并成一个网格
threejs·网格合并
优雅永不过时·3 天前
three.js 实现 css2d css3d效果 将 二维Dom 和 三维场景结合
前端·javascript·css3·threejs·three
优雅永不过时·4 天前
使用three.js 实现 自定义绘制平面的效果
前端·javascript·平面·github·threejs·着色器
gis分享者6 天前
学习threejs,使用粒子实现下雪特效
threejs·下雪特效
贵州晓智信息科技7 天前
Three.js 物理引擎教程:实现真实物理效果
开发语言·javascript·ecmascript·threejs
奇怪的点8 天前
transformControls THREE.Object3D.add: object not an instance of THREE.Object3D.
threejs
优雅永不过时·13 天前
Three.js 使用着色器 实现跳动的心
前端·javascript·webgl·threejs·three·着色器·1024程序员节
爷傲奈我何!15 天前
Threejs 实现3D 地图(05)3d 地图进场动画和地图边缘动画
vue.js·3d·threejs
爷傲奈我何!20 天前
Threejs 实现3D 地图(01)创建基本场景
vue.js·3d·threejs
gis分享者22 天前
学习threejs,网格深度材质MeshDepthMaterial
threejs·深度网格材质