Three.js 曲线

基础案例

生成圆弧顶点

html 复制代码
<script lang="ts" setup>

import { onMounted, ref } from 'vue'
import { camera, renderer } from '@/views/three3W/RendererCamera.ts'
import {
  AmbientLight,
  BufferAttribute,
  BufferGeometry, Line,
  LineBasicMaterial, LineLoop,
  Scene
} from 'three'

defineOptions({
  name: 'AboutView2'
})
const scene = new Scene()
const cvRef = ref<HTMLDivElement>()

const bufferGeometry = new BufferGeometry()
// 圆弧半径
const R = 100
// 分段数量
const N = 50
// 两个相邻点间的弧度
const sp = 2 * Math.PI / N

// 批量生成圆弧上的顶点数据
const arr = []
for (let i = 0; i < N; i++) {
  // 当前点弧度
  const angle = sp * i
  // 以坐标圆点为中心,在 XOY 平面上生成圆弧上的顶点数据
  const x = R * Math.cos(angle)
  const y = R * Math.sin(angle)
  arr.push(x, y, 0)
}
// 类型数组创建顶点数据
const vertices = new Float32Array(arr)
// 创建属性缓冲区对象
// 3个为一组,表示一个顶点的xyz坐标
const attribute = new BufferAttribute(vertices, 3)
// 设置几何体位置属性
bufferGeometry.attributes.position = attribute

// 线材质
const material = new LineBasicMaterial({
  color: 0xff0000
})

// 线模型对象。Line,LineLoop, LineSegments
const line = new Line(bufferGeometry, material) // 有缺口,未闭合
// const line = new LineLoop(bufferGeometry, material) // 闭合,循环线模型

scene.add(line)

// 环境光
const ambientLight = new AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)

// 渲染循环
function render() {
  renderer.render(scene, camera) //执行渲染操作
  requestAnimationFrame(render)
}

onMounted(() => {
  if (cvRef.value) {
    cvRef.value.appendChild(renderer.domElement)
    render()
  }
})
</script>
<template>
  <div ref="cvRef" class="cv"></div>
</template>

使用三维向量定义顶点数据

js 复制代码
import { onMounted, ref } from 'vue'
import { camera, renderer } from '@/views/three3W/RendererCamera.ts'
import {
  AmbientLight,
  BufferGeometry, Points, PointsMaterial,
  Scene, Vector2, Vector3
} from 'three'

defineOptions({
  name: 'AboutView2'
})
const scene = new Scene()
const cvRef = ref<HTMLDivElement>()

const bufferGeometry = new BufferGeometry()

// 使用的是三维向量
// const pointsArr = [
//   new Vector3(0, 0, 0),
//   new Vector3(0, 100, 0),
//   new Vector3(0, 100, 100),
//   new Vector3(0, 0, 100)
// ]
// 使用二维向量
const pointsArr = [
  new Vector2(0, 0),
  new Vector2(100, 0),
  new Vector2(100, 100),
  new Vector2(0, 100)
]

// 把数组pointsArr里面的坐标数据提取出来,赋值给`geometry.attributes.position`属性
bufferGeometry.setFromPoints(pointsArr)
console.log('几何体变化', bufferGeometry.attributes.position)

// 线材质
const material = new PointsMaterial({
  color: 0xffff00,
  size: 10
})

const points = new Points(bufferGeometry, material)

scene.add(points)

// 环境光
const ambientLight = new AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)

曲线案例

父类是:Curve

获取点的方法

getPoints

按曲线参数 t(0→1)等分取点,当曲线弯曲程度不同、斜率变化较快的地方,会密集取点,参数空间等分

getSpacedPoints

按曲线真实长度等分取点,点与点之间在空间中的距离基本相等,曲线长度等分

绘制椭圆点

js 复制代码
import { onMounted, ref } from 'vue'
import { camera, renderer } from '@/views/three3W/RendererCamera.ts'
import {
  AmbientLight,
  BufferGeometry, EllipseCurve, Points, PointsMaterial,
  Scene
} from 'three'

defineOptions({
  name: 'AboutView2'
})
const scene = new Scene()
const cvRef = ref<HTMLDivElement>()

const ellipseCurve = new EllipseCurve(0, 0, 100, 50)
// 获取顶点数据
const points1 = ellipseCurve.getPoints(50)

const bufferGeometry = new BufferGeometry()
bufferGeometry.setFromPoints(points1)

// 点材质
const material = new PointsMaterial({
  color: 0xffff00,
  size: 10
})

const points = new Points(bufferGeometry, material)

scene.add(points)

// 环境光
const ambientLight = new AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)

圆弧线

圆弧线ArcCurve的父类是椭圆弧线EllipseCurve,语法和椭圆弧线EllipseCurve相似,区别是参数3和参数4不同,椭圆需要定义xRadius和yRadius两个半径,圆只需要通过参数3定义半径aRadius即可。

参数 含义
aX, aY 圆心坐标
aRadius 圆弧半径
aStartAngle 弧线开始角度,从x轴正半轴开始,默认0,弧度单位
aEndAngle 弧线结束角度,从x轴正半轴算起,默认2 x Math.PI,弧度单位
aClockwise 是否顺时针绘制,默认值为false

曲线的精度

js 复制代码
//曲线上取点,参数表示取点细分精度
const pointsArr = arc.getPoints(50); //分段数50,返回51个顶点
// const pointsArr = arc.getPoints(10);//取点数比较少,圆弧线不那么光滑

弧线起始角度

参数4和5表示圆弧线的起始角度,three.js默认是一个完整的圆弧,其实你也可以绘制一个半圆弧。

js 复制代码
// 完整圆弧
const arc = new THREE.ArcCurve(0, 0, 100, 0, 2 * Math.PI);
// 半圆弧
const arc = new THREE.ArcCurve(0, 0, 100, 0, Math.PI);
// 四分之一圆弧
const arc = new THREE.ArcCurve(0, 0, 100, 0, Math.PI/2);

顺逆时针

参数6默认false,逆时针绘制圆弧

js 复制代码
const arc = new THREE.ArcCurve(0, 0, 100, 0, Math.PI/2,false)
// 参数6设置为true,顺时针绘制圆弧
const arc = new THREE.ArcCurve(0, 0, 100, 0, Math.PI/2,true)

样条曲线

对于一些不规则的曲线,很难用一个圆、椭圆或抛物线函数去描述,这时候,可以使用threejs提供的样条曲线或贝塞尔曲线去表达。

