FlyControls 是 THREE.js 中用于实现飞行控制的类,它用于控制摄像机在三维空间中的飞行。

在这里插入图片描述

demo演示地址

FlyControls 是 THREE.js 中用于实现飞行控制的类,它用于控制摄像机在三维空间中的飞行。

入参:

  1. object:摄像机对象,即要控制的摄像机。
  2. domElement:用于接收用户输入事件的 HTML 元素,通常是渲染器的 DOM 元素。

出参:

FlyControls 类本身没有直接返回出参,但通过修改传入的摄像机对象的位置和方向,从而影响场景中的摄像机视角。

使用示例:

// 初始化摄像机、控制器、场景和渲染器
function init() {
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    controls = new FlyControls(camera, renderer.domElement);
    scene = new THREE.Scene();
    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    // 设置摄像机初始位置
    camera.position.set(0, 0, 5);

    // 添加一个立方体到场景中
    const geometry = new THREE.BoxGeometry();
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    // 渲染场景
    animate();
}

// 动画循环
function animate() {
    requestAnimationFrame(animate);

    // 更新飞行控制器
    controls.update();

    // 渲染场景
    renderer.render(scene, camera);
}

在这个示例中,我们创建了一个 FlyControls 实例,并将摄像机和渲染器的 DOM 元素传递给它。然后在动画循环中,我们调用 controls.update() 来更新控制器状态,以响应用户的输入事件,并通过 renderer.render() 渲染场景。



import * as THREE from 'three'; // 导入主 THREE.js 库

import Stats from 'three/addons/libs/stats.module.js'; // 导入性能监控模块 Stats

import { FlyControls } from 'three/addons/controls/FlyControls.js'; // 导入飞行控制器 FlyControls
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; // 导入后期处理特效组件 EffectComposer
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; // 导入渲染通道 RenderPass
import { FilmPass } from 'three/addons/postprocessing/FilmPass.js'; // 导入胶片特效 FilmPass
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js'; // 导入输出通道 OutputPass

// 行星及其环境属性的常量定义
const radius = 6371; // 地球半径
const tilt = 0.41; // 行星倾斜角度
const rotationSpeed = 0.02; // 行星旋转速度

const cloudsScale = 1.005; // 云层纹理比例
const moonScale = 0.23; // 月球比例

const MARGIN = 0; // 场景边距
let SCREEN_HEIGHT = window.innerHeight - MARGIN * 2; // 计算屏幕高度
let SCREEN_WIDTH = window.innerWidth; // 计算屏幕宽度

let camera, controls, scene, renderer, stats; // 摄像机、控制器、场景、渲染器和性能监控模块的全局变量声明
let geometry, meshPlanet, meshClouds, meshMoon; // 几何体和代表行星、云层和月球的网格变量声明
let dirLight; // 光源的声明

let composer; // 后期处理特效的 Composer 声明

const textureLoader = new THREE.TextureLoader(); // 创建纹理加载器实例

let d, dPlanet, dMoon; // 距离计算的变量声明
const dMoonVec = new THREE.Vector3(); // 月球距离计算的向量

const clock = new THREE.Clock(); // 创建时钟用于计时

创建透视摄像机,初始化场景

