阅读three.js 示例 -- css3d_orthographic.html

该示例主要是在正投影视觉下,使用 CSS3DRenderer 渲染基于CSS的3D对象,在3D场景中展示使用div绘制的几何图形。下面将会对该示例涉及到的一些知识点进行讲解。

一、 几个知识点

1. OrthographicCamera正投影机

在three.js 相机可以分为两种,OrthographicCamera正投影机 和 PerspectiveCamera 透视投影相机。在大多数项目中,我们都是使用投影相机,因为投影相机可以模拟人眼看物体是远小近大的规律。假如不需要远小近大,则可以采用正投影相机。

使用示例:

js 复制代码
// 构造函数格式
OrthographicCamera( left, right, top, bottom, near, far )

参数说明:

参数(属性) 含义
left 渲染空间的左边界
right 渲染空间的右边界
top 渲染空间的上边界
bottom 渲染空间的下边界
near near 属性表示的是从距离相机多远的位置开始渲染,一般情况下会设置一个很小的值。默认值 0.1
far far 属性表示的是距离相机多远的位置截止渲染。如果设置的值偏小,会有部分场景看不到。默认值 2000

2. 欧拉Euler

构造函数:Euler(x,y,z,order)

参数xyz分别表示绕xyz轴旋转的角度值,角度单位是弧度。参数order表示旋转顺序,默认值XYZ,也可以设置为YXZYZX等值

javascript 复制代码
// 创建一个欧拉对象,表示绕着xyz轴分别旋转45度,0度,90度
var Euler = new THREE.Euler( Math.PI/4,0, Math.PI/2);

另一种形式设置:

ini 复制代码
var Euler = new THREE.Euler();
Euler.x = Math.PI/4;
Euler.y = Math.PI/2;
Euler.z = Math.PI/4;

注意: 模型的角度属性 .rotation 和 四元数属性 .quaternion 都是表示模型的角度状态,只是表达方式不同。 .rotation 属性值是欧拉对象 Euler, .quaternion 属性值是四元数对象 Quaternion

3. CSS3DRenderer

CSS3DRenderer 可以实现dom元素的3D效果,本质是把原来三维场景中的视图矩阵、模型矩阵、投影矩阵的变换,通过 css 中的 translateZ、matrix3d、perspective等属性模拟出来并作用在DOOM上。

示例中通过createPlane()生成的div对象,需要使用CSS3DRenderer才能显示出来。

4. lil-gui 库使用,方便对场景参数进行可视化调整。该库的具体使用,可查阅其官网:

lil-gui.georgealways.com/

二、 完整示例