3D 曲线

js 复制代码
import { onMounted, ref } from 'vue'
import { camera, renderer } from '@/views/three3W/RendererCamera.ts'
import {
  AmbientLight, BufferGeometry, CatmullRomCurve3, Line, Points, PointsMaterial,
  Scene, Vector3
} from 'three'

defineOptions({
  name: 'AboutView2'
})
const scene = new Scene()
const cvRef = ref<HTMLDivElement>()

// 三维向量Vector3创建一组顶点坐标
const arr = [
  new Vector3(-50, 20, 90),
  new Vector3(-10, 40, 40),
  new Vector3(0, 0, 0),
  new Vector3(60, -60, 0),
  new Vector3(70, 0, 80)
]
// 三维样条曲线
const curve = new CatmullRomCurve3(arr)

// 获取顶点数据
const points1 = curve.getPoints(50)

const bufferGeometry = new BufferGeometry()
bufferGeometry.setFromPoints(points1)

// 点材质
const material = new PointsMaterial({
  color: 0xffff00,
  size: 10
})

const line = new Line(bufferGeometry, material)

scene.add(line)

// 用点模型可视化样条曲线经过的顶点位置
const geometry2 = new BufferGeometry()
geometry2.setFromPoints(arr)
const material2 = new PointsMaterial({
  color: 0xff00ff,
  size: 10,
})
//点模型对象
const points = new Points(geometry2, material2)
scene.add(points)
// 环境光
const ambientLight = new AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)

// 渲染循环
function render() {
  renderer.render(scene, camera) //执行渲染操作
  requestAnimationFrame(render)
}

2D 曲线

js 复制代码
// 二维向量Vector2创建一组顶点坐标
const arr = [
    new Vector2(-100, 0),
    new Vector2(0, 30),
    new Vector2(100, 0),
]
// 二维样条曲线
const curve = new SplineCurve(arr)

贝塞尔曲线

二维二次贝塞尔曲线 QuadraticBezierCurve

前面三个参数是二维向量对象 Vector2

观察二维二次贝塞尔曲线

贝塞尔曲线,分为三个点:起点、控制点和终点

经过 p1、p3,起点与终点固定,不经过 p2,p2 只控制曲线弯曲方向。相切与 p12、p23 ,起点、终点的切线方向由 p2 决定。

js 复制代码
import { onMounted, ref } from 'vue'
import { camera, renderer } from '@/views/three3W/RendererCamera.ts'
import {
  AmbientLight, BufferGeometry, Line, LineBasicMaterial, Points, PointsMaterial, QuadraticBezierCurve,
  Scene, Vector2
} from 'three'

const scene = new Scene()

// p1、p2、p3表示三个点坐标
// p1、p3是曲线起始点,p2是曲线的控制点
const p1 = new Vector2(-80, 0)
const p2 = new Vector2(20, 100)
const p3 = new Vector2(80, 0)

// 二维二次贝赛尔曲线
const curve = new QuadraticBezierCurve(p1, p2, p3)

const points1 = curve.getPoints(100)
const bufferGeometry = new BufferGeometry()
bufferGeometry.setFromPoints(points1)

const line = new Line(bufferGeometry, new LineBasicMaterial({ color: 0x00fffff }))
scene.add(line)

// 用点模型可视化样条曲线经过的顶点位置
const geometry2 = new BufferGeometry()
geometry2.setFromPoints([ p1, p2, p3 ])
const material2 = new PointsMaterial({ color: 0xff00ff, size: 10 })
scene.add(new Points(geometry2, material2))

// 三个点构成的线条
const line2 = new Line(geometry2, new LineBasicMaterial())
scene.add(line2)

const ambientLight = new AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)

function render() {
  renderer.render(scene, camera)
  requestAnimationFrame(render)
}

三维二次贝赛尔曲线QuadraticBezierCurve3

三维二次贝赛尔曲线 QuadraticBezierCurve3 与二维二次贝赛尔曲线QuadraticBezierCurve 区别就是多了一个维度

js 复制代码
// p1、p2、p3表示三个点坐标
const p1 = new Vector3(-80, 0, 0)
const p2 = new Vector3(20, 100, 0)
const p3 = new Vector3(80, 0, 100)
// 三维二次贝赛尔曲线
const curve = new QuadraticBezierCurve3(p1, p2, p3)

二维三次贝塞尔曲线CubicBezierCurve

二维三次贝塞尔曲线 CubicBezierCurve 与二维二次贝赛尔曲线QuadraticBezierCurve 区别就是多了一个控制点。

js 复制代码
// p1、p2、p3、p4表示4个点坐标
// p1、p4是曲线起始点,p2、p3是曲线的控制点
const p1 = new Vector2(-80, 0)
const p2 = new Vector2(-40, 50)
const p3 = new Vector2(50, 50)
const p4 = new Vector2(80, 0)
// 二维三次贝赛尔曲线
const curve = new CubicBezierCurve(p1, p2, p3, p4)

三维三次贝赛尔曲线 CubicBezierCurve3

三维三次贝赛尔曲线 CubicBezierCurve3 与二维三次贝塞尔曲线CubicBezierCurve 区别就是多了一个维度

js 复制代码
const p1 = new Vector3(-80, 0, 0)
const p2 = new Vector3(-40, 50, 0)
const p3 = new Vector3(50, 50, 0)
const p4 = new Vector3(80, 0, 100)
// 三维三次贝赛尔曲线
const curve = new CubicBezierCurve3(p1, p2, p3, p4)

实现飞线轨迹

三维样条曲线 CatmullRomCurve3

js 复制代码
import { onMounted, ref } from 'vue'
import { camera, renderer } from '@/views/three3W/RendererCamera.ts'
import {
  AmbientLight, AxesHelper, BufferGeometry, CatmullRomCurve3, Line, LineBasicMaterial, Points,
  PointsMaterial, Scene, Vector3
} from 'three'

const scene = new Scene()
const cvRef = ref<HTMLDivElement>()

// p1、p3轨迹线起始点坐标
const p1 = new Vector3(-100, 0, -100)
const p3 = new Vector3(100, 0, 100)
// 计算p1和p3的中点坐标
const x2 = (p1.x + p3.x) / 2
const z2 = (p1.z + p3.z) / 2
const h = 50
const p2 = new Vector3(x2, h, z2)

