一、引入threejs依赖包
1. 终端安装three依赖包
npm i three -S
2. 组件引入threejs
import * as THREE from 'three';
二、创建threejs容器
<template>
<div>
<canvas id="three"></canvas>
</div>
</template>
三、创建threejs应用场景
三要素:scene,render,carmea
1.创建scene
const scene = new THREE.Scene();
scene可以理解为我们将要渲染的环境、背景
scene.background = new THREE.Color("#eee");
2. 获取threejs的容器(render)
const threeDemo = document.getElementById("three");
创建WebGLRenderer,将容器配置参数传入
const renderer = new THREE.WebGLRenderer({canvas: threeDemo, antialias: true});
3.创建threejs的相机(carmea)
常用两种相机实例:
PerspectiveCamera(透视摄像机): 模拟人眼所看到的景象,物体的大小会受远近距离的影响,它是3D场景的渲染中使用得最普遍的投影模式。
OrthographicCamera(正交投影摄像机): 不具有透视效果,即物体的大小不受远近距离的影响;
const camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 0.1, 1000 )
PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )具有四个参数:
- fov — 摄像机视锥体垂直视野角度。可以理解为人类的视野广度。
- aspect — 摄像机视锥体横纵比。渲染结果的横向尺寸和纵向尺寸的比值,这里使用的是 浏览器窗口的宽高比。
- near — 摄像机视锥体近端面。一切比近面更近的事物将不被渲染。
- far — 摄像机视锥体远端面。一切比远面更远的事物将不被渲染,但是设置过大可能会影响性能。
生成的camera默认是放在中心点(0,0,0)的,但这是待会模型要放的位置,因此,我们把摄像机挪个位置:
camera.position.z = 10
4. Three.js 需要一个动画循环函数,Three.js 的每一帧都会执行这个函数。
function annimate(scene, renderer, camera) { renderer.render(scene, camera); requestAnimationFrame(annimate); }
现在生成了一个灰色背景没有任何物体的threejs场景,vue代码:
<template> <div> <canvas id="three"></canvas> </div> </template> <script setup> import * as THREE from 'three'; import { onMounted } from "vue"; function initThree() { const scene = new THREE.Scene(); scene.background = new THREE.Color("#eee"); const threeDemo = document.getElementById("three"); const renderer = new THREE.WebGLRenderer({canvas: threeDemo, antialias: true}); const camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 0.1, 1000 ) camera.position.z = 10; function annimate() { renderer.render(scene, camera); requestAnimationFrame(annimate); } annimate() }; onMounted(() => { initThree(); }) </script> <style scoped lang="less"> #three { width: 100vw; height: 100vh; position: absolute; top: 0; left: 0; } </style>
四、引入3D模型
1. 引入threejs加载3d模型的js文件
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
可根据不同的3D模型文件格式引入不同的加载文件
- fbx - import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
- obj - import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
collada - import { ColladaLoader } from "three/examples/jsm/loaders/ColladaLoader";
gltf - import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
2. 获取3D模型文件
获取sketchfab3D模型文件链接
https://sketchfab.com/
下载好后,解压,放进项目文件的public目录。
3. 声明一个加载器,加载我们下载的模型,并把它添加到场景中
const gltfLoader = new GLTFLoader(); gltfLoader.load("/seraphine/scene.gltf", (gltf) => { let model = gltf.scene; scene.add(model); })
场景里有了模糊的黑色的小人,这是因为我们还没有给她添加纹理。
4. 使用threejs给3d模型添加纹理
gltfLoader.load("/seraphine/scene.gltf", (gltf) => { let model = gltf.scene; // 添加以下代码 // 遍历模型 model.traverse((obj) => { // 将图片作为纹理加载 let imgTexture = new THREE.TextureLoader().load("/seraphine/textures/Mat_cwfyfr1_userboy17.bmp_diffuse.png"); // 调整纹理图的方向 imgTexture.flipY = false; // 将纹理图生成材质 const material = new THREE.MeshBasicMaterial({ map: imgTexture, }) obj.material = material; }) scene.add(model); })
此时,小人就带了颜色了
模糊原因:原因是设备的物理像素分辨率与CSS像素分辨率的比值的问题,我们的canvas绘制出来后图片因为高清屏设备的影响,导致图片变大,然而我们在浏览器的渲染窗口并没有变大,因此图片会挤压缩放使得canvas画布会变得模糊。
修改它我们要用到devicePixelRatio这个属性(此属性返回当前显示设备的物理像素分辨率与CSS像素分辨率的比值。该值也可以被解释为像素大小的比例:即一个CSS像素的大小相对于一个物理像素的大小的比值。)
添加函数:
function resizeDevicePixel(renderer) { const canvas = renderer.domElement let width = window.innerWidth let height = window.innerHeight let devicePixelWidth = canvas.width / window.devicePixelRatio let devicePixelHeight = canvas.height / window.devicePixelRatio const needResize = devicePixelWidth !== width || devicePixelHeight !== height if (needResize) { renderer.setSize(width, height, false) } return needResize }
在animate函数内调用它:
function annimate() { renderer.render(scene, camera); requestAnimationFrame(annimate); // 添加以下代码 if(resizeDevicePixel(renderer)) { const canvas = renderer.domElement; camera.aspect = canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); } }
5. 添加轨道控制器 让模型动起来
引入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
创建轨道控制变量
const controls = new OrbitControls(camera, renderer.domElement);
在annimate函数调用(要写在最前面)
controls.update()
现在拉近可以看见近脸
五、添加光与影子
先加个地板,Three.js里物体(一般叫网格Mesh)由两部分构成,一是它的形状,二是它的材质,我们给地板创建它们:
let floorGeometry = new THREE.PlaneGeometry(3000, 3000) let floorMaterial = new THREE.MeshPhongMaterial({ color: "#7e7ab0" })
平面几何体,PlaneGeometry(width : Float, height : Float, widthSegments : Integer, heightSegments : Integer)
- width — 平面沿着X轴的宽度。默认值是1。
- height — 平面沿着Y轴的高度。默认值是1。
- widthSegments — (可选)平面的宽度分段数,默认值是1。
- heightSegments — (可选)平面的高度分段数,默认值是1。
Phong网格材质(MeshPhongMaterial):是一种用于具有镜面高光的光泽表面的材质。
生成Mesh,并添加到场景中:
let floorMesh = new THREE.Mesh(floorGeometry, floorMaterial); floorMesh.rotation.x = -0.5 * Math.PI; floorMesh.receiveShadow = true; floorMesh.position.y = -0.001; scene.add(floorMesh);
现在还是黑的,还需要加光
添加平行光:
const dirLight = new THREE.DirectionalLight(0xffffff, 0.6) //光源等位置 dirLight.position.set(-10, 8, -5) //可以产生阴影 dirLight.castShadow = true dirLight.shadow.mapSize = new THREE.Vector2(1024, 1024) scene.add(dirLight)
平行光一般用来模拟太阳光,DirectionalLight( color : Integer, intensity : Float )
- color - (可选参数) 16进制表示光的颜色。 缺省值为 0xffffff (白色)。
- intensity - (可选参数) 光照的强度。缺省值为1。
添加半球光光源:
const hemLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6) hemLight.position.set(0, 48, 0) scene.add(hemLight)
半球光光源直接放置于场景之上,光照颜色从天空光线颜色渐变到地面光线颜色。
- skyColor - (可选参数) 天空中发出光线的颜色。 缺省值 0xffffff。
- groundColor - (可选参数) 地面发出光线的颜色。 缺省值 0xffffff。
- intensity - (可选参数) 光照强度。 缺省值 1。
想要产生影子,还需要在renderer下添加:
const renderer = new THREE.WebGLRenderer({ canvas: threeDemo, antialias: true }); // ++++++ renderer.shadowMap.enabled = true;
以及:
model.traverse((obj) => { // 将图片作为纹理加载 let imgTexture = new THREE.TextureLoader().load("/seraphine/textures/Mat_cwfyfr1_userboy17.bmp_diffuse.png"); // 调整纹理图的方向 imgTexture.flipY = false; // 将纹理图生成材质 const material = new THREE.MeshBasicMaterial({ map: imgTexture, }) obj.material = material; //加这句,让模型等每个部分都能产生阴影 if (obj.isMesh) { obj.castShadow = true obj.receiveShadow = true } })
给场景添加雾化效果:
const scene = new THREE.Scene(); scene.background = new THREE.Color("#eee"); // +++++ scene.fog = new THREE.Fog('#eee', 20, 100)
最终效果: