`ArcballControls` 是 Three.js 中的一个相机控制器,用于在 3D 场景中实现类似于鼠标拖拽的交互操作。它允许用户通过鼠标拖拽来旋转、平移和缩放相机,从而改变视角和观

ArcballControls 是 Three.js 中的一个相机控制器,用于在 3D 场景中实现类似于鼠标拖拽的交互操作。它允许用户通过鼠标拖拽来旋转、平移和缩放相机,从而改变视角和观察场景中的物体。ArcballControls 可以帮助用户更直观地与 Three.js 场景进行交互,提供了简单而有效的控制方式,使用户能够更好地浏览和查看 3D 场景中的内容。

ArcballControls 的构造函数通常接受三个参数:

  1. camera:THREE.Camera 对象,表示要控制的相机。
  2. domElement:HTML 元素,表示用于监听用户输入事件(如鼠标拖拽)的 DOM 元素。
  3. scene:THREE.Scene 对象,表示当前的场景。

出参:ArcballControls 实例,用于控制相机的交互操作。

// 定义相机类型数组和相机类型对象

const cameras = ['Orthographic', 'Perspective'];
const cameraType = { type: 'Perspective' };

// 初始化透视相机距离和正交相机距离

const perspectiveDistance = 2.5;
const orthographicDistance = 120;

// 声明变量

let camera, controls, scene, renderer, gui;
let folderOptions, folderAnimations;

// Arcball 相机控制器 GUI

const arcballGui = {
    gizmoVisible: true,

    // 设置 Arcball 控制器
    setArcballControls: function() {
        controls = new ArcballControls(camera, renderer.domElement, scene);
        controls.addEventListener('change', render);

        this.gizmoVisible = true;

        this.populateGui();
    },

    // 填充 GUI
    populateGui: function() {
        folderOptions.add(controls, 'enabled').name('Enable controls');
        folderOptions.add(controls, 'enableGrid').name('Enable Grid');
        folderOptions.add(controls, 'enableRotate').name('Enable rotate');
        folderOptions.add(controls, 'enablePan').name('Enable pan');
        folderOptions.add(controls, 'enableZoom').name('Enable zoom');
        folderOptions.add(controls, 'cursorZoom').name('Cursor zoom');
        folderOptions.add(controls, 'adjustNearFar').name('adjust near/far');
        folderOptions.add(controls, 'scaleFactor', 1.1, 10, 0.1).name('Scale factor');
        folderOptions.add(controls, 'minDistance', 0, 50, 0.5).name('Min distance');
        folderOptions.add(controls, 'maxDistance', 0, 50, 0.5).name('Max distance');
        folderOptions.add(controls, 'minZoom', 0, 50, 0.5).name('Min zoom');
        folderOptions.add(controls, 'maxZoom', 0, 50, 0.5).name('Max zoom');
        folderOptions.add(arcballGui, 'gizmoVisible').name('Show gizmos').onChange(function() {
            controls.setGizmosVisible(arcballGui.gizmoVisible);
        });
        folderOptions.add(controls, 'copyState').name('Copy state(ctrl+c)');
        folderOptions.add(controls, 'pasteState').name('Paste state(ctrl+v)');
        folderOptions.add(controls, 'reset').name('Reset');
        folderAnimations.add(controls, 'enableAnimations').name('Enable anim.');
        folderAnimations.add(controls, 'dampingFactor', 0, 100, 1).name('Damping');
        folderAnimations.add(controls, 'wMax', 0, 100, 1).name('Angular spd');
    }
};

// 初始化函数

init();

function init() {
    const container = document.createElement('div');
    document.body.appendChild(container);

    // 创建渲染器
    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.toneMapping = THREE.ReinhardToneMapping;
    renderer.toneMappingExposure = 3;
    renderer.domElement.style.background = 'linear-gradient( 180deg, rgba( 0,0,0,1 ) 0%, rgba( 128,128,255,1 ) 100% )';
    container.appendChild(renderer.domElement);

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

    // 创建透视相机并设置位置
    camera = makePerspectiveCamera();
    camera.position.set(0, 0, perspectiveDistance);

    // 加载模型并设置材质
    const material = new THREE.MeshStandardMaterial();
    new OBJLoader()
        .setPath('models/obj/cerberus/')
        .load('Cerberus.obj', function(group) {
            // 设置材质贴图
            const textureLoader = new THREE.TextureLoader().setPath('models/obj/cerberus/');
            material.roughness = 1;
            material.metalness = 1;
            const diffuseMap = textureLoader.load('Cerberus_A.jpg', render);
            diffuseMap.colorSpace = THREE.SRGBColorSpace;
            material.map = diffuseMap;
            material.metalnessMap = material.roughnessMap = textureLoader.load('Cerberus_RM.jpg', render);
            material.normalMap = textureLoader.load('Cerberus_N.jpg', render);
            material.map.wrapS = THREE.RepeatWrapping;
            material.roughnessMap.wrapS = THREE.RepeatWrapping;
            material.metalnessMap.wrapS = THREE.RepeatWrapping;
            material.normalMap.wrapS = THREE.RepeatWrapping;

            // 遍历模型的子对象并应用材质
            group.traverse(function(child) {
                if (child.isMesh) {
                    child.material = material;
                }
            });

            // 调整模型位置并添加到场景中
            group.rotation.y = Math.PI / 2;
            group.position.x += 0.25;
            scene.add(group);
            render();

            // 加载 HDR 环境贴图
            new RGBELoader()
                .setPath('textures/equirectangular/')
                .load('venice_sunset_1k.hdr', function(hdrEquirect) {
                    hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
                    scene.environment = hdrEquirect;
                    render();
                });

            // 监听键盘事件和窗口大小变化事件
            window.addEventListener('keydown', onKeyDown);
            window.addEventListener('resize', onWindowResize);

            // 创建 GUI
            gui = new GUI();
            gui.add(cameraType, 'type', cameras).name('Choose Camera').onChange(function() {
                setCamera(cameraType.type);
            });
            folderOptions = gui.addFolder('Arcball parameters');
            folderAnimations = folderOptions.addFolder('Animations');
            // 设置 Arcball 控制器和 GUI
            arcballGui.setArcballControls();
            render();
        });
}

// 创建正交相机

function makeOrthographicCamera() {
    const halfFovV = THREE.MathUtils.DEG2RAD * 45 * 0.5;
    const halfFovH = Math.atan((window.innerWidth / window.innerHeight) * Math.tan(halfFovV));
    const halfW = perspectiveDistance * Math.tan(halfFovH);
    const halfH = perspectiveDistance * Math.tan(halfFovV);
    const near = 0.01;
    const far = 2000;
    const newCamera = new THREE.OrthographicCamera(-halfW, halfW, halfH, -halfH, near, far);
    return newCamera;
}

// 创建透视相机

function makePerspectiveCamera() {
    const fov = 45;
    const aspect = window.innerWidth / window.innerHeight;
    const near = 0.01;
    const far = 2000;
    const newCamera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    return newCamera;
}

// 当窗口大小变化时调整相机参数和渲染器大小

function onWindowResize() {
    if (camera.type == 'OrthographicCamera') {
        const halfFovV = THREE.MathUtils.DEG2RAD * 45 * 0.5;
        const halfFovH = Math.atan((window.innerWidth / window.innerHeight) * Math.tan(halfFovV));
        const halfW = perspectiveDistance * Math.tan(halfFovH);
        const halfH = perspectiveDistance * Math.tan(halfFovV);
        camera.left = -halfW;
        camera.right = halfW;
        camera.top = halfH;
        camera.bottom = -halfH;
    } else if (camera.type == 'PerspectiveCamera') {
        camera.aspect = window.innerWidth / window.innerHeight;
    }
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
    render();
}

// 渲染函数

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

// 监听键盘按下事件

function onKeyDown(event) {
    if (event.key === 'c') {
        if (event.ctrlKey || event.metaKey) {
            controls.copyState();
        }
    } else if (event.key === 'v') {
        if (event.ctrlKey || event.metaKey) {
            controls.pasteState();
        }
    }
}

// 切换相机类型并更新相机和控制器

function setCamera(type) {
    if (type == 'Orthographic') {
        camera = makeOrthographicCamera();
        camera.position.set(0, 0, orthographicDistance);
    } else if (type == 'Perspective') {
        camera = makePerspectiveCamera();
        camera.position.set(0, 0, perspectiveDistance);
    }
    controls.setCamera(camera);
    render();
}

全部源码


<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - arcball controls</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">

	</head>

	<body>
		<div id="info">
			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - arcball controls<br/>
			<a href="http://www.polycount.com/forum/showthread.php?t=130641" target="_blank" rel="noopener">Cerberus(FFVII Gun) model</a> by Andrew Maximov.
		</div>

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

		<script type="module">
			import * as THREE from 'three';

			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

			import { ArcballControls } from 'three/addons/controls/ArcballControls.js';

			import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';

			const cameras = [ 'Orthographic', 'Perspective' ];
			const cameraType = { type: 'Perspective' };

			const perspectiveDistance = 2.5;
			const orthographicDistance = 120;
			let camera, controls, scene, renderer, gui;
			let folderOptions, folderAnimations;

			const arcballGui = {

				gizmoVisible: true,

				setArcballControls: function () {

					controls = new ArcballControls( camera, renderer.domElement, scene );
					controls.addEventListener( 'change', render );

					this.gizmoVisible = true;

					this.populateGui();

				},

				populateGui: function () {

					folderOptions.add( controls, 'enabled' ).name( 'Enable controls' );
					folderOptions.add( controls, 'enableGrid' ).name( 'Enable Grid' );
					folderOptions.add( controls, 'enableRotate' ).name( 'Enable rotate' );
					folderOptions.add( controls, 'enablePan' ).name( 'Enable pan' );
					folderOptions.add( controls, 'enableZoom' ).name( 'Enable zoom' );
					folderOptions.add( controls, 'cursorZoom' ).name( 'Cursor zoom' );
					folderOptions.add( controls, 'adjustNearFar' ).name( 'adjust near/far' );
					folderOptions.add( controls, 'scaleFactor', 1.1, 10, 0.1 ).name( 'Scale factor' );
					folderOptions.add( controls, 'minDistance', 0, 50, 0.5 ).name( 'Min distance' );
					folderOptions.add( controls, 'maxDistance', 0, 50, 0.5 ).name( 'Max distance' );
					folderOptions.add( controls, 'minZoom', 0, 50, 0.5 ).name( 'Min zoom' );
					folderOptions.add( controls, 'maxZoom', 0, 50, 0.5 ).name( 'Max zoom' );
					folderOptions.add( arcballGui, 'gizmoVisible' ).name( 'Show gizmos' ).onChange( function () {

						controls.setGizmosVisible( arcballGui.gizmoVisible );

					} );
					folderOptions.add( controls, 'copyState' ).name( 'Copy state(ctrl+c)' );
					folderOptions.add( controls, 'pasteState' ).name( 'Paste state(ctrl+v)' );
					folderOptions.add( controls, 'reset' ).name( 'Reset' );
					folderAnimations.add( controls, 'enableAnimations' ).name( 'Enable anim.' );
					folderAnimations.add( controls, 'dampingFactor', 0, 100, 1 ).name( 'Damping' );
					folderAnimations.add( controls, 'wMax', 0, 100, 1 ).name( 'Angular spd' );

				}

			};


			init();

			function init() {

				const container = document.createElement( 'div' );
				document.body.appendChild( container );

				renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.toneMapping = THREE.ReinhardToneMapping;
				renderer.toneMappingExposure = 3;
				renderer.domElement.style.background = 'linear-gradient( 180deg, rgba( 0,0,0,1 ) 0%, rgba( 128,128,255,1 ) 100% )';
				container.appendChild( renderer.domElement );

				//

				scene = new THREE.Scene();

				camera = makePerspectiveCamera();
				camera.position.set( 0, 0, perspectiveDistance );

				const material = new THREE.MeshStandardMaterial();

				new OBJLoader()
					.setPath( 'models/obj/cerberus/' )
					.load( 'Cerberus.obj', function ( group ) {

						const textureLoader = new THREE.TextureLoader().setPath( 'models/obj/cerberus/' );

						material.roughness = 1;
						material.metalness = 1;

						const diffuseMap = textureLoader.load( 'Cerberus_A.jpg', render );
						diffuseMap.colorSpace = THREE.SRGBColorSpace;
						material.map = diffuseMap;

						material.metalnessMap = material.roughnessMap = textureLoader.load( 'Cerberus_RM.jpg', render );
						material.normalMap = textureLoader.load( 'Cerberus_N.jpg', render );

						material.map.wrapS = THREE.RepeatWrapping;
						material.roughnessMap.wrapS = THREE.RepeatWrapping;
						material.metalnessMap.wrapS = THREE.RepeatWrapping;
						material.normalMap.wrapS = THREE.RepeatWrapping;


						group.traverse( function ( child ) {

							if ( child.isMesh ) {

								child.material = material;

							}

						} );

						group.rotation.y = Math.PI / 2;
						group.position.x += 0.25;
						scene.add( group );
						render();

						new RGBELoader()
							.setPath( 'textures/equirectangular/' )
							.load( 'venice_sunset_1k.hdr', function ( hdrEquirect ) {

								hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;

								scene.environment = hdrEquirect;

								render();

							} );


						window.addEventListener( 'keydown', onKeyDown );
						window.addEventListener( 'resize', onWindowResize );

						//

						gui = new GUI();
						gui.add( cameraType, 'type', cameras ).name( 'Choose Camera' ).onChange( function () {

							setCamera( cameraType.type );

						} );

						folderOptions = gui.addFolder( 'Arcball parameters' );
						folderAnimations = folderOptions.addFolder( 'Animations' );

						arcballGui.setArcballControls();

						render();

					} );

			}

			function makeOrthographicCamera() {

				const halfFovV = THREE.MathUtils.DEG2RAD * 45 * 0.5;
				const halfFovH = Math.atan( ( window.innerWidth / window.innerHeight ) * Math.tan( halfFovV ) );

				const halfW = perspectiveDistance * Math.tan( halfFovH );
				const halfH = perspectiveDistance * Math.tan( halfFovV );
				const near = 0.01;
				const far = 2000;
				const newCamera = new THREE.OrthographicCamera( - halfW, halfW, halfH, - halfH, near, far );
				return newCamera;

			}

			function makePerspectiveCamera() {

				const fov = 45;
				const aspect = window.innerWidth / window.innerHeight;
				const near = 0.01;
				const far = 2000;
				const newCamera = new THREE.PerspectiveCamera( fov, aspect, near, far );
				return newCamera;

			}


			function onWindowResize() {

				if ( camera.type == 'OrthographicCamera' ) {

					const halfFovV = THREE.MathUtils.DEG2RAD * 45 * 0.5;
					const halfFovH = Math.atan( ( window.innerWidth / window.innerHeight ) * Math.tan( halfFovV ) );

					const halfW = perspectiveDistance * Math.tan( halfFovH );
					const halfH = perspectiveDistance * Math.tan( halfFovV );
					camera.left = - halfW;
					camera.right = halfW;
					camera.top = halfH;
					camera.bottom = - halfH;

				} else if ( camera.type == 'PerspectiveCamera' ) {

					camera.aspect = window.innerWidth / window.innerHeight;

				}

				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

				render();

			}

			function render() {

				renderer.render( scene, camera );

			}

			function onKeyDown( event ) {

				if ( event.key === 'c' ) {

					if ( event.ctrlKey || event.metaKey ) {

						controls.copyState();

					}

				} else if ( event.key === 'v' ) {

					if ( event.ctrlKey || event.metaKey ) {

						controls.pasteState();

					}

				}

			}

			function setCamera( type ) {

				if ( type == 'Orthographic' ) {

					camera = makeOrthographicCamera();
					camera.position.set( 0, 0, orthographicDistance );


				} else if ( type == 'Perspective' ) {

					camera = makePerspectiveCamera();
					camera.position.set( 0, 0, perspectiveDistance );

				}

				controls.setCamera( camera );

				render();

			}

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


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

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我来为你演示如何使用three.js实现这个效果。 首先,我们需要创建一个四棱锥的模型。我们可以使用three.js的THREE.ConeGeometry来创建一个四棱锥。代码如下: ```javascript var geometry = new THREE.ConeGeometry( 5, 12, 4 ); var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.VertexColors } ); var cone = new THREE.Mesh( geometry, material ); scene.add( cone ); ``` 这段代码将创建一个半径为5,高度为12,有4个面的四棱锥。我们还指定了一个使用顶点颜色的基础材质。 接下来,在顶点着色器,我们需要将每个顶点的颜色传递到片段着色器。我们可以使用THREE.BufferGeometry来创建一个顶点颜色缓冲区,并将其传递给geometry。代码如下: ```javascript var geometry = new THREE.ConeGeometry( 5, 12, 4 ); var colors = []; colors.push( new THREE.Color( 1, 0, 0 ) ); // red colors.push( new THREE.Color( 0, 1, 0 ) ); // green colors.push( new THREE.Color( 0, 0, 1 ) ); // blue colors.push( new THREE.Color( 1, 1, 0 ) ); // yellow geometry.setAttribute( 'color', new THREE.BufferAttribute( new Float32Array( colors ), 3 ) ); var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.VertexColors } ); var cone = new THREE.Mesh( geometry, material ); scene.add( cone ); ``` 这段代码将为每个顶点指定一个颜色,并将其传递给geometry。我们将红色,绿色,蓝色和黄色分别指定给四个顶点。 最后,在片段着色器,我们可以使用插值函数来计算每个像素的颜色。我们可以使用varying变量来从顶点着色器传递颜色。代码如下: ```javascript var material = new THREE.ShaderMaterial( { vertexShader: ` varying vec3 vColor; void main() { vColor = color; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } `, fragmentShader: ` varying vec3 vColor; void main() { gl_FragColor = vec4( vColor, 1.0 ); } ` } ); var geometry = new THREE.ConeGeometry( 5, 12, 4 ); var colors = []; colors.push( new THREE.Color( 1, 0, 0 ) ); // red colors.push( new THREE.Color( 0, 1, 0 ) ); // green colors.push( new THREE.Color( 0, 0, 1 ) ); // blue colors.push( new THREE.Color( 1, 1, 0 ) ); // yellow geometry.setAttribute( 'color', new THREE.BufferAttribute( new Float32Array( colors ), 3 ) ); var cone = new THREE.Mesh( geometry, material ); scene.add( cone ); ``` 这段代码将创建一个使用顶点颜色的着色器材质。在顶点着色器,我们将颜色存储在varying变量,并将其传递到片段着色器。在片段着色器,我们将vColor作为像素颜色返回。 通过这些步骤,我们就可以使用three.js实现四棱锥各个面的颜色渐变效果了。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值