const arr = [ p1, p2, p3 ]
// 三维样条曲线
const curve = new CatmullRomCurve3(arr)

const points1 = curve.getPoints(100)
const bufferGeometry = new BufferGeometry()
bufferGeometry.setFromPoints(points1)

const line = new Line(bufferGeometry, new LineBasicMaterial({ color: 0x00fffff }))
scene.add(line)

const ambientLight = new AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)

const axesHelper = new AxesHelper(100)
scene.add(axesHelper)

const geometry = new BufferGeometry()
geometry.setFromPoints(arr)
const pointsMaterial = new PointsMaterial({
  color: 0xff00ff,
  size: 10
})
const points = new Points(geometry, pointsMaterial)
scene.add(points)

function render() {
  renderer.render(scene, camera)
  requestAnimationFrame(render)
}

三维二次贝赛尔曲线QuadraticBezierCurve3实现飞线轨迹

js 复制代码
// p1、p3轨迹线起始点坐标
const p1 = new Vector3(-100, 0, -100)
const p3 = new Vector3(100, 0, 100)
// 计算p1和p3的中点坐标
const x2 = (p1.x + p3.x) / 2
const z2 = (p1.z + p3.z) / 2
const h = 100
const p2 = new Vector3(x2, h, z2)
// 三维二次贝赛尔曲线
const curve = new QuadraticBezierCurve3(p1, p2, p3)

组合曲线

直线线段简介

3D 直线 LineCurve3 和 2D 直线LineCurve

3D直线线段 LineCurve3,参数是表示x、y、z坐标的三维向量 Vector3 对象

js 复制代码
// 圆弧半径
const R = 80
// 直线部分高度
const H = 200
// 直线1
const line1 = new LineCurve3(new Vector3(R, H), new Vector3(R, 0))
// 直线2
const line2 = new LineCurve(new Vector2(-R, 0), new Vector2(-R, H))

LineCurve 创建两条直线线段,ArcCurve 绘制一段圆弧线,然后把两段直线和一段圆弧线,通过组合曲线的 CurvePath.curves 属性拼接起来。

js 复制代码
import { onMounted, ref } from 'vue'
import { camera, renderer } from '@/views/three3W/RendererCamera.ts'
import {
  AmbientLight, ArcCurve, AxesHelper, BufferGeometry, CurvePath, Line, LineBasicMaterial, LineCurve,
  LineCurve3, Points, PointsMaterial, Scene, Vector2, Vector3
} from 'three'

defineOptions({
  name: 'AboutView2'
})
const scene = new Scene()
const cvRef = ref<HTMLDivElement>()

// 圆弧半径
const R = 80
// 直线部分高度
const H = 200
// 直线1
const line1 = new LineCurve3(new Vector3(R, H), new Vector3(R, 0))
// 圆弧
const arc = new ArcCurve(0, 0, R, 0, Math.PI, true)
// 直线2
const line2 = new LineCurve(new Vector2(-R, 0), new Vector2(-R, H))

// CurvePath创建一个组合曲线对象
const curvePath = new CurvePath()
// line1, arc, line2拼接出来一个U型轮廓曲线,注意顺序
curvePath.curves.push(line1, arc, line2)

// 执行.getPoints(),直线部分不会像曲线返回中间多余点,只需要起始点即可。
// const pointsArr = curvePath.getSpacedPoints(16) // 按照长度
const pointsArr = curvePath.getPoints(16) // 只关心曲线部分
const geometry = new BufferGeometry()
geometry.setFromPoints(pointsArr)

const lineMaterial = new LineBasicMaterial({ color: 0x00ffff })
const line = new Line(geometry, lineMaterial)
scene.add(line)

// 可视化curve.getPoints从曲线上获取的点坐标
const material2 = new PointsMaterial({
  color: 0xff00ff,
  size: 10,
})
//点模型对象
const points = new Points(geometry, material2)
scene.add(points)

const ambientLight = new AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)

const axesHelper = new AxesHelper(100)
scene.add(axesHelper)

function render() {
  renderer.render(scene, camera)
  requestAnimationFrame(render)
}

注意:曲线首尾相接

组合曲线的坐标顺序和线条组合顺序不能随意写,总的方向,就是先确定整个曲线的起点,然后沿着一个方向依次绘制不同曲线,确保不同曲线首尾相接。

  • 直线的起点是直线的第一个参数
  • 圆弧线的起点,默认就是从x轴正半轴开始

曲线路径管道 TubeGeometry

管道TubeGeometry几何体的功能就是基于一个3D曲线路径,生成一个管道几何体。

构造函数格式:TubeGeometry(path, tubularSegments, radius, radiusSegments, closed)

参数
path 扫描路径,路径要用三维曲线
tubularSegments 路径方向细分数,默认64
radius 管道半径,默认1
radiusSegments 管道圆弧细分数,默认8
closed Boolean值,管道是否闭合
js 复制代码
import { onMounted, ref } from 'vue'
import { camera, renderer } from '@/views/three3W/RendererCamera.ts'
import {
  AmbientLight, AxesHelper, CatmullRomCurve3, DoubleSide,
  Mesh, MeshLambertMaterial, Scene, TubeGeometry, Vector3
} from 'three'

const scene = new Scene()
const cvRef = ref<HTMLDivElement>()

// 三维样条曲线
const path = new CatmullRomCurve3([
  new Vector3(-50, 20, 90),
  new Vector3(-10, 40, 40),
  new Vector3(0, 0, 0),
  new Vector3(60, -60, 0),
  new Vector3(70, 0, 80)
])

// path: 路径 100:沿着轨迹细分数  3:管道半径 30:管道截面圆细分数
const geometry = new TubeGeometry(path, 100, 3, 30)
// 双面才可以看到内部材质
const material2 = new MeshLambertMaterial({ side: DoubleSide, color: 0x00ffff })

const mesh = new Mesh(geometry, material2)
scene.add(mesh)

const ambientLight = new AmbientLight(0xffffff, 1)
scene.add(ambientLight)

const axesHelper = new AxesHelper(100)
scene.add(axesHelper)

function render() {
  renderer.render(scene, camera)
  requestAnimationFrame(render)
}

测试其他曲线

js 复制代码
// 直线
const path = new LineCurve3(
    new Vector3(0, 100, 0),
    new Vector3(0, 0, 0),
)

三维二次贝塞尔曲线生成管道几何体

js 复制代码
// p1、p2、p3表示三个点坐标
const p1 = new Vector3(-80, 0, 0)
const p2 = new Vector3(20, 100, 0)
const p3 = new Vector3(80, 0, 100)
// 三维二次贝赛尔曲线
const path = new QuadraticBezierCurve3(p1, p2, p3)

CurvePath多段路径生成管道案例

js 复制代码
import { onMounted, ref } from 'vue'
import { camera, renderer } from '@/views/three3W/RendererCamera.ts'
import {
  AmbientLight, AxesHelper, CurvePath, DoubleSide, LineCurve3,
  Mesh, MeshLambertMaterial, QuadraticBezierCurve3, Scene, TubeGeometry, Vector3
} from 'three'

defineOptions({
  name: 'AboutView2'
})
const scene = new Scene()
const cvRef = ref<HTMLDivElement>()


// 创建多段线条的顶点数据
const p1 = new Vector3(0, 0,100)
const p2 = new Vector3(0, 0,30);
const p3 = new Vector3(0, 0,0);
const p4 = new Vector3(30, 0, 0);
const p5 = new Vector3(100, 0, 0);
// 1. 3D直线线段
const line1 = new LineCurve3(p1, p2);
// 2. 三维二次贝塞尔曲线
const curve = new QuadraticBezierCurve3(p2, p3, p4);
// 3. 3D直线线段
const line2 = new LineCurve3(p4, p5);

const curvePath = new CurvePath();
// 三条线拼接为一条曲线
curvePath.curves.push(line1, curve, line2);

// CurvePath:路径   40:沿着轨迹细分数  2:管道半径   25:管道截面圆细分数
const geometry = new TubeGeometry(curvePath, 50, 2, 25);

// 双面才可以看到内部材质
const material2 = new MeshLambertMaterial({ side: DoubleSide, color: 0x00ffff })

const mesh = new Mesh(geometry, material2)
scene.add(mesh)

const ambientLight = new AmbientLight(0xffffff, 1)
scene.add(ambientLight)

const axesHelper = new AxesHelper(100)
scene.add(axesHelper)

function render() {
  renderer.render(scene, camera)
  requestAnimationFrame(render)
}

三维管道线框可视化

js 复制代码
import { ref } from 'vue'
import {
  Color, DirectionalLight, DoubleSide, Group, LineSegments,
  LineBasicMaterial, Mesh, MeshPhongMaterial, PerspectiveCamera, Scene, TubeGeometry,
  Vector3, WireframeGeometry, WebGLRenderer, CatmullRomCurve3
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

defineOptions( {
  name: 'AboutView2'
} )

// 初始化场景
const scene = new Scene()
scene.background = new Color( 0x444444 )

// 创建相机
const camera = new PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 50 )
camera.position.set( 0, 0, 30 )

// 添加光源
const light0 = new DirectionalLight( 0xffffff, 3 )
light0.position.set( 0, 200, 0 )

const lights1 = new DirectionalLight( 0xffffff, 3 )
lights1.position.set( 100, 200, 100 )

const lights2 = new DirectionalLight( 0xffffff, 3 )
lights2.position.set( -100, -200, -100 )
scene.add( light0, lights1, lights2 )

// 渲染器与控制器
const renderer = new WebGLRenderer( { antialias: true } )
renderer.setPixelRatio( window.devicePixelRatio )
renderer.setSize( window.innerWidth, window.innerHeight )
document.body.appendChild( renderer.domElement )
const orbit = new OrbitControls( camera, renderer.domElement )
orbit.enableZoom = false

// 自适应窗口
window.addEventListener( 'resize', function () {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  renderer.setSize( window.innerWidth, window.innerHeight )
}, false )

// 基础分组(保留原逻辑)
const group = new Group()

const lineMaterial = new LineBasicMaterial( { color: 0xffffff, transparent: true, opacity: 0.5 } )

const meshMaterial = new MeshPhongMaterial( {
  color: 0x156289,
  emissive: 0x072534,
  side: DoubleSide,
  flatShading: true
} )

// 动画循环
function render() {
  requestAnimationFrame( render )
  group.rotation.x += 0.005
  group.rotation.y += 0.005
  renderer.render( scene, camera )
}

const cvRef = ref()

// 这里线框可视化
function initTube() {
  const data = { segments: 20, radius: 2, radialSegments: 8 }
  const path = new CatmullRomCurve3( [
    new Vector3( -50, 20, 90 ),
    new Vector3( -10, 40, 40 ),
    new Vector3( 0, 0, 0 ),
    new Vector3( 60, -60, 0 ),
    new Vector3( 70, 0, 80 )
  ] )
  const tubeGeometry = new TubeGeometry( path, data.segments, data.radius, data.radialSegments, false )

  group.add( new LineSegments( new WireframeGeometry( tubeGeometry ), lineMaterial ) )
  group.add( new Mesh( tubeGeometry, meshMaterial ) )
}

initTube()

scene.add( group )

render()

旋转成型

LatheGeometry可以利用一个2D轮廓,经过旋转变换生成一个3D的几何体曲面

格式:LatheGeometry(points, segments, phiStart, phiLength)

参数
points Vector2表示的坐标数据组成的数组
segments 圆周方向细分数,默认12
phiStart 开始角度,默认0
phiLength 旋转角度,默认2π
js 复制代码
// Vector2表示的三个点坐标,三个点构成的轮廓相当于两端直线相连接
const pointsArr = [ new Vector2(50, 60), new Vector2(25, 0), new Vector2(50, -60) ]
// LatheGeometry:pointsArr轮廓绕y轴旋转生成几何体曲面
// pointsArr:旋转几何体的旋转轮廓形状
const geometry = new LatheGeometry(pointsArr, 30)

const meshMaterial = new MeshLambertMaterial({
  color: 0x156289,
  side: DoubleSide // 默认是单面,改成两面都可以看到
})

scene.add(new Mesh(geometry, meshMaterial))

曲线生成旋转轮廓

js 复制代码
// 通过三个点定义一个二维样条曲线
const curve = new SplineCurve([
  new Vector2(50, 60),
  new Vector2(25, 0),
  new Vector2(50, -60)
])
//曲线上获取点,作为旋转几何体的旋转轮廓
const pointsArr = curve.getPoints(50)
console.log('旋转轮廓数据', pointsArr)
// LatheGeometry:pointsArr轮廓绕y轴旋转生成几何体曲面
const geometry = new LatheGeometry(pointsArr, 30)