function init() {
    // 创建透视摄像机
    camera = new THREE.PerspectiveCamera(25, SCREEN_WIDTH / SCREEN_HEIGHT, 50, 1e7);
    camera.position.z = radius * 5; // 设置摄像机位置

    scene = new THREE.Scene(); // 创建场景
    scene.fog = new THREE.FogExp2(0x000000, 0.00000025); // 添加雾效

    dirLight = new THREE.DirectionalLight(0xffffff, 3); // 创建定向光源
    dirLight.position.set(-1, 0, 1).normalize(); // 设置光源位置
    scene.add(dirLight); // 将光源添加到场景中

    // 创建具有法线贴图的材质
    const materialNormalMap = new THREE.MeshPhongMaterial({
        specular: 0x7c7c7c, // 设置镜面高光颜色
        shininess: 15, // 设置光泽度
        map: textureLoader.load('textures/planets/earth_atmos_2048.jpg'), // 设置漫反射贴图
        specularMap: textureLoader.load('textures/planets/earth_specular_2048.jpg'), // 设置镜面高光贴图
        normalMap: textureLoader.load('textures/planets/earth_normal_2048.jpg'), // 设置法线贴图
        normalScale: new THREE.Vector2(0.85, -0.85) // 设置法线贴图缩放
    });
    materialNormalMap.map.colorSpace = THREE.SRGBColorSpace; // 设置贴图颜色空间

    // 创建行星
    geometry = new THREE.SphereGeometry(radius, 100, 50);
    meshPlanet = new THREE.Mesh(geometry, materialNormalMap);
    meshPlanet.rotation.y = 0;
    meshPlanet.rotation.z = tilt;
    scene.add(meshPlanet);

    // 创建云层
    const materialClouds = new THREE.MeshLambertMaterial({
        map: textureLoader.load('textures/planets/earth_clouds_1024.png'), // 设置云层贴图
        transparent: true // 开启透明
    });
    materialClouds.map.colorSpace = THREE.SRGBColorSpace; // 设置贴图颜色空间

    meshClouds = new THREE.Mesh(geometry, materialClouds);
    meshClouds.scale.set(cloudsScale, cloudsScale, cloudsScale);
    meshClouds.rotation.z = tilt;
    scene.add(meshClouds);

    // 创建月球
    const materialMoon = new THREE.MeshPhongMaterial({
        map: textureLoader.load('textures/planets/moon_1024.jpg') // 设置月球贴图
    });
    materialMoon.map.colorSpace = THREE.SRGBColorSpace; // 设置贴图颜色空间

    meshMoon = new THREE.Mesh(geometry, materialMoon);
    meshMoon.position.set(radius * 5, 0, 0);
    meshMoon.scale.set(moonScale, moonScale, moonScale);
    scene.add(meshMoon);

    // 创建星星
    const r = radius,
        starsGeometry = [new THREE.BufferGeometry(), new THREE.BufferGeometry()];

    const vertices1 = [];
    const vertices2 = [];

    const vertex = new THREE.Vector3();

    for (let i = 0; i < 250; i++) {
        vertex.x = Math.random() * 2 - 1;
        vertex.y = Math.random() * 2 - 1;
        vertex.z = Math.random() * 2 - 1;
        vertex.multiplyScalar(r);

        vertices1.push(vertex.x, vertex.y, vertex.z);
    }

    for (let i = 0; i < 1500; i++) {
        vertex.x = Math.random() * 2 - 1;
        vertex.y = Math.random() * 2 - 1;
        vertex.z = Math.random() * 2 - 1;
        vertex.multiplyScalar(r);

        vertices2.push(vertex.x, vertex.y, vertex.z);
    }

    starsGeometry[0].setAttribute('position', new THREE.Float32BufferAttribute(vertices1, 3));
    starsGeometry[1].setAttribute('position', new THREE.Float32BufferAttribute(vertices2, 3));

    const starsMaterials = [
        new THREE.PointsMaterial({ color: 0x9c9c9c, size: 2, sizeAttenuation: false }),
        new THREE.PointsMaterial({ color: 0x9c9c9c, size: 1, sizeAttenuation: false }),
        new THREE.PointsMaterial({ color: 0x7c7c7c, size: 2, sizeAttenuation: false }),
        new THREE.PointsMaterial({ color: 0x838383, size: 1, sizeAttenuation: false }),
        new THREE.PointsMaterial({ color: 0x5a5a5a, size: 2, sizeAttenuation: false }),
        new THREE.PointsMaterial({ color: 0x5a5a5a, size: 1, sizeAttenuation: false })
    ];

    for (let i = 10; i < 30; i++) {
        const stars = new THREE.Points(starsGeometry[i % 2], starsMaterials[i % 6]);

        stars.rotation.x = Math.random() * 6;
        stars.rotation.y = Math.random() * 6;
        stars.rotation.z = Math.random() * 6;
        stars.scale.setScalar(i * 10);

        stars.matrixAutoUpdate = false;
        stars.updateMatrix();

        scene.add(stars);
    }

    // 创建 WebGL 渲染器
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    document.body.appendChild(renderer.domElement);

    // 创建飞行控制器
    controls = new FlyControls(camera, renderer.domElement);
    controls.movementSpeed = 1000;
    controls.domElement = renderer.domElement;
    controls.rollSpeed = Math.PI / 24;
    controls.autoForward = false;
    controls.dragToLook = false;

    // 创建性能监控模块
    stats = new Stats();
    document.body.appendChild(stats.dom);

    window.addEventListener('resize', onWindowResize); // 添加窗口调整事件监听器

    // 添加后期处理特效
    const renderModel = new RenderPass(scene, camera);
    const effectFilm = new FilmPass(0.35);
    const outputPass = new OutputPass();

    composer = new EffectComposer(renderer);

    composer.addPass(renderModel);
    composer.addPass(effectFilm);
    composer.addPass(outputPass);
}

