添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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分别对应坐标系的xyz轴,对于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">
    <meta charset="UTF-8">
    <title>Three.js中文网:http://www.webgl3d.cn/</title>
    <style>
        body{
            overflow: hidden;
             margin: 0px;
    </style>
</head>
    <!-- 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/