从前端框架到GIS开发系列课程(33)Three.js常用的几何体(立方体, 球体, 平面)

今天我们介绍一下Three.js中的常用对象几何

往期内容回顾:

2025JavaScript 3D库Three.JS零基础介绍&入门系列教程-CSDN博客文章浏览阅读1.4k次,点赞25次,收藏25次。Three.js是一个功能强大的JavaScript 3D库,提供了丰富的3D对象、、光照和相机,使得3D图形的创建变得简单直观。可帮助GIS开发者在网页上创建交互式的3D图形和动画效果,能高效呈现复杂地形模型、建筑可视化等,让地理信息从二维跃升至三维,直观立体。其性能优化出色,交互体验流畅,对于想学习GIS开发的同学来说,使用 Three.js 可以帮助他们实现很多功能。本期开始我们将学习three.js的入门系列教程,并且在课程结束之后会带着大家手搓一下three.js三维汽车车展小项目。 https://blog.csdn.net/yaogis888/article/details/150484976?spm=1001.2014.3001.5502

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)
  })

若有收获,就点个赞吧!
持续更新webgis开发相关技术/面试/就业内容
关注我学习webgis开发不迷路👇👇👇
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值