const meshMaterial = new MeshLambertMaterial({
  color: 0x156289,
  side: DoubleSide
})

scene.add(new Mesh(geometry, meshMaterial))

轮廓填充

js 复制代码
import {
  Color, DirectionalLight, Mesh, PerspectiveCamera, Scene,
  WebGLRenderer, Vector2, MeshLambertMaterial, Shape, ShapeGeometry
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

const scene = new Scene()
scene.background = new Color(0x444444)

const camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 10, 1000)
camera.position.set(0, 0, 60)

const light0 = new DirectionalLight(0xffffff, 3)
light0.position.set(0, 200, 0)

const lights1 = new DirectionalLight(0xffffff, 3)
lights1.position.set(100, 200, 100)

const lights2 = new DirectionalLight(0xffffff, 3)
lights2.position.set(-100, -200, -100)
scene.add(light0, lights1, lights2)

const renderer = new WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
new OrbitControls(camera, renderer.domElement)

// 一组二维向量表示一个多边形轮廓坐标
const pointsArr = [
  new Vector2(-50, -50),
  new Vector2(-60, 0),
  new Vector2(0, 50),
  new Vector2(60, 0),
  new Vector2(50, -50)
]
// Shape表示一个平面多边形轮廓,参数是二维向量构成的数组pointsArr
const shape = new Shape(pointsArr)

// 把五边形轮廓Shape作为ShapeGeometry的参数,形成一个多边形平面几何体。
const shapeGeometry = new ShapeGeometry(shape)
const meshMaterial = new MeshLambertMaterial({
  color: 0x156289
})
scene.add(new Mesh(shapeGeometry, meshMaterial))

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

拉伸

基于一个基础的平面轮廓Shape进行变换,生成一个几何体

js 复制代码
// Shape表示一个平面多边形轮廓
const shape = new Shape([
  // 按照特定顺序,依次书写多边形顶点坐标
  new Vector2(-50, -50), //多边形起点
  new Vector2(-50, 50),
  new Vector2(50, 50),
  new Vector2(50, -50),
])
// 拉伸造型
const geometry = new ExtrudeGeometry(
    shape, //二维轮廓
    { depth: 20 } // 拉伸长度
)

const meshMaterial = new MeshLambertMaterial({
  color: 0x156289
})
scene.add(new Mesh(geometry, meshMaterial))

倒圆角

js 复制代码
const geometry = new ExtrudeGeometry(
    shape,
    {
      depth: 20,
      bevelThickness: 5, // 倒角尺寸:拉伸方向
      bevelSize: 5, // 倒角尺寸:垂直拉伸方向
      bevelSegments: 20, // 倒圆角:倒角细分精度,默认3
    }
)

倒直角

js 复制代码
const geometry = new ExtrudeGeometry(
    shape,
    {
      depth: 20,
      bevelSegments: 1, // 倒圆角:倒角细分精度,默认3
    }
)

拉伸取消默认倒角

js 复制代码
const geometry = new ExtrudeGeometry(
    shape,
    {
      depth: 20,
      bevelEnabled: false, //禁止倒角,默认true
    }
)

直角是1,倒角要给一个大的值

扫描

让一个平面轮廓Shape沿着曲线扫描成型

js 复制代码
// 扫描轮廓:Shape表示一个平面多边形轮廓
const shape = new Shape([
  // 按照特定顺序,依次书写多边形顶点坐标
  new Vector2(0, 0), //多边形起点
  new Vector2(0, 10),
  new Vector2(10, 10),
  new Vector2(10, 0),
])

// 扫描轨迹:创建轮廓的扫描轨迹(3D样条曲线)
const curve = new CatmullRomCurve3([
  new Vector3(-10, -50, -50),
  new Vector3(10, 0, 0),
  new Vector3(8, 50, 50),
  new Vector3(-5, 0, 100)
])

// 扫描造型:扫描默认没有倒角
const geometry = new ExtrudeGeometry(
    shape, //扫描轮廓
    {
      extrudePath: curve, // 扫描轨迹
      steps: 100 // 沿着路径细分精度,越大越光滑
    }
)

const meshMaterial = new MeshLambertMaterial({
  color: 0x156289
})
scene.add(new Mesh(geometry, meshMaterial))

多边形轮廓

多边形轮廓 Shape 的父类 Path

Shape 的父类是 Path, Path 提供了直线、圆弧、贝塞尔、样条等绘制方法,Shape 也会从父类是 Path 继承这些图形绘制方法。

currentPoint 表示当前路径的终点,也是最后一个点,默认值 Vector2(0,0)

js 复制代码
const shape1 = new Shape() 
console.log(shape1.currentPoint) // Vector2 {x: 0, y: 0}

moveTo() 方法,移动到指定坐标,但不画线

js 复制代码
const shape1 = new Shape()
shape1.moveTo(10, 0)
console.log(shape1.currentPoint) // Vector2 {x: 10, y: 0}

绘制直线 lineTo()

js 复制代码
const shape1 = new Shape()
shape1.moveTo(10, 0)
shape1.lineTo(100, 0)
console.log(shape1.currentPoint) // Vector2 {x: 100, y: 0}

绘制一个矩形轮廓 Shape

js 复制代码
const shape = new Shape()
shape.moveTo(10, 0)
shape.lineTo(100, 0)
shape.lineTo(100, 100)
shape.lineTo(10, 100)

const geometry = new ExtrudeGeometry(
    shape,
    { depth: 20, bevelEnabled: false }
)

const meshMaterial = new MeshLambertMaterial({
  color: 0x156289
})
scene.add(new Mesh(geometry, meshMaterial))

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

多边形轮廓 Shape (圆弧)

圆弧方法.arc()

圆弧 .arc() 参数的圆心坐标是相对当前 .currentPoint 而言,而不是坐标原点。

js 复制代码
// 绘制了一个矩形 + 扇形的轮廓,圆心在(100, 0),半径50。
const shape = new Shape()
shape.lineTo(100 + 50, 0)
// 圆弧.arc 参数的圆心 0,0 坐标是相对当前 .currentPoint 而言,而不是坐标原点
shape.arc(-50, 0, 50, 0, Math.PI / 2) //.currentPoint变为圆弧线结束点坐标
// 绘制直线,直线起点:圆弧绘制结束的点  直线结束点:(0, 0)
shape.lineTo(0, 50)

