ThreeJs笔记
简介
WebGL(Web Graphics Library,Web图形库),是一个JavaScript API,可在任何兼容的Web浏览器中渲染高性能的交互式3D和2D图形,而无需使用插件 。 WebGL通过引入一个与OpenGL ES 2.0非常一致的API来做到这一点,该API可以在HTML5 元素中使用。 这种一致性使API可以利用用户设备提供的硬件图形加速。通过这些接口,开发者可以直接跟GPU进行通信。
WebGL 程序分为两部分:
- 使用 Javascript 编写的运行在CPU的程序
- 使用 GLSL 编写的运行在GPU的着色器程序
着色器程序接收CPU传过来的数据,并进行一定处理,最终渲染成丰富多彩的应用样式。着色器程序如下:
// 顶点着色器
var vertex_shader_source =
'void main() {' +
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' + // 设置顶点坐标
' gl_PointSize = 10.0;' + // 设置顶点的大小
'' +
'}';
// 片元着色器
var fragment_shader_source =
'void main(){' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);' + //设置顶点颜色
'}';
原生WebGL 能绘制的基本图元只有 3 种,分别是点、线段、三角形,对应了物理世界中的点线面。所有复杂的图形或者立方体,都是先用点组成基本结构,然后用三角形将这些点构成的平面填充起来,最后由多个平面组成几何体。但现实情况是,如果想生成满足各种应用场景的复杂形状,几何结构会非常复杂,代码写起来也会非常复杂。所以我们选择借助一些3d渲染框架来实现应用场景的复杂形状。
例如threeJs(Three.js是国内文档资料最多、使用最广泛的三维引擎)
ThreeJs,是一个基于原生WebGL,轻量级,跨平台的Javascript库,可以在浏览器上结合HTML5的canvas,SVG或者WebGL,创建和展示3D模型和动画。允许我们在不依赖任何浏览器插件的情况下,创建一个GPU加速的3D动画场景,这可能得益于WebGL的出现,因为WebGL的底层实现是基于OpenGL。
- Three.js官网
ThreeJs入门使用
原生创建一个WebGL程序,一般需要4个步骤:
- 初始化WebGL绘图上下文
- 初始化着色器程序
- 建立模型和数据缓存
- 完成绘制和动画
但是对于Treee.js却有所不同,其使用面向对象的方式来构建程序,包含3个基本对象:场景(scene)、 相机(camera)、渲染器(renderer)。
一、获取three.js
demo版本控制
"node": "v16.18.1",
"three": "0.156.1",
"vue": "3.3.4",
下载依赖
npm install three
项目中引入
import * as THREE from 'three'; //导入全部核心包
import { Scene } from 'three'; //按需导入
// 引入附加组件,附加组件,必须显式导入,例如轨道控制OrbitControls
import { OrbitControls } from ' three/examples/jsm/controls/OrbitControl
注:附加组件以及其他需要单独引入依赖three内置在examples文件夹下
二、创建场景、摄像机、渲染器(画布)
- 场景(scene) :用于把创建完成的模型 添加到 画布中。
- 摄像机 (camera): 相当于人眼所在的位置 ;
- 渲染器(renderer):相当于canvas 画布元素。
- 创建三个元素之后 ,把画布 插入到html 元素中
const scene = new THREE.Scene();
// 设置场景背景
const textureLoader = new THREE.TextureLoader()
// scene.background = new THREE.Color(0xaaaaaa)
scene.background = textureLoader.load('src/assets/images/t2.png', () => {
renderer.render(scene, camera)
})
// 创建摄像机
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000); // (角度 , 长宽比 , 最近看到的距离, 最远)
camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 30;
camera.lookAt(scene.position); //将相机指向场景
//创建webgl渲染器 (画布)
const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xeeeeee); //canvas画布颜色 会被scene.background覆盖
renderer.setSize(window.innerWidth, window.innerHeight); //canvas 画布大小
// 将这个canvas元素插入到 html中
document.getElementById('chartlet-box').appendChild(renderer.domElement);
renderer.render(scene, camera);
创建摄像机 PerspectiveCamera参数图解
三、创建辅助坐标系
- 更直观的去设置元素的位置
- 用scene.add 去添加元素到画布
- 必须在appendChild 插入前
// 辅助坐标系
const axesHelper = new THREE.AxesHelper(200) //参数200标示坐标系大小,可以根据场景大小去设置
scene.add(axesHelper)
展示效果:
四、平面、立方体、球、线的创建
- 平面的创建,需要大小(PlaneGeometry)和材质(MeshStandardMaterial) 两个对象;
- THREE.Mesh 创建一个网格模型对象 几何体是不能被渲染的,只有几何体和材质结合成网格才能被渲染到屏幕上
- scene.add 添加到画布中
//底坐
const planeGeometry = new THREE.PlaneGeometry(200, 200)
const planeMaterial = new THREE.MeshLambertMaterial({ color: 0xaaaaaa, opacity: 1 })
const plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.rotation.x = -0.5 * Math.PI
plane.position.y = -25
//告诉底部平面需要接收阴影
plane.receiveShadow = true
scene.add(plane)
// 长方体模型
const geometry = new THREE.BoxGeometry(200, 20, 200)
// 模型材质
const material = new THREE.MeshLambertMaterial({
color: 'rgb(39, 148, 177)'
})
// 模型材质图片
const texture = textureLoader.load('src/assets/images/t1.png', () => {
renderer.render(scene, camera)
})
const material1 = new THREE.MeshLambertMaterial({
map: texture
})
const mesh = new THREE.Mesh(geometry, [material, material, material, material, material1, material])
mesh.castShadow = true
scene.add(mesh)
// 画线
const myLineMaterial = new THREE.LineBasicMaterial({ color: 'red' })
const geometryBuffer = new THREE.BufferGeometry()
const points: any[] = []
points.push(new THREE.Vector3(50, 0, 101))
points.push(new THREE.Vector3(50, -100, 101))
geometryBuffer.setFromPoints(points)
const line = new THREE.Line(geometryBuffer, myLineMaterial)
scene.add(line)
// 画圆
const geometryCircle = new THREE.CircleGeometry(5)
const materialCircle = new THREE.MeshBasicMaterial({ color: 0xffff00 })
const circle = new THREE.Mesh(geometryCircle, materialCircle)
circle.position.x = 50
circle.position.y = -25
circle.position.z = 102
circle.name = 'test'
scene.add(circle)
展示效果:此时我们可以看到,地面,立方体都没有颜色,是因为没有光源 ,下面添加光源。
五、光源的创建
- 创建灯光的类型,和颜色(光源的种类可以在官方文档中去查看)
- 设置光源的位置
- 设置光源,投影的长度
- scene.add添加到画布
// 光源 环境光会均匀的照亮场景中的所有物体
const ambient = new THREE.AmbientLight(0x404040, 16)
scene.add(ambient)
// 平行光可以投射阴影 显示阴影纹路
const directionalLight = new THREE.DirectionalLight()
directionalLight.position.set(200, 200, 200)
directionalLight.shadow.camera.near = 20 //产生阴影的最近距离
directionalLight.shadow.camera.far = 200 //产生阴影的最远距离
directionalLight.shadow.camera.left = -50 //产生阴影距离位置的最左边位置
directionalLight.shadow.camera.right = 50 //最右边
directionalLight.shadow.camera.top = 50 //最上边
directionalLight.shadow.camera.bottom = -50 //最下面
//这两个值决定使用多少像素生成阴影 默认512
directionalLight.shadow.mapSize.height = 1024
directionalLight.shadow.mapSize.width = 1024
//告诉平行光需要开启阴影投射
directionalLight.castShadow = true
scene.add(directionalLight)
效果展示:
六、鼠标操控三维场景
- 鼠标操控三维场景 需要引入OrbitControls规定控制插件来实现鼠标缩放与拖拽画布
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 轨道控件
const controls = new OrbitControls(camera, renderer.domElement)
controls.addEventListener('change', () => {
renderer.render(scene, camera)
})
- 通过OrbitControls来控制某个模型始终面向相机
controls.addEventListener('change', () => {
const { x, y, z } = camera.position
scene.traverse((child) => {
if (child.name === 'test') {
child.lookAt(x, y, z)
}
})
renderer.render(scene, camera)
})
七、添加点击事件
- 画布的生成dom通过addEventListener添加点击事件
- 结合Raycaster类进行鼠标拾取,计算鼠标或触摸点的位置
- 根据射线计算与所有对象的交点获取到点击的对象数组
renderer.domElement.addEventListener('click', (event: any) => {
const raycaster = new THREE.Raycaster() // 光线投射用于进行鼠标拾取
const mouse = new THREE.Vector2()
// 计算鼠标或触摸点的位置
mouse.x = (event.clientX / window.innerWidth) * 2 - 1
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
// 更新射线
raycaster.setFromCamera(mouse, camera)
// 计算与所有对象的交点
const intersects = raycaster.intersectObjects(scene.children, true)
if (intersects.length > 0) {
// 处理点击事件
// intersects[0] 包含了第一个交点
const clickedObject: any = intersects[0].object
//通过点击到该模型用名字匹配
if (clickedObject.name === 'test') {
console.log('获取的当前模型信息:', clickedObject)
clickedObject.material.color.set('pink')
renderer.render(scene, camera)
}
}
})
ThreeJs其他辅助类介绍
一、加载3D模型文件glb
- 需要单独引入对应的载入程序
- 解决报错: THREE.GLTFLoader: No DRACOLoader instance provided
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
const loader = new GLTFLoader()
/* 报错: THREE.GLTFLoader: No DRACOLoader instance provided */
/*
解决办法:
在node_modules安装的包中获取three版本对应的draco,路径为node_modules\three\examples\js\libs\draco
将该文件夹复制到public文件夹下并在DRACOLoader.setDecoderPath时候设置该对应路径即可
————————————————
版权声明:本文为CSDN博主「早日退休!」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_48894212/article/details/127241897
*/
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/public/draco/') // 设置public下的解码路径,注意最后面的/
loader.setDRACOLoader(dracoLoader)
loader.load(
'/src/assets/glb/LittlestTokyo.glb',
function (gltf: any) {
scene.add(gltf.scene)
},
undefined,
function (error: any) {
console.error(error)
}
)
二、创建css3D渲染器
- ThreeJs提供的CSS3DObject把一个文档dom对象转化成ThreeJs可用的Object3D对象,CSS3DRenderer就是负责渲染这个CSS3DObject的渲染器
- 创建方式渲染器方式与webgl渲染器一致、需要单独设置渲染器dom的定位样式,才能在webgl的画布上面显示
- webgl渲染器和css3D渲染器同时存在时,css3D渲染器设置了样式position在顶层显示
- 在轨道控制以及事件处理方面直接通过3d渲染器去传递下去。如果加载在webgl渲染器上会导致无法操作
const createTag = (obj: any) => {
const element = document.createElement('div')
element.className = 'tag'
element.innerHTML = `<p>名称:${obj.name}</p><p>温度:22°</p><p>湿度:29%</p>`
// css3d添加点击事件
element.addEventListener('pointerdown', () => {
console.log('click')
})
const object = new CSS3DObject(element)
object.visible = true
//缩放比例
object.scale.set(0.2, 0.2, 0.2)
//指定摆放位置
object.position.copy(obj.position)
return object
}
const tags: any[] = []
scene.traverse((child) => {
if (child.isObject3D && child.children.length === 0) {
//添加标签文字
const tag = createTag(child)
tags.push(tag)
scene.add(tag) //添加到指定的场景里
}
})
const render3D = new CSS3DRenderer()
//设置渲染器大小
render3D.setSize(width, height)
//需要设置位置---------------------- 重点 ---------------------------
render3D.domElement.style.position = 'absolute'
render3D.domElement.style.top = '0'
//该渲染器也要加上同样的控制器
const controls1 = new OrbitControls(camera, render3D.domElement)
render3D.render(scene, camera)
scene.tarverse
: 该方法接受一个函数作为参数,遍历调用者和它的所有后代,都执行该function
三、关于材质与贴图
- 材质种类参考官网文档或者推荐博客
// 颜色
const material = new THREE.MeshLambertMaterial({
color: 'rgb(39, 148, 177)'
})
// 图片
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load('src/assets/images/t1.png', () => {
// 加载完成图片后刷新页面显示材质
renderer.render(scene, camera)
})
const material1 = new THREE.MeshLambertMaterial({
map: texture
})