当前位置:   article > 正文

Three.js——第一篇:部署以及基础代码创建场景、GUI调整样式

three.js

three.js官网:three.js docs

中文技术文档1:| 麒跃科技

中文技术文档2:3. 开发和学习环境,引入threejs | Three.js中文网

很多教程一开始要大家自己部署three.js的中文本地部署,我就不弄了,我弄了半天也没弄出来烦了,反正我也不爱看官方文档我就不弄了,直接开干。

另外本人学的前端,习惯用vue就以vue的开发框架做基础引入three.js了

一、基础部署

1、先创建新文件夹,然后终端打开,按以下步骤一步一步输入命令(别管为什么,我没了解)

npm init vite@latest

然后写你的项目名称,随便就行

选择vue

选JS

然后根据最后的提示把这三句执行

然后成功搭建vue的框架

最后再导入three.js就好了

二、先尝试一个vue的小demo

先把app.vue的js、html、css部分先删去,然后用app.vue的全局样式来展示three.js的效果

1、样式,先初始化,因为three.js是基于canva画布来成像的,所以要设置一下canvas样式,先不用管那么多直接复制就好

  1. <style>
  2. *{
  3. margin: 0;
  4. padding: 0;
  5. }
  6. canvas{
  7. width: 100vw;
  8. height: 100vh;
  9. display: block;
  10. position: fixed;
  11. top: 0;
  12. left: 0;
  13. }
  14. </style>

2、js部分

首先前面我们已经下载了three.js,那么就要在js部分导入

  1. // 导入three.js
  2. import * as THREE from "three"

然后要呈现下面这种基本的three3D场景,首先要创建一个【场景】和一个【相机】,你就创就行了别问那么多

创建场景

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

创建相机

这里需要配置这么几个参数:

  1. //创建相机
  2. const Camera = new THREE.PerspectiveCamera(
  3. 45, //相机视角
  4. window.innerWidth / window.innerHeight, //宽高比
  5. 0.1, //近平面,最近能看到多近
  6. 1000 //远平面,最远能看多远
  7. );

这里的视角可以根据下面的图来理解

创建渲染器

要有渲染器才能把场景渲染到屏幕

  1. //创建渲染器
  2. const renderer = new THREE.WebGLRenderer();
  3. renderer.setSize(window.innerWidth , window.innerHeight);//渲染器的大小就是屏幕的大小
  4. document.body.appendChild(renderer.domElement);//这样就是是把canvas添加进去

现在还是一片空白

那么现在就加一个【几何体】看看,并设置它的【材质】(暂时用绿色)

  1. //创建几何体
  2. const geometry = new THREE.BoxGeometry(1,1,1);
  3. //设置材质
  4. const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

还得把【几何体】、【材质】给装进一个【网格(或者叫立方)】里,然后往场景里填加这个【网格】

  1. // 创建网格
  2. const cube = new THREE.Mesh(geometry, material);
  3. //将网格添加到场景
  4. scene.add(cube)

然后初始化相机的位置

  1. //设置相机位置
  2. camera.position.z = 5
  3. camera.lookAt(0,0,0)

现在还不能看到方块、场景,因为还没有渲染,这里就需要调用渲染器的render函数来进行渲染,把我们的【场景】和【相机】放进去

  1. //渲染
  2. renderer.render(scene,camera);

但是现在只能看到一个静态的一面的正方形,想看动起来的正方体就要调用动画函数

创建一个动画函数,然后调用requestAnimationFrame();函数来实现补帧,比如你的函数名叫xxx,那么requestAnimationFrame(xxx);就是自动来调用你这个xxx函数进行动画补帧,然后旋转的逻辑就是这三:cube.rotation.x、cube.rotation.y、cube.rotation.z,让它们不停自增就可以达到旋转了(其实选两个就够了)

最后别忘了:1、把渲染函数放进动画函数里  2、在下面调用动画函数

  1. //渲染函数
  2. function animate(){
  3. requestAnimationFrame(animate);
  4. //旋转
  5. cube.rotation.x += 0.01
  6. cube.rotation.y += 0.01
  7. //渲染
  8. renderer.render(scene,camera);
  9. }
  10. animate();

然后就爽了

三、开始正式认识3D

1、三维坐标系

这是啥我就不讲了,学过空间几何的都懂,那么像我们刚刚创建的场景里,只看到乌漆嘛黑的背景跟一个烂盒子在那转,根本没办法分清方向啊