const geometry = new ShapeGeometry(shape)

const meshMaterial = new MeshLambertMaterial({
  color: 0x156289
})
scene.add(new Mesh(geometry, meshMaterial))

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

也可以用拉伸实现。

js 复制代码
const geometry = new ExtrudeGeometry(shape, {
  depth: 20, // 拉伸长度
  bevelEnabled: false, // 禁止倒角
  curveSegments: 20, // shape曲线对应曲线细分数
})

绝对圆弧方法.absarc()

.absarc() 圆心坐标不受到 .currentPoint 影响,以坐标原点作为参考,这一点和圆弧方法 .arc() 不同。

js 复制代码
const shape = new Shape()
shape.lineTo(100, 0) // .currentPoint变为(100,0)
// absarc圆心坐标不受到 .currentPoint 影响,以坐标原点作为参考
shape.absarc(100, 0, 50, 0, Math.PI / 2) // .currentPoint变为圆弧线结束点坐标
shape.lineTo(0, 50)
const geometry = new ShapeGeometry(shape)

// const geometry = new ExtrudeGeometry(shape, {
//   depth: 20, // 拉伸长度
//   bevelEnabled: false, // 禁止倒角
//   curveSegments: 20, // shape曲线对应曲线细分数
// })

const meshMaterial = new MeshLambertMaterial({
  color: 0x156289, side: DoubleSide
})
scene.add(new Mesh(geometry, meshMaterial))

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

多边形Shape(内孔.holes)

有些多边形 Shape 内部是有孔洞的,这时候就需要借助多边形Shape的内孔.holes属性和Path对象实现。

js 复制代码
const shape = new Shape()
// .lineTo(100, 0) 绘制直线线段,线段起点:.currentPoint,线段结束点:(100,0)
shape.lineTo(100, 0)
shape.lineTo(100, 100)
shape.lineTo(0, 100)

// Shape内孔轮廓
const path1 = new Path() // 圆孔1
path1.absarc(20, 20, 10, 0, Math.PI * 2)
const path2 = new Path() // 圆孔2
path2.absarc(80, 20, 10, 0, Math.PI * 2)
const path3 = new Path() // 方形孔
path3.moveTo(50, 50)
path3.lineTo(80, 50)
path3.lineTo(80, 80)
path3.lineTo(50, 80)
shape.holes.push(path1, path2, path3)

const geometry = new ExtrudeGeometry(shape, {
  depth: 20, // 拉伸长度
  bevelEnabled: false, // 禁止倒角
  curveSegments: 50 // shape曲线对应曲线细分数
})

const meshMaterial = new MeshLambertMaterial({
  color: 0x00caca, side: DoubleSide
})
scene.add(new Mesh(geometry, meshMaterial))

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

模型边界线 EdgesGeometry

长方体边线

js 复制代码
const geometry = new BoxGeometry(50, 50, 50)
const material = new MeshLambertMaterial({
  color: 0x004444,
  transparent: true,
  opacity: 0.5,
})
const mesh = new Mesh(geometry, material)

// 长方体作为 EdgesGeometry 参数创建一个新的几何体
const edges = new EdgesGeometry(geometry)

const line = new LineSegments(edges, new LineBasicMaterial({ color: 0x00ffff }))
    
// 这里要绑定给组,只影响该组
const boxGroup = new Group()
boxGroup.add(mesh, line)

scene.add(boxGroup)

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

圆柱边线

js 复制代码
// 圆形
const geometry = new CylinderGeometry(60, 60, 100, 30)
const material = new MeshLambertMaterial({
  color: 0x004444,
  transparent: true,
  opacity: 0.5
})
const mesh = new Mesh(geometry, material)

const edges = new EdgesGeometry(geometry)
const line = new LineSegments(edges, new LineBasicMaterial({ color: 0x00ffff }))

const boxGroup = new Group()
boxGroup.add(mesh, line)

scene.add(boxGroup)

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

相邻面法线夹角大于30度,才会显示线条

js 复制代码
const edges = new EdgesGeometry(geometry, 30)

加载模型,修改材质

js 复制代码
import {
  Color, DirectionalLight, PerspectiveCamera, Scene,
  WebGLRenderer, MeshLambertMaterial, EdgesGeometry, LineBasicMaterial, LineSegments
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { loadGltf2 } from '@/views/three3W/model.ts'

defineOptions({
  name: 'AboutView3'
})

let scene: Scene
let renderer: WebGLRenderer
let camera: PerspectiveCamera

async function init2() {
  const data = await loadGltf2('/gltf/建筑模型.gltf')
  data.traverse(obj => {
    if (obj.type === 'Mesh') {
      const edges = new EdgesGeometry(obj.geometry)
      obj.add(new LineSegments(edges, new LineBasicMaterial({ color: 0x00ffff })))

      obj.material = new MeshLambertMaterial({
        color: 0x004444,
        transparent: true,
        opacity: 0.5
      })
    }
  })

  scene = data
  init()
}

function init() {
  scene.background = new Color(0x444444)
  camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 10, 1000)
  camera.position.set(0, 0, 60)
  const light0 = new DirectionalLight(0xffffff, 3)
  light0.position.set(0, 200, 0)
  const lights1 = new DirectionalLight(0xffffff, 3)
  lights1.position.set(100, 200, 100)
  const lights2 = new DirectionalLight(0xffffff, 3)
  lights2.position.set(-100, -200, -100)
  scene.add(light0, lights1, lights2)
  renderer = new WebGLRenderer({ antialias: true })
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.setSize(window.innerWidth, window.innerHeight)
  document.body.appendChild(renderer.domElement)
  new OrbitControls(camera, renderer.domElement)
  render()
}

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

init2()

几何体顶点颜色数数据

  • 顶点位置数据:geometry.attributes.position
  • 顶点法向量数据:geometry.attributes.normal
  • 顶点UV数据:geometry.attributes.uv
  • 顶点颜色数据:geometry.attributes.color
js 复制代码
const geometry = new BufferGeometry() //创建一个几何体对象
const vertices = new Float32Array([
  0, 0, 0, // 顶点1坐标
  50, 0, 0, // 顶点2坐标
  0, 25, 0 // 顶点3坐标
])
// 顶点位置
geometry.attributes.position = new BufferAttribute(vertices, 3)

