今天我们介绍一下Three.js中的常用对象几何
往期内容回顾:
在threejs
中给我们内置了一些几何形状, 具体内容参见文档
在docs
中搜索geometry
几何体
这里, 我们主要介绍几个常用的几何体(立方体, 球体, 平面)
1)立方体
立方体, 我们主要需要设置其(长宽高)属性值
- 长(width): x轴所占据的空间
- 宽(depth): z轴所占据的空间
- 高(height): y轴所占据的空间
// 三. 创建物体
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2)
const cubeMaterial = new THREE.MeshNormalMaterial()
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
scene.add(cube)
为了方便观察, 我们可以考虑使用Gui工具, 调整立方体的坐标和大小
完整示例:
// 导入threejs
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import * as dat from 'dat.gui'
// 一. 创建场景
const scene = new THREE.Scene()
// 二. 创建相机
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight
)
camera.position.set(20, 20, 20)
camera.lookAt(0, 0, 0)
// 三. 创建物体
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2)
const cubeMaterial = new THREE.MeshNormalMaterial()
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
scene.add(cube)
// 六. 集成Gui工具
const gui = new dat.GUI()
const data = {
x: 0,
y: 0,
z: 0,
width: 2,
height: 2,
depth: 2,
}
gui.add(data, 'x').onChange((value) => {
cube.position.x = value
})
gui.add(data, 'y').onChange((value) => {
cube.position.y = value
})
gui.add(data, 'z').onChange((value) => {
cube.position.z = value
})
gui.add(data, 'width', 2, 20, 1).onChange((value) => {
data.width = value
// 销毁旧的几何体体
cube.geometry.dispose()
cube.geometry = new THREE.BoxGeometry(data.width, data.height, data.depth)
})
gui.add(data, 'height', 2, 20, 1).onChange((value) => {
data.height = value
// 销毁旧的几何体体
cube.geometry.dispose()
cube.geometry = new THREE.BoxGeometry(data.width, data.height, data.depth)
})
gui.add(data, 'depth', 2, 20, 1).onChange((value) => {
data.depth = value
// 销毁旧的几何体体
cube.geometry.dispose()
cube.geometry = new THREE.BoxGeometry(data.width, data.height, data.depth)
})
// 四. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(animation)
document.body.appendChild(renderer.domElement)
function animation() {
renderer.render(scene, camera)
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
// 五. 集成辅助工具
const control = new OrbitControls(camera, renderer.domElement)
const axesHepler = new THREE.AxesHelper(10)
scene.add(axesHepler)
const gridHelper = new THREE.GridHelper(20, 20, 0xffffff, 0xffffff)
gridHelper.material.transparent = true
gridHelper.material.opacity = 0.5
scene.add(gridHelper)
2)球体
球体, 我们主要修改其半径和分段
- 半径(radius): 球体半径
- 经线分段数(widthSegments):
- 纬线分段数(heightSegments):
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32)
const sphereMaterial = new THREE.MeshNormalMaterial()
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
完整示例:
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import * as dat from 'dat.gui'
// 一. 创建场景
const scene = new THREE.Scene()
// 二. 创建相机
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight
)
camera.position.set(0, 0, 5)
// 三. 创建物体
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32)
const sphereMaterial = new THREE.MeshNormalMaterial()
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
scene.add(sphere)
const data = {
x: 0,
y: 0,
z: 0,
radius: 1,
widthSegments: 32,
heightSegments: 32,
}
const gui = new dat.GUI()
gui.add(data, 'x').onChange((value) => {
sphere.position.x = value
})
gui.add(data, 'y').onChange((value) => {
sphere.position.y = value
})
gui.add(data, 'z').onChange((value) => {
sphere.position.z = value
})
gui.add(data, 'radius', 1, 10, 1).onChange((value) => {
data.radius = value
sphere.geometry.dispose()
sphere.geometry = new THREE.SphereGeometry(
data.radius,
data.widthSegments,
data.heightSegments
)
})
gui.add(data, 'widthSegments', 3, 64, 1).onChange((value) => {
data.widthSegments = value
sphere.geometry.dispose()
sphere.geometry = new THREE.SphereGeometry(
data.radius,
data.widthSegments,
data.heightSegments
)
})
gui.add(data, 'heightSegments', 2, 64, 1).onChange((value) => {
data.heightSegments = value
sphere.geometry.dispose()
sphere.geometry = new THREE.SphereGeometry(
data.radius,
data.widthSegments,
data.heightSegments
)
})
// 四. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(animation)
// 将渲染的canvas添加到body元素中
document.body.appendChild(renderer.domElement)
// 五. 辅助工具
const control = new OrbitControls(camera, renderer.domElement)
const axesHelper = new THREE.AxesHelper(10)
scene.add(axesHelper)
const gridHelper = new THREE.GridHelper(20, 20, 0xffffff, 0xffffff)
gridHelper.material.transparent = true
gridHelper.material.opacity = 0.5
scene.add(gridHelper)
function animation() {
renderer.render(scene, camera)
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
上面我们发现Gui部分的代码很多都是重复的。
考虑到后续其它对象也需要使用Gui工具, 我们可以考虑统一封装
创建src/gui
文件夹
在gui
文件夹下创建index.js
导出两个类
BaseGui
MeshGui
export { BaseGui } from './BaseGui'
export { MeshGui } from './MeshGui'
import * as dat from 'dat.gui'
export class BaseGui {
constructor() {
this.gui = new dat.GUI()
}
initGuiFolder(item, name, parent, config) {
const folder = this.gui.addFolder(name)
Object.keys(config).forEach((key) => {
this.initGuiItem(folder, item, key, parent, config[key])
})
}
initGuiItem(gui, item, key, parent, options = {}) {
// 构造数据
const controls = {}
if (key === 'color') {
controls[key] = item.color.getHex()
} else {
controls[key] = item[key]
}
// console.log(controls)
const method = options.method || 'add'
const min = options.min || (options.extend && options.extend[0])
const max = options.max || (options.extend && options.extend[1])
const step = options.step || 1
const name = options.name
let guiItem = gui[method](controls, key)
if (guiItem.min && guiItem.max && guiItem.step) {
guiItem = min !== undefined ? guiItem.min(min) : guiItem
guiItem = max !== undefined ? guiItem.max(max) : guiItem
guiItem = step !== undefined ? guiItem.step(step) : guiItem
}
guiItem = name !== undefined ? guiItem.name(name) : guiItem
guiItem.onChange((value) => {
if (options.handler) {
options.handler(item, key, value, parent)
} else {
item[key] = value
}
})
}
}
import * as THREE from 'three'
import { BaseGui } from './BaseGui'
function defaultHandler(item, key, value) {
item[key] = value
}
const Vector3Config = {
x: {
handler: defaultHandler,
},
y: {
handler: defaultHandler,
},
z: {
handler: defaultHandler,
},
}
function eulerHandler(item, key, value) {
item[key] = THREE.MathUtils.degToRad(value)
}
const EulerConfig = {
x: {
extend: [-180, 180],
handler: eulerHandler,
},
y: {
extend: [-180, 180],
handler: eulerHandler,
},
z: {
extend: [-180, 180],
handler: eulerHandler,
},
}
function geometryHandler(item, key, value, parent) {
const params = { ...parent.geometry.parameters }
params[key] = value
parent.geometry.dispose()
parent.geometry = new THREE[parent.geometry.type](...Object.values(params))
}
const GeometryMapping = {
BoxGeometry: {
width: {
name: 'x轴宽度',
extend: [2, 20],
handler: geometryHandler,
},
height: {
name: 'y轴高度',
extend: [2, 20],
handler: geometryHandler,
},
depth: {
name: 'z轴深度',
extend: [2, 20],
handler: geometryHandler,
},
},
SphereGeometry: {
radius: {
name: '半径',
min: 1,
handler: geometryHandler,
},
widthSegments: {
name: '水平分段数',
min: 3,
handler: geometryHandler,
},
heightSegments: {
name: '垂直分段数',
min: 2,
handler: geometryHandler,
},
},
PlaneGeometry: {
width: {
name: 'x轴宽度',
min: 1,
handler: geometryHandler,
},
height: {
name: 'y轴高度',
min: 1,
handler: geometryHandler,
},
},
}
export class MeshGui extends BaseGui {
constructor(options = {}) {
if (!options.target.isMesh) {
console.error('target must be an instance of Mesh')
return
}
super()
this.init(options)
}
init(options) {
this.mesh = options.target
this.geometry = this.mesh.geometry
this.material = this.mesh.material
this.position = this.mesh.position
this.rotation = this.mesh.rotation
this.scale = this.mesh.scale
options.position !== false ? this.initPosition() : ''
options.rotation !== false ? this.initRotation() : ''
options.scale !== false ? this.initScale() : ''
options.geometry !== false ? this.initGeometry() : ''
}
initPosition() {
console.log(this.position)
this.initGuiFolder(this.position, '位置', this.mesh, Vector3Config)
}
initRotation() {
this.initGuiFolder(this.rotation, '旋转(度)', this.mesh, EulerConfig)
}
initScale() {
this.initGuiFolder(this.scale, '缩放', this.mesh, Vector3Config)
}
initGeometry() {
const geometry = this.geometry
const type = geometry.type
const config = GeometryMapping[type]
this.initGuiFolder(geometry.parameters, geometry.type, this.mesh, config)
}
}
完整示例(优化封装版)
在球体几何中引用MeshGui
// 导入threejs
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
// 导入封装的Gui工具
import { MeshGui } from '../gui'
// 一. 创建场景
const scene = new THREE.Scene()
// 二. 创建相机
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight
)
camera.position.set(20, 20, 20)
camera.lookAt(0, 0, 0)
// 三. 创建物体
const sphereGeometry = new THREE.SphereGeometry(2)
const sphereMaterail = new THREE.MeshNormalMaterial() // 法向
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterail)
scene.add(sphere)
// 六. 集成Gui工具
new MeshGui({
target: sphere,
})
// 四. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(animation)
document.body.appendChild(renderer.domElement)
function animation() {
renderer.render(scene, camera)
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
// 五. 集成辅助工具
const control = new OrbitControls(camera, renderer.domElement)
const axesHepler = new THREE.AxesHelper(10)
scene.add(axesHepler)
const gridHelper = new THREE.GridHelper(20, 20, 0xffffff, 0xffffff)
gridHelper.material.transparent = true
gridHelper.material.opacity = 0.5
scene.add(gridHelper)
3)平面
对于平面, 主要设置
- width: x轴方向的宽度
- height: y轴方向的高度
const geometry = new THREE.PlaneGeometry(20, 20)
const material = new THREE.MeshBasicMaterial({ color: 0x333333 })
const plane = new THREE.Mesh(geometry, material)
完整示例:
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { MeshGui } from './gui'
// 一. 创建场景
const scene = new THREE.Scene()
// 二. 创建相机
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight
)
camera.position.set(0, 0, 5)
// 三. 创建物体
const geometry = new THREE.PlaneGeometry(20, 20)
const material = new THREE.MeshNormalMaterial()
const plane = new THREE.Mesh(geometry, material)
scene.add(plane)
new MeshGui({
target: plane,
})
// 四. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(animation)
// 将渲染的canvas添加到body元素中
document.body.appendChild(renderer.domElement)
// 五. 辅助工具
const control = new OrbitControls(camera, renderer.domElement)
const axesHelper = new THREE.AxesHelper(10)
scene.add(axesHelper)
const gridHelper = new THREE.GridHelper(20, 20, 0xffffff, 0xffffff)
gridHelper.material.transparent = true
gridHelper.material.opacity = 0.5
scene.add(gridHelper)
function animation() {
renderer.render(scene, camera)
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
4)缓冲区几何体
虽然three.js给我们内置了很多常用的几何体
但是对于一些没有提供的几何体如何处理呢?
比如, 像下图所示的几何体
这里, 我们可以通过基类BufferGeometry自定义几何体
缓冲区几何体
1.根据我们的几何常识: 连点成线, 连线成面, 连面成体
2.如果我们要渲染一个几何体, 只需要确定几何体的顶点坐标, 然后将这些点连接起来, 但是点太多, 为了方便管理我们使用一个缓冲区Buffer来存储, 因此自定义几何体也称缓冲区几何体
步骤
1.实例化BufferGeometry对象
2.定义顶点坐标缓冲区(数组), 数组中每3个元素为一组, 表示一点的坐标
3.设置几何体的position属性
示例
// 1. 创建缓冲区几何体对象
const geometry = new THREE.BufferGeometry()
// 2. 定义顶点坐标缓冲区
const vertices = new Float32Array([
// 第一个三角形
-1.0, -1.0, 1.0, 1.0, -1.0, 0, 1.0, 1.0, 1.0,
// 第二个三角形
-1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 0,
])
// 3. 设置顶点坐标
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,
wireframe: true,
})
const mesh = new THREE.Mesh(geometry, material)
完整示例:
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
// 一. 创建场景
const scene = new THREE.Scene()
// 二. 创建相机
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight
)
camera.position.set(0, 0, 5)
// 三. 创建物体
// 根据顶点构建三角形
// 1. 创建缓冲区几何体对象
const geometry = new THREE.BufferGeometry()
// 2. 定义顶点坐标缓冲区
const vertices = new Float32Array([
// 第一个三角形
-1.0, -1.0, 1.0, 1.0, -1.0, 0, 1.0, 1.0, 1.0,
// 第二个三角形
-1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 0,
])
// 3. 设置顶点坐标
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,
// wireframe: true,
})
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
// 四. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(animation)
// 将渲染的canvas添加到body元素中
document.body.appendChild(renderer.domElement)
// 五. 辅助工具
const control = new OrbitControls(camera, renderer.domElement)
const axesHelper = new THREE.AxesHelper(10)
scene.add(axesHelper)
const gridHelper = new THREE.GridHelper(20, 20, 0xffffff, 0xffffff)
gridHelper.material.transparent = true
gridHelper.material.opacity = 0.5
scene.add(gridHelper)
function animation() {
renderer.render(scene, camera)
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})