【那么我们就要在【场景】里添加一个xyz轴辅助线】

  1. //添加世界坐标辅助器
  2. const axesHelpr = new THREE.AxesHelper(5) //5这个参数是指xyz轴的线段要多长
  3. scene.add(axesHelpr)

红色X轴、绿色Y轴、蓝色Z轴,但是此时我们看不到Z轴是因为我们一开始设置了初始化相机的视角是正对着Z轴的,所以Z轴就是一个点

那么我们可以把相机位置偏移一点

但是光这么看不好操控,我们想要拖动坐标轴,或者说改变我们的视角方向怎么办

【创建轨道控制器】

先导入控制器

然后添加轨道控制器

  1. //添加轨道控制器
  2. const controls = new OrbitControls(camera,renderer.domElement);

还要再动画函数里更新轨道控制器

现在就可以任意拖动坐标轴了

另外还有这些

  1. controls.enableDamping = true//设置阻尼惯性
  2. controls.dampingFactor = 0.05//设置阻尼系数
  3. controls.autoRotate = true//开启坐标轴自动旋转

提示:

const controls = new OrbitControls(camera,监听对象);

这里控制器的第二个参数是要监听的对象

  1. const controls = new OrbitControls(camera,renderer.domElement);
  2. // const controls = new OrbitControls(camera,document.body); //这里控制器的第二个参数是要监听的对象,我们也可以换成document.body或者别的都可以
  3. // (不过留意一下,监听对象要换成document.body的话,记得要在css那给body加上宽高才能看得到)

2、位移

我们默认几何体的位置是在坐标轴的中心点(x,y,z)-(0,0,0)

那么用“网格.position.x”、“网格.position.y”、“网格.position.z”

或者“网格.position.set(x值,y值,z值)”

这两种方法都可以让物体位置偏移

  1. //位置偏移
  2. // cube.position.x = 2
  3. cube.position.set(2,0,0)

那么注意,跟平面的前端布局一样,当没有父盒子元素时平面的元素里最大的就是body,所有外边距、内边距啥的都是以body为参照;这里也一样,没有父元素时,最大就是场景,元素位置以世界坐标轴为参照;但是当有了父盒子之后,子盒子的参照物就成了父盒子

这里讲一下怎么添加父盒子

  1. //创建几何体
  2. const geometry = new THREE.BoxGeometry(1,1,1);
  3. //设置材质
  4. const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
  5. const father_material = new THREE.MeshBasicMaterial({ color: 0xff9800 })//这里为了区分父子,给父亲也提供一个新的材质
  6. // 创建网格
  7. const cube = new THREE.Mesh(geometry, material);
  8. const fatherCube = new THREE.Mesh(geometry, father_material);//创建父元素,还是由原来的几何体组成,只不过变了新材质
  9. //位置偏移
  10. cube.position.x = 2
  11. fatherCube.position.x = 0 //这里初始化父元素在x轴0的位置
  12. //将网格添加到场景
  13. // scene.add(cube)
  14. scene.add(fatherCube) //现在在场景里添加的是父元素
  15. fatherCube.add(cube) //在父元素里添加子元素

3、缩放大小

用“网格.scale(x值,y值,z值)”可以放大缩小物体

  1. //放大缩小
  2. cube.scale.set(2,2,2)

跟位移一样,它也是相对的,如果把父元素变大或缩小,那么子元素也会参照父元素的大小与之改变

  1. cube.scale.set(2,2,2)
  2. fatherCube.scale.set(0.5,0.5,0.5)

4、旋转

用“网格.ratation.x”、“网格.ratation.y”、“网格.ratation.z”就可以旋转了,在前面的demo里我们也利用这个进行了自动旋转

cube.rotation.x = Math.PI / 4 // Π/4 就是旋转 45°

同样,相对父元素的

  1. cube.rotation.x = Math.PI / 4 // Π/4 就是旋转 45°
  2. fatherCube.rotation.x = - (Math.PI / 4)

5、响应式画布以及全屏控制

当我们要缩放浏览器窗口、或者不同的浏览器有不同的尺寸时,我们就需要及时调整场景以及相机的大小宽高比

物品们只需要设置一个对window的监听器,当window窗口出发了“resize”更改大小的事件时,及时的更新场景以及相机大小

  1. //监听窗口已完成响应式画布
  2. window.addEventListener('resize', ()=>{
  3. //更新变化场景大小
  4. scene.setSize(window.innerWidth , innerHeight);
  5. //更新相机宽高比
  6. camera.aspect = window.innerWidth / window.innerHeight;
  7. //更新相机投影矩阵
  8. camera.updateProjectionMatrix();
  9. })

