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

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

2024-08-24 23:08:54 前端知识 前端哥 21 782 我要收藏

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样式,先不用管那么多直接复制就好

<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();

然后就爽了

三、开始正式认识3D

1、三维坐标系

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

【那么我们就要在【场景】里添加一个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加上宽高才能看得到)

2、位移

我们默认几何体的位置是在坐标轴的中心点(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) //在父元素里添加子元素

3、缩放大小

用“网格.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)

4、旋转

用“网格.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)

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

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

物品们只需要设置一个对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就可以实现可视化操作

1、先导入

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

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

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

let eventObj = {
  FullScreean(){
    document.body.requestFullscreen()
  },
  exitFullScreen(){
    document.exitFullscreen()
  }
}

3、然后创建GUI对象

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

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

这里就多了可视化按钮

还可以换成中文的按钮

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

4、改变样式的方法

还可以操控位移、缩放、旋转等等,不过注意,因为【网格.位移】、【网格.缩放】、【网格.旋转】这些都是对象,所以就不用单独创建一个对象来给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('橙盒子材质为线框模式')

5、操作完之后返回数据

那我们做这些可不是为了好玩就没了啊,我们通过可视化操作调好了位置之后,得知道这个位置在哪啊,那就直接调用它们的: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}`)
  })

6、调整材质

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

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

7、调整颜色

调颜色有一丢丢麻烦,首先要定义装有颜色变量的一个对象,然后通过【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)
  })

下一篇深讲几何体

转载请注明出处或者链接地址:https://www.qianduange.cn//article/16720.html
标签
three.js
评论
发布的文章

安装Nodejs后,npm无法使用

2024-11-30 11:11:38

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!