const colors = new Float32Array([
  1, 0, 0, // 顶点1颜色
  0, 0, 1, // 顶点2颜色
  0, 1, 0, // 顶点3颜色
])
// 设置几何体 attributes 属性的颜色 color 属性
// 3 个为一组,表示一个顶点的颜色数据 RGB
geometry.attributes.color = new BufferAttribute(colors, 3)

// 点渲染模式
const material = new PointsMaterial({
  // color: 0x333333, // 使用顶点颜色数据,color 属性可以不用设置
  vertexColors: true, // 默认false,设置为 true 表示使用顶点颜色渲染
  size: 20.0 // 点对象像素尺寸
})
const points = new Points(geometry, material) // 点模型对象

scene.add(points)

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

改成网格模型

js 复制代码
// 点渲染模式
const material = new MeshBasicMaterial({
  // color: 0x333333, // 使用顶点颜色数据,color 属性可以不用设置
  vertexColors: true, // 默认false,设置为 true 表示使用顶点颜色渲染
  side: DoubleSide,
})
const mesh = new Mesh(geometry, material) // 点模型对象

曲线颜色渐变

js 复制代码
const geometry = new BufferGeometry() // 创建一个几何体对象
// 三维样条曲线
const curve = new CatmullRomCurve3([
  new Vector3(-50, 20, 90),
  new Vector3(-10, 40, 40),
  new Vector3(0, 0, 0),
  new Vector3(60, -60, 0),
  new Vector3(70, 0, 80)
])
const pointsArr = curve.getSpacedPoints(100) // 曲线取点
geometry.setFromPoints(pointsArr) // pointsArr赋值给顶点位置属性

// 设置几何体顶点颜色 .attributes.color
const pos = geometry.attributes.position
const count = pos.count // 顶点数量
// 计算每个顶点的颜色值
const colorsArr = []
for (let i = 0; i < count; i++) {
  const percent = i / count // 点索引值相对所有点数量的百分比
  // 根据顶点位置顺序大小设置颜色渐变
  // 红色分量从0到1变化,蓝色分量从1到0变化
  colorsArr.push(percent, 0, 1 - percent) //蓝色到红色渐变色
}
// 类型数组创建顶点颜色color数据
const colors = new Float32Array(colorsArr)
// 设置几何体attributes属性的颜色color属性
geometry.attributes.color = new BufferAttribute(colors, 3)

// 点渲染模式
const material = new LineBasicMaterial({
  vertexColors: true, // 默认false,设置为 true 表示使用顶点颜色渲染
  side: DoubleSide,
})
const line = new Line(geometry, material)

scene.add(line)

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

Color颜色渐变插值

颜色对象 Color 颜色渐变插值方法 .lerpColors() 和 .lerp()

意思是颜色混合,按照一定比例进行调配。

js 复制代码
const c1 = new Color(0xff0000) //红色
const c2 = new Color(0x0000ff) //蓝色
const c = new Color()
c.lerpColors(c1, c2, 0) // 0 表示用c1颜色,不和 c2混合
console.log('颜色插值结果', c)

const geometry = new BufferGeometry() // 创建一个几何体对象
const curve = new CatmullRomCurve3([
  new Vector3(-50, 20, 90),
  new Vector3(-10, 40, 40),
  new Vector3(0, 0, 0),
  new Vector3(60, -60, 0),
  new Vector3(70, 0, 80)
])
const pointsArr = curve.getSpacedPoints(100) // 曲线取点
geometry.setFromPoints(pointsArr) // pointsArr赋值给顶点位置属性

const material = new LineBasicMaterial({
  color: c, // 设置颜色
  side: DoubleSide,
})
const line = new Line(geometry, material)
scene.add(line)

// 动画循环
function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

c1 和 c2 各取 50%

js 复制代码
const c1 = new Color(0xff0000)
const c2 = new Color(0x0000ff)
const c = new Color()
c.lerpColors(c1, c2, 0.5)

完全取 c2

js 复制代码
const c1 = new Color(0xff0000)
const c2 = new Color(0x0000ff)
const c = new Color()
c.lerpColors(c1, c2, 1)

等同于这样使用

js 复制代码
c1.clone().lerp(c2, percent)

总结:创建一个 c,用来接收混合结果。

lerp() 方法,把当前颜色自己混合到 c2

方法和之前一样。

js 复制代码
const c1 = new Color(0xff0000)
const c2 = new Color(0x0000ff)
c1.lerp(c2, 1)

Color颜色插值应用

js 复制代码
const geometry = new BufferGeometry()
const curve = new CatmullRomCurve3([
  new Vector3(-50, 20, 90),
  new Vector3(-10, 40, 40),
  new Vector3(0, 0, 0),
  new Vector3(60, -60, 0),
  new Vector3(70, 0, 80)
])
const pointsArr = curve.getSpacedPoints(100)
geometry.setFromPoints(pointsArr)

const pos = geometry.attributes.position
const count = pos.count
// 定义渐变颜色
const c1 = new Color(0x00ffff)
const c2 = new Color(0xffff00)
const colorsArr = []
for (let i = 0; i < count; i++) {
  const percent = i / count
  // 克隆颜色,并使用
  const c = c1.clone().lerp(c2, percent)
  colorsArr.push(c.r, c.g, c.b)
}
const colors = new Float32Array(colorsArr)
geometry.attributes.color = new BufferAttribute(colors, 3)

const material = new LineBasicMaterial({
  vertexColors: true,
  side: DoubleSide,
})
const line = new Line(geometry, material)

scene.add(line)

function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

render()

查看或设置gltf几何体顶点

js 复制代码
geometry.attributes.position = new BufferAttribute()
geometry.attributes.normal = new BufferAttribute()
geometry.attributes.color = new BufferAttribute()
geometry.attributes.uv = new BufferAttribute()
geometry.index = new BufferAttribute()

导入模型,获取顶点数据

js 复制代码
async function init() {
  scene = await loadGltf2('/gltf/地形.glb')
  // 获取网格模型
  const mesh = scene.children[0]
  // 顶点数据
  const geometry = mesh.geometry
  const att = geometry.attributes
  // 顶点位置数据
  const pos = att.position
  console.log(pos)
  // 几何体顶点索引属性 geometry.index
  console.log('index', geometry.index)
  // 顶点数量 BufferAttribute.count
  const count = pos.count //几何体顶点数量
  console.log('count', count)
  envar()
}

.getX()、.getY()和.getZ()

BufferAttribute对象具有.getX()、.getY()和.getZ()方法。