然后我们还可以设置一个按钮来实现全屏的效果

  1. //全屏控制
  2. //新建一个按钮
  3. var btn = document.createElement("button")
  4. //设置一下按钮的样式
  5. btn.innerHTML = '点击全屏'
  6. btn.style.width = '180px'
  7. btn.style.height = '80px'
  8. btn.style.position = 'absolute'
  9. btn.style.top = '10px'
  10. btn.style.left = '10px'
  11. btn.style.zIndex = '999'
  12. btn.onclick = function(){
  13. //请求全屏
  14. document.body.requestFullscreen();
  15. }
  16. //最后追加到body中
  17. document.body.appendChild(btn);

还有退出全屏(其实摁Esc就够了,有也行没有也行)

  1. //这是退出全屏
  2. var btn2 = document.createElement("button")
  3. //设置一下按钮的样式
  4. btn2.innerHTML = '退出全屏'
  5. btn2.style.width = '180px'
  6. btn2.style.height = '80px'
  7. btn2.style.position = 'absolute'
  8. btn2.style.top = '10px'
  9. btn2.style.left = '300px'
  10. btn2.style.zIndex = '999'
  11. btn2.onclick = function(){
  12. //退出全屏(当然其实摁Esc就够了)
  13. document.exitFullscreen()
  14. }
  15. //最后追加到body中
  16. document.body.appendChild(btn2);

当然你如果觉得太麻烦懒得记的话,也可以先复制保存,以后要用到的时候直接复制粘贴就好了,反正就是一个响应式画布以及全屏而已嘛

四、lil-GUI便捷可视化操作

前面我们学的内容里,位移、旋转、全屏按钮......都贼几把麻烦,要写一堆代码,那么用了lil-GUI就可以实现可视化操作

1、先导入

  1. //导入lil.gui
  2. import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";

2、然后创建一个对象,里面包含我们想要执行的一些函数方法

比如我们把前面全屏的操作删掉,用一个叫eventObj的对象装起来

  1. let eventObj = {
  2. FullScreean(){
  3. document.body.requestFullscreen()
  4. },
  5. exitFullScreen(){
  6. document.exitFullscreen()
  7. }
  8. }
'
运行

3、然后创建GUI对象

然后调用GUI对象的add方法,把对象传入第一个参数,要执行的方法传到第二个参数(注意是字符串)

  1. const gui = new GUI()
  2. gui.add(eventObj , "FullScreean")
  3. gui.add(eventObj , "exitFullScreen")

这里就多了可视化按钮

还可以换成中文的按钮

  1. gui.add(eventObj , "FullScreean").name('全屏')
  2. gui.add(eventObj , "exitFullScreen").name('退出全屏')

4、改变样式的方法

还可以操控位移、缩放、旋转等等,不过注意,因为【网格.位移】、【网格.缩放】、【网格.旋转】这些都是对象,所以就不用单独创建一个对象来给gui添加,直接添加

第一种写法:直接在GUI.add()方法里传四个参数

第一个参数是【网格.位移】、【网格.缩放】、【网格.旋转】这些操作

第二个参数是“x”、“y”、“z”

第三个参数和第四个参数是可变化的范围界限

例子:

gui.add(cube.position, "x", -5, 5).name('绿色盒子x轴平移')

第二种写法:在GUI.add()方法里传两个参数,然后.最小值().最大值().变化频数()......

例子:

  1. gui.add(cube.position, "x").min(-5).max(5).step(1).name('绿色盒子x轴平移')
  2. gui.add(cube.position, "y").min(-5).max(5).step(1).name('绿色盒子y轴平移')
  3. gui.add(cube.position, "z").min(-5).max(5).step(1).name('绿色盒子z轴平移')

