08.06. 图元练习示例
06 图元练习示例
上一篇文章中,列举了 Three.js 中内置的 22 种图元(Primitives),那么本文将重点练习,尝试使用这些图元。
复习一下图元的概念:图元 就是 Three.js 中内置的 几何体。
示例代码目标
-
将内置的所有种类图元,除 TextBufferGeometry 以外,其他 21 种图元 逐一练习
TextBufferGeometry 比较特殊,会在稍后一节专门讲解
-
在练习中,尽量都使用图元的 BufferGeometry 类型,不做过多自定义设置
-
最终将所有创建出的形状放置在同一场景中,并进行渲染
示例代码组织逻辑
- 创建 src/components/hello-primitives/ 目录,用来存放本示例所有代码
- 在该目录下,创建 index.tsx 文件,用来构建 Three.js 3 大基础元素:场景、镜头、渲染器
- 在该目录下,创建 index.scss 文件,用来添加需要用到的 CSS 样式
- 在该目录下,分别创建 MyBox.ts、MyCircle.ts ... 等文件,用来依次创建不同的图元实例
- 在 index.stx 文件中,引入各个图元实例,并加入场景中进行渲染
- 修改 App.tsx,将之前编写的 <HelloThreejs /> 替换为我们本示例的组件 <HelloPrimitives />
本示例主要用来练习 Three.js,对于示例中使用到的一些 ES6、React Hooks、TypeScript 等相关知识,若非必要,一般情况下就不再过多讲解说明。
示例代码
index.tsx
import { useRef, useEffect, useCallback } from 'react'
import * as Three from 'three'
import './index.scss'
import myBox from './my-box'
import myCircle from './my-circle'
import myCone from './my-cone'
import myCylinder from './my-cylinder'
import myDodecahedron from './my-dodecahedron'
import myEdges from './my-edges'
import myExtrude from './my-extrude'
import myIcosahedron from './my-icosahedron'
import myLathe from './my-lathe'
import myOctahedron from './my-octahedron'
import myParametric from './my-parametric'
import myPlane from './my-plane'
import myPolyhedron from './my-polyhedron'
import myRing from './my-ring'
import myShape from './my-shape'
import mySphere from './my-sphere'
import myTetrahedron from './my-tetrahedron'
import myTorus from './my-torus'
import myTorusKnot from './my-torus-knot'
import myTube from './my-tube'
import myWireframe from './my-wireframe'
const meshArr: (Three.Mesh | Three.LineSegments)[] = []
const HelloPrimitives = () => {
const canvasRef = useRef<HTMLCanvasElement>(null)
const rendererRef = useRef<Three.WebGLRenderer | null>(null)
const cameraRef = useRef<Three.PerspectiveCamera | null>(null)
const createMaterial = () => {
const material = new Three.MeshPhongMaterial({ side: Three.DoubleSide })
const hue = Math.floor(Math.random() * 100) / 100 //随机获得一个色相
const saturation = 1 //饱和度
const luminance = 0.5 //亮度
material.color.setHSL(hue, saturation, luminance)
return material
}
const createInit = useCallback(
() => {
if (canvasRef.current === null) {
return
}
meshArr.length = 0 //以防万一,先清空原有数组
//初始化场景
const scene = new Three.Scene()
scene.background = new Three.Color(0xAAAAAA)
//初始化镜头
const camera = new Three.PerspectiveCamera(40, 2, 0.1, 1000)
camera.position.z = 120
cameraRef.current = camera
//初始化渲染器
const renderer = new Three.WebGLRenderer({ canvas: canvasRef.current as HTMLCanvasElement })
rendererRef.current = renderer
//添加 2 盏灯光
const light0 = new Three.DirectionalLight(0xFFFFFF, 1)
light0.position.set(-1, 2, 4)
scene.add(light0)
const light1 = new Three.DirectionalLight(0xFFFFFF, 1)
light0.position.set(1, -2, -4)
scene.add(light1)
//获得各个 solid 类型的图元实例,并添加到 solidPrimitivesArr 中
const solidPrimitivesArr: Three.BufferGeometry[] = []
solidPrimitivesArr.push(myBox, myCircle, myCone, myCylinder, myDodecahedron)
solidPrimitivesArr.push(myExtrude, myIcosahedron, myLathe, myOctahedron, myParametric)
solidPrimitivesArr.push(myPlane, myPolyhedron, myRing, myShape, mySphere)
solidPrimitivesArr.push(myTetrahedron, myTorus, myTorusKnot, myTube)
//将各个 solid 类型的图元实例转化为网格,并添加到 primitivesArr 中
solidPrimitivesArr.forEach((item) => {
const material = createMaterial() //随机获得一种颜色材质
const mesh = new Three.Mesh(item, material)
meshArr.push(mesh) //将网格添加到网格数组中
})
//获得各个 line 类型的图元实例,并添加到 meshArr 中
const linePrimitivesArr: Three.BufferGeometry[] = []
linePrimitivesArr.push(myEdges, myWireframe)
//将各个 line 类型的图元实例转化为网格,并添加到 meshArr 中
linePrimitivesArr.forEach((item) => {
const material = new Three.LineBasicMaterial({ color: 0x000000 })
const mesh = new Three.LineSegments(item, material)
meshArr.push(mesh)
})
//定义物体在画面中显示的网格布局
const eachRow = 5 //每一行显示 5 个
const spread = 15 //行高 和 列宽
//配置每一个图元实例,转化为网格,并位置和材质后,将其添加到场景中
meshArr.forEach((mesh, index) => {
//我们设定的排列是每行显示 eachRow,即 5 个物体、行高 和 列宽 均为 spread 即 15
//因此每个物体根据顺序,计算出自己所在的位置
const row = Math.floor(index / eachRow) //计算出所在行
const column = index % eachRow //计算出所在列
mesh.position.x = (column - 2) * spread //为什么要 -2 ?
//因为我们希望将每一行物体摆放的单元格,依次是:-2、-1、0、1、2,这样可以使每一整行物体处于居中显示
mesh.position.y = (2 - row) * spread
scene.add(mesh) //将网格添加到场景中
})
//添加自动旋转渲染动画
const render = (time: number) => {
time = time * 0.001
meshArr.forEach(item => {
item.rotation.x = time
item.rotation.y = time
})
renderer.render(scene, camera)
window.requestAnimationFrame(render)
}
window.requestAnimationFrame(render)
},
[canvasRef],
)
const resizeHandle = () => {
//根据窗口大小变化,重新修改渲染器的视椎
if (rendererRef.current === null || cameraRef.current === null) {
return
}
const canvas = rendererRef.current.domElement
cameraRef.current.aspect = canvas.clientWidth / canvas.clientHeight
cameraRef.current.updateProjectionMatrix()
rendererRef.current.setSize(canvas.clientWidth, canvas.clientHeight, false)
}
//组件首次装载到网页后触发,开始创建并初始化 3D 场景
useEffect(() => {
createInit()
resizeHandle()
window.addEventListener('resize', resizeHandle)
return () => {
window.removeEventListener('resize', resizeHandle)
}
}, [canvasRef, createInit])
return (
<canvas ref={canvasRef} className='full-screen' />
)
}
export default HelloPrimitives
my-box.ts
import { BoxBufferGeometry } from "three"
const width = 8
const height = 8
const depth = 8
const myBox = new BoxBufferGeometry(width, height, depth)
export default myBox
my-circle.ts
import { CircleBufferGeometry } from "three"
const radius = 7
const segments = 24
const myCircle = new CircleBufferGeometry(radius,segments)
export default myCircle
由于图元实在是太多,这里省略掉一些图元相关代码......
my-wireframe.ts
import { BoxBufferGeometry, WireframeGeometry } from "three";
const width = 8;
const height = 8;
const depth = 8
const myWireframe = new WireframeGeometry(new BoxBufferGeometry(width, height, depth))
export default myWireframe
想要查看完整的图元示例代码,可以访问:https://threejsfundamentals.org/threejs/threejs-primitives.html
本文示例中的代码源头,都来源于上面那个网页,尽管该网页 JS 中创建图元使用的是 JS 而非 TS。
你可以通过查看该页面中内嵌的 JS 代码,来补齐其他图元对应的创建写法。
图元种类这么多,本文只是带着你一块过一遍,具体每一个图元具体的参数含义,以后总会慢慢了解的。
下一节,我们将讲述一下 TextBufferGeometry 的用法。