threejs基础入门

151 阅读8分钟

个人笔记--基础篇

什么是threejs

Three.js是一个跨浏览器的脚本,使用JavaScript函数库或API来在网页浏览器中创建和展示动画的三维计算机图形。Three.js使用WebGL。源代码托管在GitHub。Three.js允许使用JavaScript创建网页中的GPU加速的3D动画元素,而不是使用特定的浏览器插件。webgl封装成便于用户使用的Three.js ,类似jquery封装了JavaScript。

什么是webgl

WebGL是一种JavaScript API,用于在不使用插件的情况下在任何兼容的网页浏览器中呈现交互式2D和3D图形。WebGL完全集成到浏览器的所有网页标准中,可将影像处理和效果的GPU加速使用方式当做网页Canvas的一部分。WebGL元素可以加入其他HTML元素之中并与网页或网页背景的其他部分混合,WebGL技术结合了HTML5和 Java Script,允许开发者在网页(Web页面)上创建和渲染三维图形。

threejs入门

参考资料:

threejs官网:threejs.org/

threejs英文文档:threejs.org/docs/index.…

threejs中文文档:www.yanhuangxueyuan.com/threejs/doc…

threejs零基础入门教程:www.yanhuangxueyuan.com/Three.js/

threejs博客教程:www.wenjiangs.com/doc/kl5y4qv…

threejs通用文档:threejs.org/manual/#zh/…

threejs编辑器:threejs.org/editor/

threejs github:github.com/mrdoob/thre…

安装

参照:threejs.org/docs/index.…

项目启动运行

1、vite或者vue-cli等框架启动,直接运行命令行启动

npm start
//或
npm run dev

2、从 CDN 导入,使用服务器启动。常用的两种启动方式如下:

如果是vscode编辑工具启动,安装插件:Live Server,并在html页面中右键,点击Open with Live Server。

如果用cmd命令行启动

//全局安装依赖包
npm install -g http-server
//切换到HTML文件所在的目录
cd /path/to/your/html/files
//cmd命令
http-server --bind your-ip-address

场景搭建

threejs由场景、相机、渲染器、灯光、控制器等几个要素组成。每个要素都有不同的类型,例如光照有太阳光、环境光、半球光等等,每中光照都有不同的属性可以进行配置。

场景(scene):场景是所有物体的容器,如果要显示一个物体,就需要将物体对象加入场景中。

let scene = new THREE.Scene();

相机(camera):相机决定了场景中那个角度的景色会显示出来。相机就像人的眼睛一样,人站在不同位置,抬头或者低头都能够看到不同的景色。

let camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000)

渲染器(renderer):渲染器决定了渲染的结果应该画在页面的什么元素上面

let renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
//挂载到页面
document.body.appendChild(renderer.domElement)

灯光(light):模拟现实环境中的光照

let directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);

控制器(controls):对3D场景进行旋转、放大缩小等操作

let controls = new OrbitControls(camera, renderer.domElement);

threejs.png

简单案例

<!--
 * @Descripttion: 
 * @Date: 2022-07-19 14:44:28
-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    * {
      margin: 0;
      padding: 0;
      overflow: hidden;
      box-sizing: border-box;
    }
  </style>

  <body>
    <div id="container"></div>
  </body>
  <script type="importmap">
    {
      "imports": {
        "three": "./build/three.module.js"
      }
    }
  </script>

  <script type="module">
    import * as THREE from "three";
    import { OrbitControls } from "./jsm/controls/OrbitControls.js";
 

    let camera, scene, renderer, controls;
    const container = document.getElementById("container");

    /* 场景 */
    function initScene() {
      scene = new THREE.Scene();
      scene.background = new THREE.Color("#7DD5EE");

      var axes = new THREE.AxesHelper(100);
      scene.add(axes);
    }

    /* 相机 */
    function initCamera() {
      camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 5000);
      camera.position.set(0, 200, 400);
    }

    /* 渲染器 */
    function initRender() {
      renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);
      container.appendChild(renderer.domElement);
    }

    /* 灯光 */
    function initLight() {
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
      directionalLight.position.set(50, 200, 100);
    }

    /* 控制器 */
    function initControls() {
      controls = new OrbitControls(camera, renderer.domElement);
    }

    /* 窗口变动触发 */
    function onWindowResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    }

    function animate() {
      // 设置纹理偏移
      requestAnimationFrame(animate);
      render();
    }

    /* 渲染内容 */
    function initContent() {
      const geometry = new THREE.BoxGeometry(1, 1, 1);
      const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
      const cube = new THREE.Mesh(geometry, material);
      scene.add(cube);
    }

    function render() {
      renderer.render(scene, camera);
      controls.update();
    }

    function init() {
      initScene();
      initCamera();
      initRender();
      initLight();
      initControls();
      initContent();
      window.addEventListener("resize", onWindowResize, false);
    }

    /* 初始加载 */
    (function () {
      init();
      animate();
    })();
  </script>
