赞
踩
three.js官网:three.js docs
中文技术文档2:3. 开发和学习环境,引入threejs | Three.js中文网
很多教程一开始要大家自己部署three.js的中文本地部署,我就不弄了,我弄了半天也没弄出来烦了,反正我也不爱看官方文档我就不弄了,直接开干。
另外本人学的前端,习惯用vue就以vue的开发框架做基础引入three.js了
1、先创建新文件夹,然后终端打开,按以下步骤一步一步输入命令(别管为什么,我没了解)
npm init vite@latest
然后写你的项目名称,随便就行
选择vue
选JS
然后根据最后的提示把这三句执行
然后成功搭建vue的框架
最后再导入three.js就好了
先把app.vue的js、html、css部分先删去,然后用app.vue的全局样式来展示three.js的效果
1、样式,先初始化,因为three.js是基于canva画布来成像的,所以要设置一下canvas样式,先不用管那么多直接复制就好
- <style>
- *{
- margin: 0;
- padding: 0;
- }
-
- canvas{
- width: 100vw;
- height: 100vh;
- display: block;
- position: fixed;
- top: 0;
- left: 0;
- }
- </style>
2、js部分
首先前面我们已经下载了three.js,那么就要在js部分导入
- // 导入three.js
- import * as THREE from "three"
然后要呈现下面这种基本的three3D场景,首先要创建一个【场景】和一个【相机】,你就创就行了别问那么多
创建场景
- //创建场景
- const scene = new THREE.Scene();
创建相机
这里需要配置这么几个参数:
- //创建相机
- const Camera = new THREE.PerspectiveCamera(
- 45, //相机视角
- window.innerWidth / window.innerHeight, //宽高比
- 0.1, //近平面,最近能看到多近
- 1000 //远平面,最远能看多远
- );
这里的视角可以根据下面的图来理解
创建渲染器
要有渲染器才能把场景渲染到屏幕
- //创建渲染器
- const renderer = new THREE.WebGLRenderer();
- renderer.setSize(window.innerWidth , window.innerHeight);//渲染器的大小就是屏幕的大小
- document.body.appendChild(renderer.domElement);//这样就是是把canvas添加进去
现在还是一片空白
那么现在就加一个【几何体】看看,并设置它的【材质】(暂时用绿色)
- //创建几何体
- 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)
然后初始化相机的位置
- //设置相机位置
- camera.position.z = 5
- camera.lookAt(0,0,0)
现在还不能看到方块、场景,因为还没有渲染,这里就需要调用渲染器的render函数来进行渲染,把我们的【场景】和【相机】放进去
- //渲染
- renderer.render(scene,camera);
但是现在只能看到一个静态的一面的正方形,想看动起来的正方体就要调用动画函数
创建一个动画函数,然后调用requestAnimationFrame();函数来实现补帧,比如你的函数名叫xxx,那么requestAnimationFrame(xxx);就是自动来调用你这个xxx函数进行动画补帧,然后旋转的逻辑就是这三:cube.rotation.x、cube.rotation.y、cube.rotation.z,让它们不停自增就可以达到旋转了(其实选两个就够了)
最后别忘了:1、把渲染函数放进动画函数里 2、在下面调用动画函数
- //渲染函数
- function animate(){
- requestAnimationFrame(animate);
- //旋转
- cube.rotation.x += 0.01
- cube.rotation.y += 0.01
- //渲染
- renderer.render(scene,camera);
- }
- animate();
然后就爽了
这是啥我就不讲了,学过空间几何的都懂,那么像我们刚刚创建的场景里,只看到乌漆嘛黑的背景跟一个烂盒子在那转,根本没办法分清方向啊
【那么我们就要在【场景】里添加一个xyz轴辅助线】
- //添加世界坐标辅助器
- const axesHelpr = new THREE.AxesHelper(5) //5这个参数是指xyz轴的线段要多长
- scene.add(axesHelpr)
红色X轴、绿色Y轴、蓝色Z轴,但是此时我们看不到Z轴是因为我们一开始设置了初始化相机的视角是正对着Z轴的,所以Z轴就是一个点
那么我们可以把相机位置偏移一点
但是光这么看不好操控,我们想要拖动坐标轴,或者说改变我们的视角方向怎么办
【创建轨道控制器】
先导入控制器
然后添加轨道控制器
- //添加轨道控制器
- const controls = new OrbitControls(camera,renderer.domElement);
还要再动画函数里更新轨道控制器
现在就可以任意拖动坐标轴了
另外还有这些
- controls.enableDamping = true//设置阻尼惯性
- controls.dampingFactor = 0.05//设置阻尼系数
- controls.autoRotate = true//开启坐标轴自动旋转
提示:
const controls = new OrbitControls(camera,监听对象);
这里控制器的第二个参数是要监听的对象
- const controls = new OrbitControls(camera,renderer.domElement);
- // const controls = new OrbitControls(camera,document.body); //这里控制器的第二个参数是要监听的对象,我们也可以换成document.body或者别的都可以
- // (不过留意一下,监听对象要换成document.body的话,记得要在css那给body加上宽高才能看得到)
我们默认几何体的位置是在坐标轴的中心点(x,y,z)-(0,0,0)
那么用“网格.position.x”、“网格.position.y”、“网格.position.z”
或者“网格.position.set(x值,y值,z值)”
这两种方法都可以让物体位置偏移
- //位置偏移
- // cube.position.x = 2
- cube.position.set(2,0,0)
那么注意,跟平面的前端布局一样,当没有父盒子元素时平面的元素里最大的就是body,所有外边距、内边距啥的都是以body为参照;这里也一样,没有父元素时,最大就是场景,元素位置以世界坐标轴为参照;但是当有了父盒子之后,子盒子的参照物就成了父盒子
这里讲一下怎么添加父盒子
- //创建几何体
- const geometry = new THREE.BoxGeometry(1,1,1);
-
- //设置材质
- const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
- const father_material = new THREE.MeshBasicMaterial({ color: 0xff9800 })//这里为了区分父子,给父亲也提供一个新的材质
-
- // 创建网格
- const cube = new THREE.Mesh(geometry, material);
- const fatherCube = new THREE.Mesh(geometry, father_material);//创建父元素,还是由原来的几何体组成,只不过变了新材质
-
- //位置偏移
- cube.position.x = 2
- fatherCube.position.x = 0 //这里初始化父元素在x轴0的位置
-
- //将网格添加到场景
- // scene.add(cube)
- scene.add(fatherCube) //现在在场景里添加的是父元素
- fatherCube.add(cube) //在父元素里添加子元素
用“网格.scale(x值,y值,z值)”可以放大缩小物体
- //放大缩小
- cube.scale.set(2,2,2)
跟位移一样,它也是相对的,如果把父元素变大或缩小,那么子元素也会参照父元素的大小与之改变
- cube.scale.set(2,2,2)
- fatherCube.scale.set(0.5,0.5,0.5)
用“网格.ratation.x”、“网格.ratation.y”、“网格.ratation.z”就可以旋转了,在前面的demo里我们也利用这个进行了自动旋转
cube.rotation.x = Math.PI / 4 // Π/4 就是旋转 45°
同样,相对父元素的
- cube.rotation.x = Math.PI / 4 // Π/4 就是旋转 45°
- fatherCube.rotation.x = - (Math.PI / 4)
当我们要缩放浏览器窗口、或者不同的浏览器有不同的尺寸时,我们就需要及时调整场景以及相机的大小宽高比
物品们只需要设置一个对window的监听器,当window窗口出发了“resize”更改大小的事件时,及时的更新场景以及相机大小
- //监听窗口已完成响应式画布
- window.addEventListener('resize', ()=>{
- //更新变化场景大小
- scene.setSize(window.innerWidth , innerHeight);
- //更新相机宽高比
- camera.aspect = window.innerWidth / window.innerHeight;
- //更新相机投影矩阵
- camera.updateProjectionMatrix();
- })
然后我们还可以设置一个按钮来实现全屏的效果
- //全屏控制
- //新建一个按钮
- var btn = document.createElement("button")
- //设置一下按钮的样式
- btn.innerHTML = '点击全屏'
- btn.style.width = '180px'
- btn.style.height = '80px'
- btn.style.position = 'absolute'
- btn.style.top = '10px'
- btn.style.left = '10px'
- btn.style.zIndex = '999'
- btn.onclick = function(){
- //请求全屏
- document.body.requestFullscreen();
- }
- //最后追加到body中
- document.body.appendChild(btn);
还有退出全屏(其实摁Esc就够了,有也行没有也行)
- //这是退出全屏
- var btn2 = document.createElement("button")
- //设置一下按钮的样式
- btn2.innerHTML = '退出全屏'
- btn2.style.width = '180px'
- btn2.style.height = '80px'
- btn2.style.position = 'absolute'
- btn2.style.top = '10px'
- btn2.style.left = '300px'
- btn2.style.zIndex = '999'
- btn2.onclick = function(){
- //退出全屏(当然其实摁Esc就够了)
- document.exitFullscreen()
- }
- //最后追加到body中
- document.body.appendChild(btn2);
当然你如果觉得太麻烦懒得记的话,也可以先复制保存,以后要用到的时候直接复制粘贴就好了,反正就是一个响应式画布以及全屏而已嘛
前面我们学的内容里,位移、旋转、全屏按钮......都贼几把麻烦,要写一堆代码,那么用了lil-GUI就可以实现可视化操作
- //导入lil.gui
- import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
比如我们把前面全屏的操作删掉,用一个叫eventObj的对象装起来
- let eventObj = {
- FullScreean(){
- document.body.requestFullscreen()
- },
- exitFullScreen(){
- document.exitFullscreen()
- }
- }
'运行
然后调用GUI对象的add方法,把对象传入第一个参数,要执行的方法传到第二个参数(注意是字符串)
- const gui = new GUI()
- gui.add(eventObj , "FullScreean")
- gui.add(eventObj , "exitFullScreen")
这里就多了可视化按钮
还可以换成中文的按钮
- gui.add(eventObj , "FullScreean").name('全屏')
- gui.add(eventObj , "exitFullScreen").name('退出全屏')
还可以操控位移、缩放、旋转等等,不过注意,因为【网格.位移】、【网格.缩放】、【网格.旋转】这些都是对象,所以就不用单独创建一个对象来给gui添加,直接添加
第一种写法:直接在GUI.add()方法里传四个参数
第一个参数是【网格.位移】、【网格.缩放】、【网格.旋转】这些操作
第二个参数是“x”、“y”、“z”
第三个参数和第四个参数是可变化的范围界限
例子:
gui.add(cube.position, "x", -5, 5).name('绿色盒子x轴平移')
第二种写法:在GUI.add()方法里传两个参数,然后.最小值().最大值().变化频数()......
例子:
- gui.add(cube.position, "x").min(-5).max(5).step(1).name('绿色盒子x轴平移')
- gui.add(cube.position, "y").min(-5).max(5).step(1).name('绿色盒子y轴平移')
- gui.add(cube.position, "z").min(-5).max(5).step(1).name('绿色盒子z轴平移')
然后有的时候我们需要把缩放、平移......这些操作都包装一下,就可以调用gui.addFolder()来创建一个文件夹,然后用【文件夹.add()】的方式来操作
- //第一个文件夹分类
- let folder1 = gui.addFolder('绿色立方体的变化')
- folder1.add(cube.position, "x").min(-5).max(5).step(1).name('绿色盒子x轴平移')
- folder1.add(cube.position, "y").min(-5).max(5).step(1).name('绿色盒子y轴平移')
- folder1.add(cube.position, "z").min(-5).max(5).step(1).name('绿色盒子z轴平移')
- folder1.add(cube.rotation, "x").min(0).max(2*Math.PI).step(0.01).name('绿色盒子x轴旋转')
- folder1.add(cube.rotation, "y").min(0).max(2*Math.PI).step(0.01).name('绿色盒子y轴旋转')
- folder1.add(cube.rotation, "z").min(0).max(2*Math.PI).step(0.01).name('绿色盒子z轴旋转')
- folder1.add(cube.scale, "x").min(-5).max(5).step(1).name('绿色盒子x轴缩放')
- folder1.add(cube.scale, "y").min(-5).max(5).step(1).name('绿色盒子y轴缩放')
- folder1.add(cube.scale, "z").min(-5).max(5).step(1).name('绿色盒子z轴缩放')
-
-
- //第二个文件夹分类
- let folder2 = gui.addFolder('橙色立方体的变化')
- folder2.add(fatherCube.position, "x").min(-5).max(5).step(1).name('橙色盒子x轴平移')
- folder2.add(fatherCube.position, "y").min(-5).max(5).step(1).name('橙色盒子y轴平移')
- folder2.add(fatherCube.position, "z").min(-5).max(5).step(1).name('橙色盒子z轴平移')
- folder2.add(fatherCube.rotation, "x").min(0).max(2*Math.PI).step(0.01).name('橙色盒子x轴旋转')
- folder2.add(fatherCube.rotation, "y").min(0).max(2*Math.PI).step(0.01).name('橙色盒子y轴旋转')
- folder2.add(fatherCube.rotation, "z").min(0).max(2*Math.PI).step(0.01).name('橙色盒子z轴旋转')
- folder2.add(fatherCube.scale, "x").min(-5).max(5).step(1).name('橙色盒子x轴缩放')
- folder2.add(fatherCube.scale, "y").min(-5).max(5).step(1).name('橙色盒子y轴缩放')
- folder2.add(fatherCube.scale, "z").min(-5).max(5).step(1).name('橙色盒子z轴缩放')
- folder2.add(father_material, "wireframe").name('橙盒子材质为线框模式')
那我们做这些可不是为了好玩就没了啊,我们通过可视化操作调好了位置之后,得知道这个位置在哪啊,那就直接调用它们的:onChange、onFinishChange两个触发事件
顾名思义,onChange就只要变化了就实时触发返回信息,而onFinishChange只有当最后停止了操作,才会触发返回值,看例子
onChange
- folder1
- .add(cube.position, "x")
- .min(-5)
- .max(5)
- .step(1)
- .name('绿色盒子x轴平移')
- .onChange( value => {
- console.log(`现在绿盒子沿X轴平移到了:${value}`)
- })
onFinishChange
- folder1
- .add(cube.scale, "z")
- .min(-5)
- .max(5)
- .step(1)
- .name('绿色盒子z轴缩放')
- .onFinishChange( value => {
- console.log(`最终确定绿盒子向y轴缩放到:${value}`)
- })
我们还可以通过add方法传入material材质对象、以及"wireframe"属性来控制是否显示线框材质,(“wireframe”是布尔值,true就是开启)
- folder1.add(material, "wireframe").name('绿盒子材质为线框模式')
- folder2.add(father_material, "wireframe").name('橙盒子材质为线框模式')
调颜色有一丢丢麻烦,首先要定义装有颜色变量的一个对象,然后通过【addColor】(注意不是单单只是add了),传入对象参数以及颜色变量属性,就可以更改颜色了
而要想该完的颜色能显示出来,我们还得调整【材质】,【网格.材质.color.set()】才可以调整成功颜色(这里注意,直接用材质的固定名material,而不是相对于个别盒子设置的自定义材质的名字!!!)
比如我前面设置了父盒子的材质是father_material就不行,只能是material
- let colorParams = {
- cubeColor: '0x00ff00',
- fatherCubeColor: '0xff9800'
- }
- folder1
- .addColor(colorParams, 'cubeColor')
- .name('子盒子的颜色调整')
- .onChange( value => {
- cube.material.color.set(value)
- })
-
- folder2
- .addColor(colorParams, 'fatherCubeColor')
- .name('父盒子的颜色调整')
- .onChange( value => {
- fatherCube.material.color.set(value)
- })
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。