Threejs-管道漫游

管道漫游

image

管道外观:
image

通过一个轨迹线生成一个管道几何体,然后相机沿着该轨迹线移动,注意相机的方向要沿着轨迹线的切线方向,这样会形成一个管道漫游的效果。
管道几何体TubeGeometry、纹理贴图
相机对象Camera.position属性和.lookAt()方法

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>管道漫游</title>
 <style>
  body{
      overflow: hidden;
      margin: 0px;
  }
  #canvas{
    width: 1000px;
    height: 800px;
    margin:50px auto;
  }
</style>
</head>
<body>

  <div id="canvas"></div>
 
 <script type="importmap">
  {
    "imports": {
      "three": "../three.js-r148/build/three.module.js",
      "three/addons/": "../three.js-r148/examples/jsm/",
      "@tweenjs/tween.js": "../tween/tween.esm.js"
    }
  }
</script>
<script src="./index.js" type="module">  </script>
</body>
</html>

创建管道模型


// 三维样条曲线
const path = new THREE.CatmullRomCurve3([
 new THREE.Vector3(0, 20, 90),
 new THREE.Vector3(200, 20, 90),
 new THREE.Vector3(200, -100, 90),
 new THREE.Vector3(0, -100, 90),
 new THREE.Vector3(90, -40, 60),
 new THREE.Vector3(120, 30, 30),
]);
// 样条曲线path作为TubeGeometry参数生成管道
const geometry = new THREE.TubeGeometry(path, 200, 5, 30);
const texLoader = new THREE.TextureLoader(); 
//纹理贴图
const texture = texLoader.load('../imgs/wall.jpg');
//UV坐标U方向阵列模式
texture.wrapS = THREE.RepeatWrapping;
//纹理沿着管道方向阵列(UV坐标U方向)
texture.repeat.x = 10;
const material = new THREE.MeshLambertMaterial({
 map:texture,
 side: THREE.DoubleSide, //双面显示看到管道内壁
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh)

获得运动轨迹上的顶点

// 从曲线上等间距获取一定数量点坐标
const pointsArr = path.getSpacedPoints(1000);

相机放在管道内轨迹线上

相机放在管道内轨迹线上任意一个位置,并控制相机视线和曲线切线重合。

曲线当前点pointsArr[i]
曲线下一个点pointsArr[i + 1]
曲线上当前点pointsArr[i]和下一个点pointsArr[i+1]近似模拟当前点曲线切线,两点间距越小,模拟精度越高。
.lookAt()设置相机观察点为当前点pointsArr[i]的下一个点pointsArr[i + 1],使相机视线和曲线上当前点切线重合。

// 从曲线上等间距获取一定数量点坐标
const pointsArr = path.getSpacedPoints(1000);
const i = 100;
// 相机位置:曲线上当前点pointsArr[i]
camera.position.copy(pointsArr[i]);
// 相机观察目标:当前点的下一个点pointsArr[i + 1]
camera.lookAt(pointsArr[i + 1]);

相机控件.target.lookAt()参数一致

相机控件.target和.lookAt()参数同步,这样你可以旋转相机观察管道内部

const controls = new OrbitControls(camera, renderer.domElement);
controls.target.copy(pointsArr[i+1]);
controls.update();

相机动画

const pointsArr = path.getSpacedPoints(1000);
// 渲染循环
let i = 0; //在渲染循环中累加变化
function render() {
    if (i < pointsArr.length - 1) {
        // 相机位置设置在当前点位置
        camera.position.copy(pointsArr[i]);
        camera.lookAt(pointsArr[i + 1]);
        i += 1; //调节速度
    } else {
        i = 0
    }
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

完整代码

创建文件index.js

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';


//场景
const scene = new THREE.Scene();

//辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(300);
scene.add(axesHelper);


//环境光
const ambient = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambient);

//渲染器和相机
const width = 1000;
const height = 800;
const camera = new THREE.PerspectiveCamera(60, width / height, 1, 3000);
camera.position.set(0, 300, 600);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer({
    logarithmicDepthBuffer: true,// 设置对数深度缓冲区,优化深度冲突问题
    antialias:true//执行抗锯齿
});
renderer.setSize(width, height);
const dom  = document.getElementById('canvas')
dom.appendChild(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);


// 三维样条曲线
const path = new THREE.CatmullRomCurve3([
 new THREE.Vector3(0, 20, 90),
 new THREE.Vector3(200, 20, 90),
 new THREE.Vector3(200, -100, 90),
 new THREE.Vector3(0, -100, 90),
 new THREE.Vector3(90, -40, 60),
 new THREE.Vector3(120, 30, 30),
]);
// 样条曲线path作为TubeGeometry参数生成管道
const geometry = new THREE.TubeGeometry(path, 200, 5, 30);
const texLoader = new THREE.TextureLoader(); 
//纹理贴图
const texture = texLoader.load('../imgs/wall.jpg');
//UV坐标U方向阵列模式
texture.wrapS = THREE.RepeatWrapping;
//纹理沿着管道方向阵列(UV坐标U方向)
texture.repeat.x = 10;
const material = new THREE.MeshLambertMaterial({
 map:texture,
 side: THREE.DoubleSide, //双面显示看到管道内壁
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh)

// 从曲线上等间距获取一定数量点坐标
const pointsArr = path.getSpacedPoints(1000);
// 渲染循环
let i =0
function render() {
   if (i < pointsArr.length - 1) {
    // 相机位置设置在当前点位置
    camera.position.copy(pointsArr[i]);
    // 曲线上当前点pointsArr[i]和下一个点pointsArr[i+1]近似模拟当前点曲线切线
    // 设置相机观察点为当前点的下一个点,相机视线和当前点曲线切线重合
    camera.lookAt(pointsArr[i + 1]);
    i += 1; //调节速度
  } else {
    i = 0
  }
 renderer.render(scene, camera);
 requestAnimationFrame(render);
}
render();

// 画布跟随窗口变化
window.onresize = function () {
 renderer.setSize(window.innerWidth, window.innerHeight);
 camera.aspect = window.innerWidth / window.innerHeight;
 camera.updateProjectionMatrix();
};



  转载请注明: 小浩之随笔 Threejs-管道漫游

 上一篇
Threejs-关键帧动画 Threejs-关键帧动画
什么是关键帧动画 关键帧动画,你可以理解为在一个时间轴上,选择几个关键的时间点,然后分别定义这几个时间点对应物体状态(比如位置、姿态、颜色等),然后基于这几个关键的时间点,采用特定的插值方法计算物体的状态数据,从而达到比较流畅的动画效果。
2023-07-18
下一篇 
Threejs-拓扑图飞线 Threejs-拓扑图飞线
拓扑图飞线 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <
2023-07-16
  目录