然后有的时候我们需要把缩放、平移......这些操作都包装一下,就可以调用gui.addFolder()来创建一个文件夹,然后用【文件夹.add()】的方式来操作

  1. //第一个文件夹分类
  2. let folder1 = gui.addFolder('绿色立方体的变化')
  3. folder1.add(cube.position, "x").min(-5).max(5).step(1).name('绿色盒子x轴平移')
  4. folder1.add(cube.position, "y").min(-5).max(5).step(1).name('绿色盒子y轴平移')
  5. folder1.add(cube.position, "z").min(-5).max(5).step(1).name('绿色盒子z轴平移')
  6. folder1.add(cube.rotation, "x").min(0).max(2*Math.PI).step(0.01).name('绿色盒子x轴旋转')
  7. folder1.add(cube.rotation, "y").min(0).max(2*Math.PI).step(0.01).name('绿色盒子y轴旋转')
  8. folder1.add(cube.rotation, "z").min(0).max(2*Math.PI).step(0.01).name('绿色盒子z轴旋转')
  9. folder1.add(cube.scale, "x").min(-5).max(5).step(1).name('绿色盒子x轴缩放')
  10. folder1.add(cube.scale, "y").min(-5).max(5).step(1).name('绿色盒子y轴缩放')
  11. folder1.add(cube.scale, "z").min(-5).max(5).step(1).name('绿色盒子z轴缩放')
  12. //第二个文件夹分类
  13. let folder2 = gui.addFolder('橙色立方体的变化')
  14. folder2.add(fatherCube.position, "x").min(-5).max(5).step(1).name('橙色盒子x轴平移')
  15. folder2.add(fatherCube.position, "y").min(-5).max(5).step(1).name('橙色盒子y轴平移')
  16. folder2.add(fatherCube.position, "z").min(-5).max(5).step(1).name('橙色盒子z轴平移')
  17. folder2.add(fatherCube.rotation, "x").min(0).max(2*Math.PI).step(0.01).name('橙色盒子x轴旋转')
  18. folder2.add(fatherCube.rotation, "y").min(0).max(2*Math.PI).step(0.01).name('橙色盒子y轴旋转')
  19. folder2.add(fatherCube.rotation, "z").min(0).max(2*Math.PI).step(0.01).name('橙色盒子z轴旋转')
  20. folder2.add(fatherCube.scale, "x").min(-5).max(5).step(1).name('橙色盒子x轴缩放')
  21. folder2.add(fatherCube.scale, "y").min(-5).max(5).step(1).name('橙色盒子y轴缩放')
  22. folder2.add(fatherCube.scale, "z").min(-5).max(5).step(1).name('橙色盒子z轴缩放')
  23. folder2.add(father_material, "wireframe").name('橙盒子材质为线框模式')

5、操作完之后返回数据

那我们做这些可不是为了好玩就没了啊,我们通过可视化操作调好了位置之后,得知道这个位置在哪啊,那就直接调用它们的:onChange、onFinishChange两个触发事件

顾名思义,onChange就只要变化了就实时触发返回信息,而onFinishChange只有当最后停止了操作,才会触发返回值,看例子

onChange

  1. folder1
  2. .add(cube.position, "x")
  3. .min(-5)
  4. .max(5)
  5. .step(1)
  6. .name('绿色盒子x轴平移')
  7. .onChange( value => {
  8. console.log(`现在绿盒子沿X轴平移到了:${value}`)
  9. })

onFinishChange

  1. folder1
  2. .add(cube.scale, "z")
  3. .min(-5)
  4. .max(5)
  5. .step(1)
  6. .name('绿色盒子z轴缩放')
  7. .onFinishChange( value => {
  8. console.log(`最终确定绿盒子向y轴缩放到:${value}`)
  9. })

6、调整材质

我们还可以通过add方法传入material材质对象、以及"wireframe"属性来控制是否显示线框材质,(“wireframe”是布尔值,true就是开启)

  1. folder1.add(material, "wireframe").name('绿盒子材质为线框模式')
  2. folder2.add(father_material, "wireframe").name('橙盒子材质为线框模式')

7、调整颜色

调颜色有一丢丢麻烦,首先要定义装有颜色变量的一个对象,然后通过【addColor】(注意不是单单只是add了),传入对象参数以及颜色变量属性,就可以更改颜色了

而要想该完的颜色能显示出来,我们还得调整【材质】,【网格.材质.color.set()】才可以调整成功颜色(这里注意,直接用材质的固定名material,而不是相对于个别盒子设置的自定义材质的名字!!!)

比如我前面设置了父盒子的材质是father_material就不行,只能是material

  1. let colorParams = {
  2. cubeColor: '0x00ff00',
  3. fatherCubeColor: '0xff9800'
  4. }
  5. folder1
  6. .addColor(colorParams, 'cubeColor')
  7. .name('子盒子的颜色调整')
  8. .onChange( value => {
  9. cube.material.color.set(value)
  10. })
  11. folder2
  12. .addColor(colorParams, 'fatherCubeColor')
  13. .name('父盒子的颜色调整')
  14. .onChange( value => {
  15. fatherCube.material.color.set(value)
  16. })

下一篇深讲几何体

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/993471
推荐阅读
相关标签
  

闽ICP备14008679号