更新屏幕高度和宽度

function onWindowResize() {
    // 更新屏幕高度和宽度
    SCREEN_HEIGHT = window.innerHeight;
    SCREEN_WIDTH = window.innerWidth;

    // 更新摄像机的纵横比并更新投影矩阵
    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
    camera.updateProjectionMatrix();

    // 更新渲染器和后期处理特效组件的尺寸
    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    composer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
}

function animate() {
    // 请求下一帧动画
    requestAnimationFrame(animate);

    // 渲染场景并更新性能监控模块
    render();
    stats.update();
}

旋转行星和云层


function render() {
    // 旋转行星和云层

    const delta = clock.getDelta(); // 获取时间间隔

    meshPlanet.rotation.y += rotationSpeed * delta; // 根据时间间隔旋转行星
    meshClouds.rotation.y += 1.25 * rotationSpeed * delta; // 根据时间间隔旋转云层

    // 当接近表面时减慢速度
    dPlanet = camera.position.length(); // 计算摄像机到行星的距离

    dMoonVec.subVectors(camera.position, meshMoon.position); // 计算摄像机到月球的向量距离
    dMoon = dMoonVec.length(); // 计算摄像机到月球的距离

    if (dMoon < dPlanet) {
        d = (dMoon - radius * moonScale * 1.01); // 如果接近月球,则减速
    } else {
        d = (dPlanet - radius * 1.01); // 如果接近行星,则减速
    }

    controls.movementSpeed = 0.33 * d; // 根据距离更新控制器的移动速度
    controls.update(delta); // 更新控制器的状态

    composer.render(delta); // 渲染场景并应用后期处理效果
}