js 复制代码
<!DOCTYPE html>
<html>
	<head>
		<title>three.js css3d - orthographic</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-color: #f0f0f0;
			}
			a {
				color: #f00;
			}
			#info {
				color: #000000;
			}
		</style>
	</head>
	<body>
		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> css3d - orthographic</div>

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

		<script type="module">

			import * as THREE from 'three';

			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
			import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

			let camera, scene, renderer;

			let scene2, renderer2;

			const frustumSize = 500;

			init();
			animate();

			function init() {

				const aspect = window.innerWidth / window.innerHeight;
				camera = new THREE.OrthographicCamera( frustumSize * aspect / - 2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / - 2, 1, 1000 );

				camera.position.set( - 200, 200, 200 );

				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0xf0f0f0 );

				scene2 = new THREE.Scene();

				// MeshBasicMaterial: 基础网格材质
				// wireframeLinewidth: 控制线框宽度,默认值为1
				const material = new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true, wireframeLinewidth: 1, side: THREE.DoubleSide } );

				// left
				// Euler: 欧拉角
				createPlane(
					100, 100,
					'chocolate',
					new THREE.Vector3( - 50, 0, 0 ),
					new THREE.Euler( 0, - 90 * THREE.MathUtils.DEG2RAD, 0 )
				);
				// right
				createPlane(
					100, 100,
					'saddlebrown',
					new THREE.Vector3( 0, 0, 50 ),
					new THREE.Euler( 0, 0, 0 )
				);
				// top
				// 这段代码创建了一个尺寸为 100x100 的黄绿色平面,位置位于 (0, 50, 0),并绕 X 轴逆时针旋转 90 度。
				createPlane(
					100, 100,
					'yellowgreen',
					new THREE.Vector3( 0, 50, 0 ),
					new THREE.Euler( - 90 * THREE.MathUtils.DEG2RAD, 0, 0 )
				);
				// bottom
				createPlane(
					300, 300,
					'seagreen',
					new THREE.Vector3( 0, - 50, 0 ),
					new THREE.Euler( - 90 * THREE.MathUtils.DEG2RAD, 0, 0 )
				);

				//

				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				document.body.appendChild( renderer.domElement );

				renderer2 = new CSS3DRenderer();
				renderer2.setSize( window.innerWidth, window.innerHeight );
				renderer2.domElement.style.position = 'absolute';
				renderer2.domElement.style.top = 0;
				document.body.appendChild( renderer2.domElement );

				const controls = new OrbitControls( camera, renderer2.domElement );
				controls.minZoom = 0.5;
				controls.maxZoom = 2;

				function createPlane( width, height, cssColor, pos, rot ) {

					const element = document.createElement( 'div' );
					element.style.width = width + 'px';
					element.style.height = height + 'px';
					element.style.opacity = 0.75;
					element.style.background = cssColor;

					const object = new CSS3DObject( element );
					object.position.copy( pos );
					object.rotation.copy( rot );
					scene2.add( object );

					// PlaneGeometry: 平面缓冲集合体
					const geometry = new THREE.PlaneGeometry( width, height );
					const mesh = new THREE.Mesh( geometry, material );
					mesh.position.copy( object.position );
					mesh.rotation.copy( object.rotation );
					scene.add( mesh );

				}

				window.addEventListener( 'resize', onWindowResize );
				createPanel();

			}

			function onWindowResize() {

				const aspect = window.innerWidth / window.innerHeight;

				camera.left = - frustumSize * aspect / 2;
				camera.right = frustumSize * aspect / 2;
				camera.top = frustumSize / 2;
				camera.bottom = - frustumSize / 2;

				camera.updateProjectionMatrix();

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

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

			}

			function animate() {

				requestAnimationFrame( animate );

				renderer.render( scene, camera );
				renderer2.render( scene2, camera );

			}

			function createPanel() {

				const panel = new GUI();
				const folder1 = panel.addFolder( 'camera setViewOffset' ).close();

				const settings = {
					'setViewOffset'() {

						folder1.children[ 1 ].enable().setValue( window.innerWidth );
						folder1.children[ 2 ].enable().setValue( window.innerHeight );
						folder1.children[ 3 ].enable().setValue( 0 );
						folder1.children[ 4 ].enable().setValue( 0 );
						folder1.children[ 5 ].enable().setValue( window.innerWidth );
						folder1.children[ 6 ].enable().setValue( window.innerHeight );

					},
					'fullWidth': 0,
					'fullHeight': 0,
					'offsetX': 0,
					'offsetY': 0,
					'width': 0,
					'height': 0,
					'clearViewOffset'() {

						folder1.children[ 1 ].setValue( 0 ).disable();
						folder1.children[ 2 ].setValue( 0 ).disable();
						folder1.children[ 3 ].setValue( 0 ).disable();
						folder1.children[ 4 ].setValue( 0 ).disable();
						folder1.children[ 5 ].setValue( 0 ).disable();
						folder1.children[ 6 ].setValue( 0 ).disable();
						camera.clearViewOffset();

					}
				};

				folder1.add( settings, 'setViewOffset' );
				folder1.add( settings, 'fullWidth', window.screen.width / 4, window.screen.width * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { fullWidth: val } ) ).disable();
				folder1.add( settings, 'fullHeight', window.screen.height / 4, window.screen.height * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { fullHeight: val } ) ).disable();
				folder1.add( settings, 'offsetX', 0, 256, 1 ).onChange( ( val ) => updateCameraViewOffset( { x: val } ) ).disable();
				folder1.add( settings, 'offsetY', 0, 256, 1 ).onChange( ( val ) => updateCameraViewOffset( { y: val } ) ).disable();
				folder1.add( settings, 'width', window.screen.width / 4, window.screen.width * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { width: val } ) ).disable();
				folder1.add( settings, 'height', window.screen.height / 4, window.screen.height * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { height: val } ) ).disable();
				folder1.add( settings, 'clearViewOffset' );

			}

			function updateCameraViewOffset( { fullWidth, fullHeight, x, y, width, height } ) {

				if ( ! camera.view ) {

					camera.setViewOffset( fullWidth || window.innerWidth, fullHeight || window.innerHeight, x || 0, y || 0, width || window.innerWidth, height || window.innerHeight );

				} else {

					camera.setViewOffset( fullWidth || camera.view.fullWidth, fullHeight || camera.view.fullHeight, x || camera.view.offsetX, y || camera.view.offsetY, width || camera.view.width, height || camera.view.height );

				}

			}

		</script>
	</body>
</html>
相关推荐
xixixin_1 小时前
【Vite】前端开发服务器的配置
服务器·前端·网络
.生产的驴1 小时前
Vue3 加快页面加载速度 使用CDN外部库的加载 提升页面打开速度 服务器分发
运维·服务器·前端·vue.js·分布式·前端框架·vue
史迪仔01121 小时前
Python生成器:高效处理大数据的秘密武器
前端·数据库·python
蓝婷儿2 小时前
前端面试每日三题 - Day 34
前端·面试·职场和发展
CopyLower2 小时前
苹果计划将AI搜索集成至Safari:谷歌搜索下降引发的市场变革
前端·人工智能·safari
我是Superman丶4 小时前
【技巧】前端VUE用中文方法名调用没效果的问题
前端·javascript·vue.js
斯~内克4 小时前
Vue 3 中 watch 的使用与深入理解
前端·javascript·vue.js
蜡笔小柯南5 小时前
解决:npm install报错,reason: certificate has expired
前端·npm·node.js
lqj_本人6 小时前
鸿蒙OS&UniApp制作多选框与单选框组件#三方框架 #Uniapp
前端·javascript·uni-app
@PHARAOH7 小时前
WHAT - 前端开发流程 SOP(标准操作流程)参考
前端·领导力