three.js教程1-快速入门
1、项目开发环境引入threeJs
如果采用的是Vue + threejs或React + threejs技术栈,threejs就是一个js库,直接通过npm命令行安装就行。
npm安装特定版本three.js(注意使用哪个版本,查文档就查对应版本)
// 比如安装148版本 npm install three@0.148.0 --save
// 引入three.js import * as THREE from 'three';
除了three.js核心库以外,在threejs文件包中examples/jsm目录下,还可以看到各种不同功能的扩展库。
// 引入扩展库OrbitControls.js import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 引入扩展库GLTFLoader.js import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
// 扩展库引入——旧版本,比如122, 新版本路径addons替换了examples/jsm import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
2、type="importmap"配置——扩展库引入
通过配置<script type="importmap">
,让学习环境.html文件,也能和vue或react开发环境中一样方式方式引入threejs扩展库。这样你实际项目的开发环境复制课程源码,不用改变threejs引入代码。
<script type="importmap"> { "imports": { "three": "./three.js/build/three.module.js", "three/addons/": "./three.js/examples/jsm/" } } </script>
<script type="module"> // three/addons/路径之后对应的是three.js官方文件包`/examples/jsm/`中的js库 // 扩展库OrbitControls.js import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 扩展库GLTFLoader.js import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; console.log(OrbitControls); console.log(GLTFLoader); </script>
3、3D场景逻辑结构
(1)三维场景Scene
你可以把三维场景Scene对象理解为虚拟的3D场景,用来表示模拟生活中的真实三维场景,或者说三维世界。
// 创建3D场景对象Scene const scene = new THREE.Scene();
(2)物体形状:几何体geometry
Three.js提供了各种各样的几何体API,用来表示三维物体的几何形状
文档搜索关键词geometry
你可以看到threejs提供各种几何体相关API,具体使用方法,也可以参考文档。
//创建一个长方体几何对象Geometry const geometry = new THREE.BoxGeometry(100, 100, 100);
(3)物体外观:材质Material
如果你想定义物体的外观效果,比如颜色,就需要通过材质Material
相关的API实现。
//创建一个材质对象Material const material = new THREE.MeshBasicMaterial({ color: 0xff0000,//0xff0000设置材质颜色为红色 });
(4)物体:网格模型Mesh
实际生活中有各种各样的物体,在threejs中可以通过网格模型Mesh表示一个虚拟的物体,比如一个箱子、一个鼠标。
/ 两个参数分别为几何体geometry、材质material const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
//设置网格模型在三维空间中的位置坐标,默认是坐标原点 mesh.position.set(0,10,0); //通过add方法把网格模型mesh添加到场景中 scene.add(mesh);
4、虚拟相机
透视投影相机PerspectiveCamera
本质上就是在模拟人眼观察这个世界的规律,远小近大,距离越远看着越小,距离越近看着越大。
// 实例化一个透视投影相机对象 const camera = new THREE.PerspectiveCamera();
(1)位置属性position
生活中用相机拍照,你相机位置不同,拍照结果也不同,threejs中虚拟相机同样如此。
//相机在Three.js三维坐标系中的位置 // 根据需要设置相机位置具体值(x,y,z) camera.position.set(200, 200, 200);
(2) 观察目标lookAt()
相机拍照你需要控制相机的拍照目标,具体说相机镜头对准哪个物体或说哪个坐标。对于threejs相机而言,就是设置.lookAt()
方法的参数,指定一个3D坐标。
/相机观察目标指向Threejs 3D空间中某个位置 camera.lookAt(0, 0, 0); //坐标原点 camera.lookAt(mesh.position);//指向mesh对应的位置
注意:如果OrbitControls有target属性,则相机的lookAt属性就失效了
(3)up属性,结果朝向
//默认是( 0, 1, 0 ),默认是y轴朝上 //现在改成z轴朝上 camera.up.set(0, 0, 1)
(4)相机视野范围:视椎体
透视投影相机的四个参数fov, aspect, near, far
构成一个四棱台3D空间,被称为视锥体,只有视锥体之内的物体,才会渲染出来,视锥体范围之外的物体不会显示在Canvas画布上。
// width和height用来设置Three.js输出的Canvas画布尺寸(像素px) const width = 800; //宽度 const height = 500; //高度 //构造器(视野角度fov:默认50, Canvas画布宽高比aspect:默认是1, 近裁截面距离near:默认0.1, 远裁截面距离far:默认2000) const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
5、渲染器
生活中如果有了景物和相机,那么如果想获得一张照片,就需要你拿着相机,按一下,咔,完成拍照。对于threejs而言,如果完成“咔”这个拍照动作,就需要WebGL渲染器WebGLRenderer。
// 创建渲染器对象 const renderer = new THREE.WebGLRenderer(); // 定义threejs输出画布的尺寸(单位:像素px) const width = 800; //宽度 const height = 500; //高度 renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
渲染器WebGLRenderer
执行渲染方法.render()
就可以生成一个Canvas画布(照片),并把三维场景Scene呈现在canvas画布上面,你可以把.render()
理解为相机的拍照动作“咔”。
renderer.render(scene, camera); //执行渲染操作 //Canvas画布插入到div元素中 document.getElementById('webgl').appendChild(renderer.domElement);
渲染器锯齿模糊设置
// 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题 // 不同硬件设备的屏幕的设备像素比window.devicePixelRatio值可能不同 renderer.setPixelRatio(window.devicePixelRatio);//设置设备像素比 renderer.setClearColor(0x444444, 1); //设置背景颜色 renderer.antialias = true, //抗锯齿,平滑
6、坐标轴辅助AxesHelper
用户在三维空间中显示3个坐标轴的对象,坐标轴颜色红R、绿G、蓝B分别对应坐标系的x、y、z轴,对于three.js的3D坐标系默认y轴朝上。
// AxesHelper:辅助观察的坐标系,辅助开发调试,项目正式发布时隐藏 const axesHelper = new THREE.AxesHelper(150); scene.add(axesHelper);
7、光源光照
实际生活中物体表面的明暗效果是会受到光照的影响,threejs中同样也要模拟光照Light
对网格模型Mesh
表面的影响。
threejs提供的网格材质,有的受光照影响,有的不受光照影响。
基础网格材质MeshBasicMaterial不会受到光照影响(有光源和没有光源,它都会显示颜色和材质)。
漫反射网格材质MeshLambertMaterial会受到光照影响(如果没有光源,它就是暗的,不会显示颜色和材质),该材质也可以称为Lambert网格材质,音译为兰伯特网格材质。不同面和光线夹角不同,立方体不同面就会呈现出来不同的明暗效果。
高光(镜面)网格材质MeshPhongMaterial
可以提供一个高光反射效果。在太阳下面观察一辆车,你会发现在特定角度和位置,你可以看到车表面某个局部区域非常高亮。(高光亮度属性.shininess,高光颜色属性.specular)
(1)环境光AmbientLight
环境光AmbientLight没有特定方向,只是整体改变场景的光照明暗。
//环境光:没有特定方向,整体改变场景的光照明暗 const ambient = new THREE.AmbientLight(0xffffff, 0.4); scene.add(ambient);
(2)其他发光光源:点光源、平行光、聚光
//点光源:两个参数分别表示光源颜色和光照强度 // 参数1:0xffffff是纯白光,表示光源颜色 // 参数2:1.0,表示光照强度,可以根据需要调整 const pointLight = new THREE.PointLight(0xffffff, 1.0); //点光源位置 pointLight.position.set(400, 0, 0);//点光源放在x轴上 scene.add(directionalLight); //点光源添加到场景中
// 平行光 const directionalLight = new THREE.DirectionalLight(0xffffff, 1); // 设置光源的方向:通过光源position属性和目标指向对象的position属性计算 directionalLight.position.set(80, 100, 50); // 方向光指向对象网格模型mesh,可以不设置,默认的位置是0,0,0 directionalLight.target = mesh; scene.add(directionalLight);
(3)点光源辅助PointLightHelper
// 光源辅助 const pointLightHelper = new THREE.PointLightHelper(pointLight, 10); scene.add(pointLightHelper);
8、相机轨道控制器OrbitControls
OrbitControls可用于实现三维场景的放大缩小、旋转和平移等操作。
OrbitControls本质上就是改变相机的参数,比如相机的位置属性,改变相机位置也可以改变相机拍照场景中模型的角度,实现模型的360度旋转预览效果,改变透视投影相机距离模型的距离,就可以改变相机能看到的视野范围。
旋转:拖动鼠标左键
缩放:滚动鼠标中键
平移:拖动鼠标右键
// 引入轨道控制器扩展库OrbitControls.js import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const controls = new OrbitControls( camera, renderer.domElement ); // requestAnimationFrame实现周期性循环执行 // requestAnimationFrame默认每秒钟执行60次,但不一定能做到,要看代码的性能 function render() { renderer.render(scene, camera); //执行渲染操作 mesh.rotateY(0.01);//每次绕y轴旋转0.01弧度 requestAnimationFrame(render);//请求再次执行函数render } render();
注意相机控件OrbitControls会影响lookAt设置,注意手动设置OrbitControls的目标参数
// 设置相机控件轨道控制器OrbitControls const controls = new OrbitControls(camera, renderer.domElement); // 相机控件.target属性在OrbitControls.js内部表示相机目标观察点,默认0,0,0 controls.target.set(1000, 0, 1000); controls.update();//update()函数内会执行camera.lookAt(controls.targe)
9、canvas画布全屏显示
需要监控浏览器窗口的变化,一旦canvas画布宽高度动态变化,需要更新相机和渲染的参数,否则无法正常渲染。
<style>
body{
overflow: hidden;
margin: 0px;
}
</style>
// width和height用来设置Three.js输出的Canvas画布尺寸(像素px) const width = window.innerWidth; //窗口文档显示区的宽度作为画布宽度 const height = window.innerHeight; //窗口文档显示区的高度作为画布高度 const renderer = new THREE.WebGLRenderer(); document.body.appendChild(renderer.domElement); // onresize 事件会在窗口被调整大小时发生 window.onresize = function () { // 重置渲染器输出画布canvas尺寸 renderer.setSize(window.innerWidth, window.innerHeight); // 全屏情况下:设置观察范围长宽比aspect为窗口宽高比 camera.aspect = window.innerWidth / window.innerHeight; // 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix // 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源) // 如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵 camera.updateProjectionMatrix(); };
10、stats查看threejs渲染帧率
three.js每执行WebGL渲染器.render()
方法一次,就在canvas画布上得到一帧图像,不停地周期性执行.render()
方法就可以更新canvas画布内容,一般场景越复杂往往渲染性能越低,也就是每秒钟执行.render()
的次数越低。
通过stats.js库可以查看three.js当前的渲染性能,具体说就是计算three.js的渲染帧率(FPS),所谓渲染帧率(FPS),简单说就是three.js每秒钟完成的渲染次数,一般渲染达到每秒钟60次为最佳状态。
stats.js下载链接:https://github.com/mrdoob/stats.js
//引入性能监视器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() { //循环调用方法update(),来刷新时间 stats.update(); renderer.render(scene, camera); //执行渲染操作 requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧 } render();
下面展示一个完整案例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Three.js中文网:http://www.webgl3d.cn/</title> <style> body{ overflow: hidden; margin: 0px; } </style> </head> <body> <!-- type="importmap"功能:.html文件中也能和nodejs开发环境中一样方式,引入npm安装的js库 --> <script type="importmap"> { "imports": { "three": "../../../three.js/build/three.module.js", "three/addons/": "../../../three.js/examples/jsm/" } } </script> <script type="module"> import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; //引入性能监视器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); // 三维场景 const scene = new THREE.Scene(); // 创建网格模型对象 const geometry = new THREE.BoxGeometry(100, 100, 100); // 漫反射网格材质;MeshLambertMaterial const material = new THREE.MeshLambertMaterial({ color: 0x00ffff, //设置材质颜色 transparent: true, //开启透明 opacity: 0.5, //设置透明度 }); const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh scene.add(mesh); //网格模型添加到场景中 //辅助观察的坐标系 const axesHelper = new THREE.AxesHelper(100); scene.add(axesHelper); //光源设置 const pointLight = new THREE.PointLight(0xffffff, 1.0); pointLight.position.set(400, 200, 300); scene.add(pointLight); const ambient = new THREE.AmbientLight(0xffffff, 0.4); scene.add(ambient); //渲染器和相机 const width = window.innerWidth; const height = window.innerHeight; const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000); camera.position.set(292, 223, 185); camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer(); renderer.setSize(width, height); document.body.appendChild(renderer.domElement); // 渲染循环 function render() { stats.update();//渲染循环中执行stats.update()来刷新时间 renderer.render(scene, camera); mesh.rotateY(0.01); requestAnimationFrame(render); } render(); const controls = new OrbitControls(camera, renderer.domElement); // 画布跟随窗口变化 window.onresize = function () { renderer.setSize(window.innerWidth, window.innerHeight); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); }; </script> </body> </html>
文章中部分素材选取自Threejs中文网:http://www.webgl3d.cn/