赞
踩
threejs 官网 :https://threejs.org/
threejs 案例: https://threejs.org/examples/#webgl_animation_keyframes
threejs API:https://threejs.org/docs/index.html#manual/zh/introduction/Creating-a-scene
开始使用
npm install --save three;
全局引用
import * as THREE from 'three';
按需引用 ,demo均使用全局
import {class} from 'three';
package-lock.json
- "three": "^0.151.3",
-
- "three-orbitcontrols": "^2.110.3",
-
- "vue": "^2.6.14",
三大核心
- new THREE.WebGLRenderer()// 创建渲染器
-
- new THREE.Scene() // 实例化场景
-
- new THREE.PerspectiveCamera()// 实例化相机
创建四个文件方便场景管理
渲染控制器ThreeController.js 作为 3d渲染的主要入口文件
ThreeController.js
- import * as THREE from 'three';
-
- export const renderer = new THREE.WebGLRenderer() // 创建渲染器
-
- export const scene = new THREE.Scene() // 实例化场景
-
- export const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000) 相机
-
- export class ThreeController {
-
- Model = null;
-
- scene = null;
-
- constructor(Model) { //构造器函数
-
- Model.appendChild(renderer.domElement) //容器
-
- renderer.setSize(Model.offsetWidth, Model.offsetHeight, true)
-
- this.Model = Model
-
- this.scene = scene
-
- camera.position.set(100, 100, 100) // 设置相机位置
-
- camera.lookAt(new THREE.Vector3(0, 0, 0)) // 设置相机看先中心点
-
- camera.up = new THREE.Vector3(0, 1, 0) // 设置相机自身的方向
-
- renderer.shadowMap.enabled = true;
-
- renderer.render(scene, camera);
-
- }
-
- // 外部访问将模型添加到场景中
-
- addObject(...object) {
-
- object.forEach(elem => {
-
- this.scene.add(elem)
-
- })
-
- }
}
HomeView.vue
-
-
- <template>
-
- <div>
-
- <div class="three-canvas" ref="threeTarget"></div>
-
- </div>
-
- </template>
-
-
-
- import {ThreeController,} from "@/components/ThreeController";
-
- import { ModelListConfig} from"@/components/ModelListConfig";
-
- import { LightList } from "@/components/LightListConfig";
-
- import { allHelper } from "@/components/AxesHelperConfig";
-
- return { ThreeController: null,
-
-
-
- }
-
- mounted() {
-
- this.init();
-
- },
-
- methods: {
-
-
- init() {
-
- this.ThreeController = newThreeController(this.$refs.threeTarget);
-
- this.ThreeController.addObject(...ModelListConfig);
-
- this.ThreeController.addObject(...LightList);
-
- this.ThreeController.addObject(...allHelper);
-
- },
此时场景中一片漆黑
接下来添加模型 ModelListConfig.js
- import * as THREE from 'three';
-
- 具体模型类型参考Api
-
- export const ModelListConfig = [] 存储模型数组,也可某个模型单独导出
-
- const sky = new THREE.TextureLoader().load(Require('sky.jpg'))
-
- 创建材质贴图
-
- export const MeshModel = new THREE.Mesh( 创建几何体
-
- new THREE.BoxGeometry(20, 20, 20), 正方体 size 20
-
- new THREE.MeshStandardMaterial({ 材质纹理
-
- color: 'rgb(36, 172, 242)',
-
- // roughness: 0 , 光滑度 0最光滑
-
- // metalness: 0, 金属度 1最像金属
-
- map: sky
-
- })
-
- )
-
- ModelListConfig.push(MeshModel) 将模型添加到数组中
-
-
-
- // 多人开发用来存储数据
-
- MeshModel.userData = {
-
- name: 'MeshModel',
-
- user: '我是正方体模型'
-
- }
可以看到场景依旧是漆黑,所以要“开灯”即添加光线
接下来添加光线 LightListConfig.js
- import * as THREE from 'three';
- export const LightList = []
- // // 添加环境光(自然光),设置自然光的颜色,设置自然光的强度(0 最暗, 1 最强)
- const hemiLight = new THREE.HemisphereLight("#A09E9E", 0.5);
- hemiLight.position.set(0, 40, 15);
- LightList.push(hemiLight)
- const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5);
- LightList.push(hemiLighthelper)
- export const pointLight = new THREE.PointLight(
- 'rgb(255,255,255)',
- 0.7,
- 600,
- 0.2
- )
- pointLight.position.set(0, 1, 50) // 设置点光源位置 (x,y,z)
- LightList.push(pointLight)
这时模型就可以正常显示了
创建辅助线 AxesHelperConfig.js
- import { AxesHelper ,GridHelper } from 'three'
- /**
- * 场景中添加辅助线
- * @param {allHelper.push(new AxesHelper())}
- * 添加栅格
- * @param {allHelper.push(new GridHelper())}
- */
- export const allHelper = []
- // 坐标辅助
- export const axesHelper = new AxesHelper(200) // 创建坐标辅助 (500 为辅助线的长度)
- export const gridHelper = new GridHelper(500, 20, 'green', 'rgb(255, 255, 255)')
- allHelper.push(gridHelper,axesHelper) // 添加到辅助列表
-
- /*
- gridHelper Config
- size -- 坐标格尺寸. 默认为 10. 这就是网格组成的大正方形最大是多少
- divisions -- 坐标格细分次数. 默认为 10. 组成的最大的正方向平均分多少份
- colorCenterLine -- 中线颜色. 值可以为 Color 类型, 16进制 和 CSS 颜色名. 默认为 0x444444。这个是指网格和坐标的 x轴 z 轴重合线的颜色。
- colorGrid -- 坐标格网格线颜色. 值可以为 Color 类型, 16进制 和 CSS 颜色名. 默认为 0x888888
- */
效果
相机视角拖拽 ThreeController.js 需要射线控制器 OrbitControls 因为渲染成是三维之后,我们点击的是相机呈现的二维浏览器画面,距离模型到页面上是有一定距离的,简单来说就像我们站在一个物体面前,用手机拍照时,点击的照片中的物体,实际上我们点击的并不是物体,而是相机渲染给我们的一个二维照片。OrbitControls,会穿透整个三维场并返回一个list,第[0]项就是我们想要点击模型。
ThreeController.js
- import { OrbitControls }from'three/examples/jsm/controls/OrbitControls'
- import { TransformControls } from 'three/examples/jsm/controls/TransformControls'
- const mouse = new THREE.Vector2() // 初始化鼠标位置
- const raycaster = new THREE.Raycaster()//初始化射线发射器
- // 屏幕鼠标x,屏幕鼠标y 视图宽度,视图高度
- let x = 0; let y = 0; let width = 0; let height = 0
- constructor(Model) {
- …
- EventInjection(camera) //要在渲染器之前
- …
- renderer.render(scene, camera);
-
- }
- export const EventInjection=()=>{
- // 鼠标移动事件
- const transformControls = new TransformControls(camera, renderer.domElement)
- renderer.domElement.addEventListener("mousemove", event => {
- x = event.offsetX
- y = event.offsetY
- width = renderer.domElement.offsetWidth
- height = renderer.domElement.offsetHeight
- mouse.x = x / width * 2 - 1
- mouse.y = -y * 2 / height + 1
- })
- let transing = false
- transformControls.addEventListener("mouseDown", event => {
- transing = true
- return event
- })
- // 鼠标点击事件
- renderer.domElement.addEventListener("click", event => {
- if (transing) {
- transing = false
- return
- }
- scene.remove(transformControls) // 移除变换控制器
- transformControls.enabled = false // 停用变换控制器
- raycaster.setFromCamera(mouse, camera) // 配置射线发射器,传递鼠标和相机对象
- const intersection = raycaster.intersectObjects(scene.children) // 获取射线发射器捕获的模型列表,传进去场景中所以模型,穿透的会返回我们
- if (intersection.length) {
- const object = intersection[0].object // 获取第一个模型
- console.log(object)
- scene.add(transformControls) // 添加变换控制器
- transformControls.enabled = true // 启用变换控制器
- transformControls.attach(object)
- }
- return event
- })
- // 监听变换控制器模式更改
- document.addEventListener("keyup", event => {
- if (transformControls.enabled) { // 变换控制器为启用状态执行
- if (event.key === 'e') { // 鼠标按下e键,模式改为缩放
- transformControls.mode = 'scale'
- return false
- }
- if (event.key === 'r') { // 鼠标按下r键,模式改为旋转
- transformControls.mode = 'rotate'
- return false
- }
- if (event.key === 't') { // 鼠标按下t键,模式改为平移
- transformControls.mode = 'translate'
- return false
- }
- }
- return event
- })
- // three.js自带的方法
- const orbitControls = new OrbitControls(camera, renderer.domElement)
- // console.log(MOUSE)//查看MOUSE中的配置项
- orbitControls.mouseButtons = { // 设置鼠标功能键(轨道控制器)
- LEFT: null, // 左键无事件
- MIDDLE: THREE.MOUSE.DOLLY, // 中键缩放
- RIGHT: THREE.MOUSE.ROTATE// 右键旋转
- }
- scene.add(transformControls)
- }
这时我们在log里可以看到之前添加的 userdata
同时也可以做拽查看
开源模型地址 https://github.com/mrdoob/three.js/blob/master
开源的模型
可以自己改成其他格式,使用别的引用方法尝试
ThreeController.js 示例演示,导入glb文件
- import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
- export const LoadingGLTFMethod=(GltfModel)=> {
- loader.load(`${process.env.BASE_URL}model/${GltfModel}`, (gltf) => {
- gltf.scene.scale.set(15, 15, 15)
- gltf.scene.position.set(0, 0, 0)
- gltf.scene.userData={
- name:"LoadingGLTFModel",
- data:"123",
- id:"1212121212",
- title:"人物模型"
- }
- let axis = new THREE.Vector3(0, 1, 0);//向量axis
- gltf.scene.rotateOnAxis(axis, Math.PI);
- gltf.scene.traverse(function (object) {
- if (object.isMesh) {
- object.castShadow = true; //阴影
- object.receiveShadow = true; //接受别人投的阴影
- }
- })
- scene.Model = gltf.scene;
- scene.add(gltf.scene) //公用访问时使用常量向场景中添加引入的模型
- return gltf.scene
- })
- }
- LoadingGLTFMethod("Soldier.glb");//看一看模型有没有出现
可以看到已经成功导入
创建地板 ModelListConfig.js
- export const Ground = new THREE.Mesh(new THREE.PlaneGeometry(300, 300), new THREE.MeshPhongMaterial({
- color: 0x888888, depthWrite: true,
- }));
- ModelListConfig.push(Ground)
当然你也可以自己做一个材质,来作为地板的纹理
- const Require = (src) => {
- return require(`../assets/${src}`)
- }
- const GroundTexture = new THREE.TextureLoader().load(Require('RC.jpg'))
- export const Ground = new THREE.Mesh(new THREE.PlaneGeometry(300, 300), new THREE.MeshPhongMaterial({
- color: 0x888888, depthWrite: true,
- map: GroundTexture
- }));
- Ground.rotation.x = - Math.PI / 2;
- Ground.receiveShadow = true;
- ModelListConfig.push(Ground) // 添加到模型数组
贴图效果:
上述我们在导入文件时,设定了导入的物体是接受投影的,也添加过自然光,这时发现并没有影子。在方向光的作用下,物体会形成阴影投影效果,Three.js物体投影模拟计算主要设置三部分,一个是设置产生投影的模型对象,一个是设置接收投影效果的模型,最后一个是光源对象本身的设置,光源如何产生投影。
LightListConfig.js 添加平行光
- const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
- directionalLight.position.set(0, 35, 20);// 设置光源位置
- directionalLight.castShadow = true; // 设置用于计算阴影的光源对象
- // 设置计算阴影的区域,最好刚好紧密包围在对象周围
- // 计算阴影的区域过大:模糊 过小:看不到或显示不完整
- directionalLight.shadow.camera.near =0.5;
- directionalLight.shadow.camera.far = 50;
- directionalLight.shadow.camera.left = -10;
- directionalLight.shadow.camera.right = 10;
- directionalLight.shadow.camera.top = 100;
- directionalLight.shadow.camera.bottom = -10;
- // 设置mapSize属性可以使阴影更清晰,不那么模糊
- // directionalLight.shadow.mapSize.set(1024,1024)
- LightList.push(directionalLight)
这时候我们加上外部控制 HomeView.vue
- <template>
- <div>
- <ul>
- <li><button @click="LoadingMethod()" >LoadingMethod</button></li>
- <li><button @click="logs()">logs</button></li>
- <li><button @click="LoadingTrack()">LoadingTrack</button></li>
- <li><button @click="Rorate()">Rorate</button></li>
- <li><button@click="LoadingSceneMaterials()">LoadingSceneMaterials
- </button></li>
- <li><button @click="run()">Run</button></li>
- </ul>
- <div class="log-content ">
- <h6>name:{{userData.name}}</h6>
- <h6>data:{{userData.data}}</h6>
- <h6>id:{{userData.id}}</h6>
- <h6>title:{{userData.title}}</h6>
- </div>
- <div class="three-canvas" ref="threeTarget"></div>
- </div>
- </template>
- import {
- ThreeController,
- LoadingGLTFMethod,
- scene,
- }
- from "@/components/ThreeController"; //中央渲染控制
- return {
- …
- userData: {},
- test: {},
- };
- methods: {
- …
- LoadingMethod() {
- LoadingGLTFMethod("Soldier.glb");
- },
- logs() {
- this.test = this.ThreeController.scene.children.find((item) => {
- if(item.userData.name){
- return item.userData.name == "LoadingGLTFModel";
- }
- });
- this.userData=this.test.userData
- },
- }
-
- <style lang="scss" scoped>
- .three-canvas {
- width: 100vw;
- height: 100vh;
- }
- ul{
- position: absolute;
- right: 10px;
- top: 10px;
- }
- .log-content {
- width: 200px;
- height: 60px;
- position: absolute;
- margin: 10px;
- background-color: skyblue;
- }
调用 LoadingMethod() 模型加载后,我们再调用logs() 查看我们加上的usedata
它是一个浏览器的宏任务, requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行,它会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,如果系统绘制率是 60Hz,那么回调函数就会16.7ms再 被执行一次,如果绘制频率是75Hz,那么这个间隔时间就变成了 1000/75=13.3ms。换句话说就是,requestAnimationFrame的执行步伐跟着系统的绘制频率走。它能保证回调函数在屏幕每一次的绘制间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
ThreeController.js 使用requestAnimationFrame() 让模型“run”起来
- export const clock = new THREE.Clock();
- export let mixer = null
- export class ThreeController {
- constructor(Model) {
- … animate() //执行做好的动画帧
- }
- }
- export const animate = () => { requestAnimationFrame(animate);
- if (mixer) { mixer.update(clock.getDelta());}
- renderer.render(scene, camera);
- }
-
- export const startAnimation=(skinnedMesh, animations, animationName)=>{
- const m_mixer = new THREE.AnimationMixer(skinnedMesh);
- const clip = THREE.AnimationClip.findByName(animations,animationName);
- if (clip) {
- const action = m_mixer.clipAction(clip);
- action.play();
- }
- return m_mixer;
- }
- export const LoadingGLTFMethod=(GltfModel,type)=> {//这里新加了一个type
- …
- mixer = startAnimation(
- gltf.scene,
- gltf.animations,
- gltf.animations[type].name // animationName,1 是"Run"
- );
- }
- LoadingGLTFMethod("Soldier.glb",1);
效果 (不会做动图:))
此时模型仅仅是原地run ,这时我们加上手动控制
模型中有position属性,代表物体在空间中x、y、z轴的坐标
HomeView.vue
- import {
- …
- renderer,
- camera,
- } from "@/components/ThreeController";
- methods: {
- …
- Rorate(){
- this.test = this.ThreeController.scene.children.find((item) => {
- return item.userData.name == "LoadingGLTFModel";
- });
- this.animates();
- },
- animates() {
- requestAnimationFrame(this.animates);
- //this.test.position.x += 0.01;
- //this.test.position.y += 0.01;
- this.test.position.z += 0.1;
- renderer.render(scene, camera);
- },
- }加载完模型后,执行Rorate(),这时它就是真正的往前run了
Line类是一种线形状几何体,物体运动的轨迹我们可以看成一条线,让模型围轨道运动
ModelListConfig.js
- const curveArr = [0, 0, 0, 350, 0, 0, 0, 0, 350];
- const curve = [];
- for (let i = 0; i < curveArr.length; i += 3) { //每三个点生成一个坐标
- curve.push(new THREE.Vector3(curveArr[i], curveArr[i + 1], curveArr[i + 2]));
- }
- const RoutePoints = new THREE.CatmullRomCurve3(curve, true)
- const sphereCurve = RoutePoints.clone()
- export const pathPoints = sphereCurve.getPoints(200)//取200个点
- const line = new THREE.Line(
- new THREE.BufferGeometry().setFromPoints(pathPoints),
- new THREE.LineBasicMaterial({
- color: "red",
- linewidth: 1,
- })
- )
- ModelListConfig.push(line)
为了更直观一些,我们还可以把取到的点也渲染上
- const addMesh = () => {
- let list = []
- for (let point of pathPoints) {
- const sphere = new THREE.BoxGeometry(3, 1, 1)
- const sphereMaterial = new THREE.MeshBasicMaterial({ map: RouteTexture })
- const sphereMesh = new THREE.Mesh(sphere, sphereMaterial)
- sphereMesh.position.set(point.x, point.y, point.z)
- ModelListConfig.push(sphereMesh)
- list.push(sphereMesh)
- }
- list.push(line)
- return list
- }
- // 加载轨道
- export const LoadingTrack = () => {
- return addMesh()
- }
让物体围绕着生成的轨道运动 HomeView.vue
- Return{ … num:0}
- LoadingTrack(){ //加载出轨道
- this.ThreeController.addObject(...LoadingTrack())
- },
- run(){
- this.test = this.ThreeController.scene.children.find((item) => {
- return item.userData.name == "LoadingGLTFModel";
- });
- this.runanimates();
- },
- runanimates(){
- if(this.num<=pathPoints.length-2){
- this.num+=1
- }else{
- this.num=0
- }
- requestAnimationFrame(this.runanimates);
- this.test.position.x = pathPoints[ this.num].x ;
- this.test.position.y = pathPoints[ this.num].y;
- this.test.position.z = pathPoints[ this.num].z
- renderer.render(scene, camera);
- },
也可以用来实现相机漫游,实时光影 只不过就是运动的物体从模型,变成相机或者光线
加载场景材质,ThreeController.js 天空盒效果
- export const SceneMapMaterial=(list)=>{
- const map=new THREE.CubeTextureLoader()
- .setPath( `${process.env.BASE_URL}model/` )
- .load(list );
- return map
- }
HomeView.vue
- SceneList:[ 'px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg','nz.jpg' ]
- LoadingSceneMaterials(){
- scene.background = SceneMapMaterial(this.SceneList)//max px 1024*1024
- },
- 名字有对应的作用,且不能超过最大限制
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。