</html>

添加几何体

添加几何体流程:

  1. 声明几何体形状。例如:正方体,长方体。
  2. 声明几何体材质。例如:金属材质。
  3. 使用网格模型mesh组合形状与材质,形成物体实例。
  4. 将物体实例添加到场景中。
const geometry = new THREE.BoxGeometry(100, 100, 100);
const material = new THREE.MeshBasicMaterial({ color: 'blue' });
const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
scene.add(mesh);

常见几何体有:

image.png

常见材质有:

image.png

模型导入

threejs常使用到的模型格式分为:obj模型、gltf/glb模型、fbx模型、json模型。这些模型可以有3DMAX、Blender、Maya等建模软件进行建模并导出,由开发人员拿到模型后利用threejs api将模型导入到3D场景中。

例如gltf模型导入

第一步:引入相关加载器包文件

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

第二步:导入模型

//声明要使用的加载器
const loader = new GLTFLoader();
//导入模型
loader.load('models/gltf/duck/duck.gltf',
     //回调函数:获取模型数据,添加到场景中
	function ( gltf ) {
		scene.add( gltf.scene );
	},
	//回调函数:获取模型加载的进度相关参数
	function ( xhr ) {
		console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
	},
	//回调函数:获取加载中的报错信息
	function ( error ) {
		console.log( 'An error happened' );
	}
);

由此便将模型导入到场景中,打开项目即可看到模型。打印模型数据console.log(gltf.scene),控制台得到以下数据。

image-20240226091230471.png

第三步:模型交互

//设置位置
plane.position.x=10;
plane.position.y=10;
plane.position.z=10;

plane.position.set(10,10,10)

plane.position=new THREE.Vector3(10,10,10)
//缩放
plane.scale.set(0.5, 0.5, 0.5);
//旋转
plane.rotation.x = -0.5 * Math.PI;

plane.rotation.set(-0.5 * Math.PI,0,0)

plane.rotation=new THREE.Vector3(-0.5 * Math.PI,0,0)
//如果想使用度数(0到360)设置旋转。
var degrees=45
var inRadius=degree * (Math.PI/180)
//平移
plane.translateX(4)

个人笔记--进阶项目篇

原生项目框架

新建入口文件index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  
  <body>
    <div id="container"></div>
  </body>
  <script type="importmap">
    {
      "imports": {
        "three": "./build/three.module.js"
      }
    }
  </script>

  <script type="module">
    import * as THREE from "three";

    ...开发代码
  </script>
</html>

其他资源采用script/link方式引入

Vite构建工具框架

1、安装

//自主选择
npm create vite@latest
//或
npm create vite@latest my-vue-app -- --template vanilla

2、项目设置 新建src文件夹,并建立自己的文件分类(img/js...),并在html文件中引入。

vite.config.js配置入口文件,index.html或main.js

import { defineConfig } from "vite";
import { resolve } from "path";
import path from "path";

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        index: resolve(__dirname, "index.html"),
      },
    },
  },
  server: {
    port: "9000", //端口
    host: true,
    open: true, //服务启动时自动在浏览器中打开应用
  },
  resolve: {
    alias: {
      //别名配置
      "~": path.resolve(__dirname, "./"), //配置的别名
      "@": path.resolve(__dirname, "./src"),
    },
  },
});

Parce构建工具框架

1、安装

在开始之前,您需要安装 Node 和 Yarn 或 npm,并为您的项目创建一个目录。然后,使用 Yarn 将 Parcel 安装到您的应用程序中:

npm install --save-dev parcel
//或
yarn add --dev parcel

2、项目设置

现在已经安装了 Parcel,让我们为我们的应用程序创建一些源文件。Parcel 接受任何类型的文件作为入口点,但 HTML 文件是一个很好的起点。Parcel 将从那里遵循您的所有依赖项来构建您的应用程序。
创建src文件夹,并且创建index.html文件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="./assets/css/style.css" />
  </head>
  <body>
    <script src="./main/main.js" type="module"></script>
  </body>
</html>

创建一个main.js

import * as THREE from "three";

// console.log(THREE);

// 目标:了解three.js最基本的内容

// 1、创建场景
const scene = new THREE.Scene();

// 2、创建相机
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

// 设置相机位置
camera.position.set(0, 0, 10);
scene.add(camera);

// 添加物体
// 创建几何体
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 根据几何体和材质创建物体
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
// 将几何体添加到场景中
scene.add(cube);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// console.log(renderer);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);

// 使用渲染器,通过相机将场景渲染进来
renderer.render(scene, camera);

3、打包脚本

到目前为止,我们一直在parcel直接运行 CLI,但在您的package.json文件中创建一些脚本以简化此操作会很有用。我们还将设置一个脚本来使用该命令构建您的应用程序以进行parcel build最后,您还可以使用该字段在一个地方声明您的source,这样您就不需要在每个parcel命令中重复它们。
package.json:

{
  "name": "01-three_basic",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "parcel src/index.html",
    "build": "parcel build src/index.html"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "parcel": "^2.4.1"
  },
  "dependencies": {
    "dat.gui": "^0.7.9",
    "gsap": "^3.10.3",
    "three": "^0.139.2"
  }
}

现在您可以运行yarn build以构建您的生产项目并yarn dev启动开发服务器。

常用方法篇

设置物体旋转方向和位置

 //设置位置
plane.position.x=10;
plane.position.y=10;
plane.position.z=10;

plane.position.set(10,10,10)

plane.position=new THREE.Vector3(10,10,10)
//缩放
plane.scale.set(0.5, 0.5, 0.5);
//旋转
plane.rotation.x = -0.5 * Math.PI;

plane.rotation.set(-0.5 * Math.PI,0,0)

plane.rotation=new THREE.Vector3(-0.5 * Math.PI,0,0)
//如果想使用度数(0到360)设置旋转。
var degrees=45
var inRadius=degree * (Math.PI/180)
//平移
plane.translateX(4)

常用特效篇

label标签

image.png

   import { CSS3DRenderer, CSS3DSprite } from "../jsm/renderers/CSS3DRenderer.js";
   
   var planeInfo = document.createElement("div");
   planeInfo.className = "label";
   planeInf2.innerHTML = "CSS3DSprite";
   infoModal = new CSS3DSprite(planeInfo);
   infoModal.position.set(-20, 20, 20);
   scene.add(infoModal);
      
   labelRenderer2 = new CSS3DRenderer();
   labelRenderer2.setSize(window.innerWidth, window.innerHeight);
   labelRenderer2.domElement.style.position = "absolute";
   labelRenderer2.domElement.style.top = "0px";
   // labelRenderer.domElement.style.pointerEvents = 'none';
   container.appendChild(labelRenderer2.domElement);
   
   function render() {
      renderer.render(scene, camera);
      labelRenderer.render(scene, camera);
      labelRenderer2.render(scene, camera);
      controls.update();
    }

其他插件

性能检测

image.png

  import Stats from './jsm/libs/stats.module.js';
  
  let stats = new Stats();
  container.appendChild( stats.dom );
  
  function animate() {
    requestAnimationFrame( animate );
    render();
    stats.update();
  }
  

补间动画

  <script src="./js/tween.min.js"></script>
  
  function startTween() {
      // 创建一个 tween 动画对象
      var tween = new TWEEN.Tween(camera.position)
        .to({ x: 20, y: 20, z: 20 }, 5000) // 定义结束位置和时间
        .easing(TWEEN.Easing.Quadratic.Out) // 指定缓动函数
        .onUpdate(function () {
          // 动画更新回调函数,每次更新时执行
          console.log(camera.position);
        })
        .onComplete(function () {
          // 动画完成后执行的回调函数
          console.log("Animation completed");
        });

      // 开始动画
      tween.start();
    }

水面

image.png

  import { Water } from './jsm/objects/Water.js';
  
  var waterGeometry = new THREE.PlaneBufferGeometry(10000, 10000);

  let water = new Water(
      waterGeometry,
      {
        textureWidth: 512,
        textureHeight: 512,
        waterNormals: new THREE.TextureLoader().load('./assets/img/waternormals.jpg', function (texture) {
          texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
        }),
        alpha: 1.0,
        sunDirection: light.position.clone().normalize(),
        sunColor: 0xffffff,
        waterColor: 0x00456e,
        distortionScale: 5.7,
        fog: scene.fog !== undefined
      }
    );
    water.rotation.x = - Math.PI / 2;
    scene.add(water);
    
  function render() {
    renderer.render(scene, camera);
    controls.update()
    water.material.uniforms[ 'time' ].value += 1.0 / 60.0;
  }
  

太阳光

image.png

    import { Lensflare, LensflareElement } from "./jsm/objects/Lensflare.js";
    
    const pointLight = new THREE.PointLight(0xffffff, 1.2, 2000);
    pointLight.color.setHSL(0.995, 0.5, 0.9);
    pointLight.position.set(1000, 45, -500);
    const textureLoader = new THREE.TextureLoader();
    const textureFlare0 = textureLoader.load("./练习模型/模型5/lensflare0.png");
    const textureFlare1 = textureLoader.load("./练习模型/模型5/lensflare1.png");
    // 镜头光晕
    const lensflare = new Lensflare();
    lensflare.addElement(new LensflareElement(textureFlare0, 600, 0, pointLight.color));
    lensflare.addElement(new LensflareElement(textureFlare1, 60, 0.6));
    lensflare.addElement(new LensflareElement(textureFlare1, 70, 0.7));
    lensflare.addElement(new LensflareElement(textureFlare1, 120, 0.9));
    lensflare.addElement(new LensflareElement(textureFlare1, 70, 1));
    pointLight.add(lensflare);
    scene.add(pointLight);