BufferAttribute共有顶点数量count,通过.getX(i)方法可以获取第 i + 1 个点的x分量,i 的范围就是[0,count-1]

js 复制代码
const pos = mesh.geometry.attributes.position
// 获取几何体第一个顶点的x坐标
const x = pos.getX(0)
console.log('x', x)

.setX()、.setY() 和 .setZ()

通过 .setY() 是设置顶点y坐标

js 复制代码
const pos = mesh.geometry.attributes.position
pos.setX(0,100)

案例

js 复制代码
import {
  Color,
  DirectionalLight,
  PerspectiveCamera,
  Scene,
  WebGLRenderer,
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { loadGltf2 } from '@/views/three3W/model.ts'

defineOptions({
  name: 'AboutView4'
})

let scene: Scene
let camera: PerspectiveCamera
let renderer: WebGLRenderer

async function init() {
  scene = await loadGltf2('/gltf/地形.glb')
  // 获取网格模型
  const mesh = scene.children[0]
  // 顶点位置数据
  const pos = mesh.geometry.attributes.position
  // 批量设置所有几何体顶点位置的y坐标
  for (let i = 0; i < pos.count; i++) {
    // 获取第i+1个顶点y坐标
    const y = pos.getY(i)
    // 设置第i+1个顶点y坐标为自身2倍
    pos.setY(i, y * 2)
  }
  envar()
  render()
}

function envar() {
  scene.background = new Color(0x444444)
  camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 10, 1000)
  camera.position.set(0, 0, 60)
  const light0 = new DirectionalLight(0xffffff, 3)
  light0.position.set(0, 200, 0)
  const lights1 = new DirectionalLight(0xffffff, 3)
  lights1.position.set(100, 200, 100)
  const lights2 = new DirectionalLight(0xffffff, 3)
  lights2.position.set(-100, -200, -100)
  scene.add(light0, lights1, lights2)
  renderer = new WebGLRenderer({ antialias: true })
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.setSize(window.innerWidth, window.innerHeight)
  document.body.appendChild(renderer.domElement)
  new OrbitControls(camera, renderer.domElement)
}

function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

init()

案例

导入地形 gltf 设置颜色

js 复制代码
import {
  BufferAttribute,
  Color,
  DirectionalLight, MeshLambertMaterial,
  PerspectiveCamera,
  Scene,
  WebGLRenderer,
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { loadGltf2 } from '@/views/three3W/model.ts'

let scene: Scene
let camera: PerspectiveCamera
let renderer: WebGLRenderer

async function init() {
  scene = await loadGltf2('/gltf/地形.glb')
  // 获取网格模型
  const mesh = scene.children[0]
  // 顶点位置数据
  const pos = mesh.geometry.attributes.position

  // 1. 批量设置所有几何体顶点位置的y坐标
  const yArr = [] //顶点所有y坐标,也就是地形高度
  for (let i = 0; i < pos.count; i++) {
    yArr.push(pos.getY(i))
  }
  yArr.sort()
  // 最小值
  const miny = yArr[0]
  // 最大值
  const maxy = yArr[yArr.length - 1]
  // 山脉整体高度
  const height = maxy - miny

  // 2. 计算每个顶点的颜色值
  const colorsArr = []
  const c1 = new Color(0x0000ff) //山谷颜色
  const c3 = new Color(0x00ff00) // 山腰颜色
  const c2 = new Color(0xff0000) //山顶颜色
  for (let i = 0; i < pos.count; i++) {
    // 当前高度和整体高度比值
    const percent = (pos.getY(i) - miny) / height

    // 颜色插值计算, 调整
    let c
    if (percent < 0.5) { // 0.5 作为颜色插值分界点
      c = c1.clone().lerp(c3, percent * 2)
    } else {
      c = c2.clone().lerp(c3, (percent - 0.5) * 2)
    }
    // const c = c1.clone().lerp(c2, percent) // 颜色插值计算。不调整
    colorsArr.push(c.r, c.g, c.b)
  }
  const colors = new Float32Array(colorsArr)
  // 设置几何体attributes属性的颜色color属性
  mesh.geometry.attributes.color = new BufferAttribute(colors, 3)

  // 3. 设置材质,使用顶点颜色渲染
  mesh.material = new MeshLambertMaterial({
    vertexColors: true
  })
  envar()
  render()
}

function envar() {
  scene.background = new Color(0x444444)
  camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 10, 1000)
  camera.position.set(0, 0, 60)
  const light0 = new DirectionalLight(0xffffff, 3)
  light0.position.set(0, 200, 0)
  const lights1 = new DirectionalLight(0xffffff, 3)
  lights1.position.set(100, 200, 100)
  const lights2 = new DirectionalLight(0xffffff, 3)
  lights2.position.set(-100, -200, -100)
  scene.add(light0, lights1, lights2)
  renderer = new WebGLRenderer({ antialias: true })
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.setSize(window.innerWidth, window.innerHeight)
  document.body.appendChild(renderer.domElement)
  new OrbitControls(camera, renderer.domElement)
}

function render() {
  requestAnimationFrame(render)
  renderer.render(scene, camera)
}

init()
相关推荐
Keepreal4961 天前
使用 Three.js 和 GSAP 动画库实现3D 名字抽奖
javascript·vue.js·three.js
Jedi Hongbin2 天前
Three.js NodeMaterial 节点材质系统文档
前端·javascript·three.js·nodematerial
入秋2 天前
Three.js后期处理实战:噪点 景深 以及色彩调整
前端·javascript·three.js
答案answer2 天前
你不知道的Three.js性能优化和使用小技巧
前端·性能优化·three.js
爱看书的小沐3 天前
【小沐学WebGIS】基于Three.JS绘制飞行轨迹Flight Tracker(Three.JS/ vue / react / WebGL)
javascript·vue·webgl·three.js·航班·航迹·飞行轨迹
小兔崽子去哪了6 天前
Three.js 学习记录
three.js
Demoncode_y6 天前
Vue3 + Three.js 实现 3D 汽车个性化定制及展示
前端·javascript·vue.js·3d·汽车·three.js
Mintopia11 天前
Cesium-kit 又发新玩意儿了:CameraControl 相机控制组件全解析
前端·three.js·cesium
Mintopia13 天前
🌍 cesium-kit —— 快速实现动态标点与交互的 Cesium 工具库
前端·three.js·cesium