完整源码

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - fly controls - earth</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="main.css">
		<style>
			body {
				background:#000;
				color: #eee;
			}

			a {
				color: #0080ff;
			}

			b {
				color: orange
			}
		</style>
	</head>

	<body>

		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - earth [fly controls]<br/>
		<b>WASD</b> move, <b>R|F</b> up | down, <b>Q|E</b> roll, <b>up|down</b> pitch, <b>left|right</b> yaw
		</div>

		<script type="importmap">
			{
				"imports": {
					"three": "../build/three.module.js",
					"three/addons/": "./jsm/"
				}
			}
		</script>

		<script type="module">

			import * as THREE from 'three';

			import Stats from 'three/addons/libs/stats.module.js';

			import { FlyControls } from 'three/addons/controls/FlyControls.js';
			import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
			import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
			import { FilmPass } from 'three/addons/postprocessing/FilmPass.js';
			import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';

			const radius = 6371;
			const tilt = 0.41;
			const rotationSpeed = 0.02;

			const cloudsScale = 1.005;
			const moonScale = 0.23;

			const MARGIN = 0;
			let SCREEN_HEIGHT = window.innerHeight - MARGIN * 2;
			let SCREEN_WIDTH = window.innerWidth;

			let camera, controls, scene, renderer, stats;
			let geometry, meshPlanet, meshClouds, meshMoon;
			let dirLight;

			let composer;

			const textureLoader = new THREE.TextureLoader();

			let d, dPlanet, dMoon;
			const dMoonVec = new THREE.Vector3();

			const clock = new THREE.Clock();

			init();
			animate();

			function init() {

				camera = new THREE.PerspectiveCamera( 25, SCREEN_WIDTH / SCREEN_HEIGHT, 50, 1e7 );
				camera.position.z = radius * 5;

				scene = new THREE.Scene();
				scene.fog = new THREE.FogExp2( 0x000000, 0.00000025 );

				dirLight = new THREE.DirectionalLight( 0xffffff, 3 );
				dirLight.position.set( - 1, 0, 1 ).normalize();
				scene.add( dirLight );

				const materialNormalMap = new THREE.MeshPhongMaterial( {

					specular: 0x7c7c7c,
					shininess: 15,
					map: textureLoader.load( 'textures/planets/earth_atmos_2048.jpg' ),
					specularMap: textureLoader.load( 'textures/planets/earth_specular_2048.jpg' ),
					normalMap: textureLoader.load( 'textures/planets/earth_normal_2048.jpg' ),

					// y scale is negated to compensate for normal map handedness.
					normalScale: new THREE.Vector2( 0.85, - 0.85 )

				} );
				materialNormalMap.map.colorSpace = THREE.SRGBColorSpace;

				// planet

				geometry = new THREE.SphereGeometry( radius, 100, 50 );

				meshPlanet = new THREE.Mesh( geometry, materialNormalMap );
				meshPlanet.rotation.y = 0;
				meshPlanet.rotation.z = tilt;
				scene.add( meshPlanet );

				// clouds

				const materialClouds = new THREE.MeshLambertMaterial( {

					map: textureLoader.load( 'textures/planets/earth_clouds_1024.png' ),
					transparent: true

				} );
				materialClouds.map.colorSpace = THREE.SRGBColorSpace;

				meshClouds = new THREE.Mesh( geometry, materialClouds );
				meshClouds.scale.set( cloudsScale, cloudsScale, cloudsScale );
				meshClouds.rotation.z = tilt;
				scene.add( meshClouds );

				// moon

				const materialMoon = new THREE.MeshPhongMaterial( {

					map: textureLoader.load( 'textures/planets/moon_1024.jpg' )

				} );
				materialMoon.map.colorSpace = THREE.SRGBColorSpace;

				meshMoon = new THREE.Mesh( geometry, materialMoon );
				meshMoon.position.set( radius * 5, 0, 0 );
				meshMoon.scale.set( moonScale, moonScale, moonScale );
				scene.add( meshMoon );

				// stars

				const r = radius, starsGeometry = [ new THREE.BufferGeometry(), new THREE.BufferGeometry() ];

				const vertices1 = [];
				const vertices2 = [];

				const vertex = new THREE.Vector3();

				for ( let i = 0; i < 250; i ++ ) {

					vertex.x = Math.random() * 2 - 1;
					vertex.y = Math.random() * 2 - 1;
					vertex.z = Math.random() * 2 - 1;
					vertex.multiplyScalar( r );

					vertices1.push( vertex.x, vertex.y, vertex.z );

				}

				for ( let i = 0; i < 1500; i ++ ) {

					vertex.x = Math.random() * 2 - 1;
					vertex.y = Math.random() * 2 - 1;
					vertex.z = Math.random() * 2 - 1;
					vertex.multiplyScalar( r );

					vertices2.push( vertex.x, vertex.y, vertex.z );

				}

				starsGeometry[ 0 ].setAttribute( 'position', new THREE.Float32BufferAttribute( vertices1, 3 ) );
				starsGeometry[ 1 ].setAttribute( 'position', new THREE.Float32BufferAttribute( vertices2, 3 ) );

				const starsMaterials = [
					new THREE.PointsMaterial( { color: 0x9c9c9c, size: 2, sizeAttenuation: false } ),
					new THREE.PointsMaterial( { color: 0x9c9c9c, size: 1, sizeAttenuation: false } ),
					new THREE.PointsMaterial( { color: 0x7c7c7c, size: 2, sizeAttenuation: false } ),
					new THREE.PointsMaterial( { color: 0x838383, size: 1, sizeAttenuation: false } ),
					new THREE.PointsMaterial( { color: 0x5a5a5a, size: 2, sizeAttenuation: false } ),
					new THREE.PointsMaterial( { color: 0x5a5a5a, size: 1, sizeAttenuation: false } )
				];

				for ( let i = 10; i < 30; i ++ ) {

					const stars = new THREE.Points( starsGeometry[ i % 2 ], starsMaterials[ i % 6 ] );

					stars.rotation.x = Math.random() * 6;
					stars.rotation.y = Math.random() * 6;
					stars.rotation.z = Math.random() * 6;
					stars.scale.setScalar( i * 10 );

					stars.matrixAutoUpdate = false;
					stars.updateMatrix();

					scene.add( stars );

				}

				renderer = new THREE.WebGLRenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
				document.body.appendChild( renderer.domElement );

				//

				controls = new FlyControls( camera, renderer.domElement );

				controls.movementSpeed = 1000;
				controls.domElement = renderer.domElement;
				controls.rollSpeed = Math.PI / 24;
				controls.autoForward = false;
				controls.dragToLook = false;

				//

				stats = new Stats();
				document.body.appendChild( stats.dom );

				window.addEventListener( 'resize', onWindowResize );

				// postprocessing

				const renderModel = new RenderPass( scene, camera );
				const effectFilm = new FilmPass( 0.35 );
				const outputPass = new OutputPass();

				composer = new EffectComposer( renderer );

				composer.addPass( renderModel );
				composer.addPass( effectFilm );
				composer.addPass( outputPass );

			}

			function onWindowResize() {

				SCREEN_HEIGHT = window.innerHeight;
				SCREEN_WIDTH = window.innerWidth;

				camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
				camera.updateProjectionMatrix();

				renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
				composer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );

			}

			function animate() {

				requestAnimationFrame( animate );

				render();
				stats.update();

			}

			function render() {

				// rotate the planet and clouds

				const delta = clock.getDelta();

				meshPlanet.rotation.y += rotationSpeed * delta;
				meshClouds.rotation.y += 1.25 * rotationSpeed * delta;

				// slow down as we approach the surface

				dPlanet = camera.position.length();

				dMoonVec.subVectors( camera.position, meshMoon.position );
				dMoon = dMoonVec.length();

				if ( dMoon < dPlanet ) {

					d = ( dMoon - radius * moonScale * 1.01 );

				} else {

					d = ( dPlanet - radius * 1.01 );

				}

				controls.movementSpeed = 0.33 * d;
				controls.update( delta );

				composer.render( delta );

			}

		</script>
	</body>
</html>

本内容来源于小豆包,想要更多内容请跳转小豆包 》

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-27 01:50:06       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-27 01:50:06       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-27 01:50:06       82 阅读
  4. Python语言-面向对象

    2024-03-27 01:50:06       91 阅读

热门阅读

  1. Web使用基础

    2024-03-27 01:50:06       43 阅读
  2. 【Vue】实现的底层原理

    2024-03-27 01:50:06       42 阅读
  3. Qt 事件

    Qt 事件

    2024-03-27 01:50:06      35 阅读
  4. 代码随想录刷题随记6-哈希表2,双指针

    2024-03-27 01:50:06       48 阅读
  5. Vue 与 React 的区别

    2024-03-27 01:50:06       42 阅读
  6. 搭建Kafka集群

    2024-03-27 01:50:06       44 阅读
  7. 简单讲讲spring事务的传播机制

    2024-03-27 01:50:06       41 阅读
  8. 一个好用的文档部署工具-VitePress

    2024-03-27 01:50:06       39 阅读
  9. ChatGPT 3.5和ChatGPT 4.0之间有哪些区别?

    2024-03-27 01:50:06       161 阅读
  10. C++类的定义

    2024-03-27 01:50:06       36 阅读