阅读three.js 官网示例 -- 立方体视频墙

499 阅读4分钟

该示例是在3D场景中使用html绘制了一个立方体,立方体前后左右四面都是挂载展示视频。该示例实现起来并不复杂,但通过学习该示例,我们能比较熟练地在3D场景中灵活运用dom元素。

一、示例介绍

image.png

二、实现讲解

1. 实现思路

立方体前后左右四面,实际上是四个div元素,通过使用CSS3DObject将这几个div元素渲染成3D对象,控制其position 和 rotation 属性,这样看起来就是立方体的四个面了。此外,每个面的div 元素里,使用iframe内嵌了youtebe的视频。

2. 视频墙方法封装

(1)使用原生document.createElement 方法,生成一个div元素
(2)生成一个iframe对象,并改变其src值。并在iframe设置在div层内
(3)使用 CSS3DObject 对div进行3D对象处理。这样就可以灵活控制其在3D场景中的position和 rotation
(4)Element( id, x, y, z, ry ) 接受的参数分别是:
id: 视频ID号
x: position 的 x 轴位置
y: position 的 y 轴位置
z: position 的 z 轴位置
ry: rotation 中绕y轴旋转角度

function Element( id, x, y, z, ry ) {

        const div = document.createElement( 'div' );
        div.style.width = '480px';
        div.style.height = '360px';
        div.style.backgroundColor = '#000';

        const iframe = document.createElement( 'iframe' );
        iframe.style.width = '480px';
        iframe.style.height = '360px';
        iframe.style.border = '0px';
        iframe.src = [ 'https://www.youtube.com/embed/', id, '?rel=0' ].join( '' );
        div.appendChild( iframe );

        const object = new CSS3DObject( div );
        object.position.set( x, y, z );
        object.rotation.y = ry;

        return object;

}

3. 主体方法实现

(1)常规步骤:声明相机、场景
(2)使用轨迹球控制器TrackballControls
(3)使用Element()方法生成四个视频墙,并加入到Group对象中,形成对象组合
(4)使用scene.add( group ) 将视频墙组加入到scene中,方便一起旋转、平移、缩放
(5)id为blocker的dom遮罩,通过式设置其全屏覆盖画布上。当操控3D场景时候显示遮罩,不操控3D场景时候就隐藏遮罩。目的: 防止在操控场景时候,触发了视频播放等功能。

function init() {

        const container = document.getElementById( 'container' );

        camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 5000 );
        camera.position.set( 500, 350, 750 );

        scene = new THREE.Scene();

        // CSS 3D渲染器
        renderer = new CSS3DRenderer();
        renderer.setSize( window.innerWidth, window.innerHeight );
        container.appendChild( renderer.domElement );

        // THREE.Group:对象组合,把零散模型组合在一起,方便一起旋转、缩放、平移等操作
        const group = new THREE.Group();
        group.add( new Element( 'SJOz3qjfQXU', 0, 0, 240, 0 ) );
        group.add( new Element( 'Y2-xZ-1HE-Q', 240, 0, 0, Math.PI / 2 ) );
        group.add( new Element( 'IrydklNpcFI', 0, 0, - 240, Math.PI ) );
        group.add( new Element( '9ubytEsCaS0', - 240, 0, 0, - Math.PI / 2 ) );
        scene.add( group );

        // TrackballControls 轨迹球控制器
        controls = new TrackballControls( camera, renderer.domElement );
        controls.rotateSpeed = 4;

        // Block iframe events when dragging camera

        const blocker = document.getElementById( 'blocker' );
        blocker.style.display = 'none';
        
        controls.addEventListener( 'start', function () {
             blocker.style.display = '';
        });
        controls.addEventListener( 'end', function () {
              blocker.style.display = 'none';
        });
}

三、一些识点讲解

1. Group 对象组的使用

它几乎和Object3D是相同的,目的是使得组中对象在语法上的结构更加清晰。
通过 THREE.Group 类创建一个组对象 group,然后通过 add 方法把网格模型 mesh1、mesh2 作为设置为组对象 group 的子对象,然后在通过执行 scene.add(group)把组对象 group 作为场景对象的 scene 的子对象。 以本示例为例,形成了三层结构: scene >> group >> mesh

const group = new THREE.Group();
group.add( new Element( 'SJOz3qjfQXU', 0, 0, 240, 0 ) );
group.add( new Element( 'Y2-xZ-1HE-Q', 240, 0, 0, Math.PI / 2 ) );
group.add( new Element( 'IrydklNpcFI', 0, 0, - 240, Math.PI ) );
group.add( new Element( '9ubytEsCaS0', - 240, 0, 0, - Math.PI / 2 ) );
scene.add( group );

(1) group.children
以下截图是对group对象的打印,children就是使用group.add()方法所添加的内容 image.png

(2) add() 添加多个场景,也可以使用简写形式 group.add(mesh1, mesh2);

group.add(mesh1, mesh2);

(3) 对group对象进行位移操作 如果使用以下操作,则整个立方体都会沿着y轴正方向移动100

group.translateY(100);

(4) name属性 我们可以对层级模型对象设置name属性,如:

group.name = "积木房";
mesh1.name = "积木房1层";

2. CSS3DObject、CSS3DSprite(精灵)、CSS2DObject 的区别

  • CSS3DObject 不面向摄像机,场景缩放时,缩小放大跟随着,不被模型遮挡,通过DOM事件点击
  • CSS2DObject 面向摄像机,场景缩放时,缩小放大都一样大,不被模型遮挡,通过DOM事件点击
  • CSS3DSprite 面向摄像机,场景缩放时,缩小放大跟随着,可以被射线拾取
    注意:CSS3DObject 和 CSS3DSprite 需要使用 CSS3DRenderer 渲染器, 而 CSS2DObject 使用 CSS2DRenderer 渲染器。其中正方体和标签position位置都是在原点。

下面借助坐标系、正方体来辅助观察,分别使用上面三个API绘制的标签的区别。

CSS3DObject: 跟随缩放,不被遮挡,旋转时候相对正方体位置始终不变

image.png

CSS2DObject: 不跟随缩放,不被遮挡

image.png

CSS3DSprite 旋转时候文字相对正方体会改变位置 image.png

三、完整代码

<!DOCTYPE html>
<html>
	<head>
		<title>three.js css3d - youtube</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: #ffffff;
			}

			#blocker {
				position: absolute;
				top: 0;
				left: 0;
				bottom: 0;
				right: 0;
			}
		</style>
	</head>
	<body>
		<div id="container"></div>
		<div id="blocker"></div>

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

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

			import { TrackballControls } from "three/addons/controls/TrackballControls.js";
			import {
				CSS3DRenderer,
				CSS3DObject,
			} from "three/addons/renderers/CSS3DRenderer.js";

			let camera, scene, renderer;
			let controls;

			// 生成一面视频墙
			function Element(id, x, y, z, ry) {
				const div = document.createElement("div");
				div.style.width = "480px";
				div.style.height = "360px";
				div.style.backgroundColor = "#000";

				const iframe = document.createElement("iframe");
				iframe.style.width = "480px";
				iframe.style.height = "360px";
				iframe.style.border = "0px";
				iframe.src = ["https://www.youtube.com/embed/", id, "?rel=0"].join("");
				div.appendChild(iframe);

				const object = new CSS3DObject(div);
				object.position.set(x, y, z);
				object.rotation.y = ry;

				return object;
			}

			init();
			animate();

			function init() {
				const container = document.getElementById("container");

				camera = new THREE.PerspectiveCamera(
					50,
					window.innerWidth / window.innerHeight,
					1,
					5000
				);
				camera.position.set(500, 350, 750);

				scene = new THREE.Scene();

				// CSS 3D渲染器
				renderer = new CSS3DRenderer();
				renderer.setSize(window.innerWidth, window.innerHeight);
				container.appendChild(renderer.domElement);

				// THREE.Group:对象组合,把零散模型组合在一起,方便一起旋转、缩放、平移等操作
				const group = new THREE.Group();
				group.add(new Element("SJOz3qjfQXU", 0, 0, 240, 0));
				group.add(new Element("Y2-xZ-1HE-Q", 240, 0, 0, Math.PI / 2));
				group.add(new Element("IrydklNpcFI", 0, 0, -240, Math.PI));
				group.add(new Element("9ubytEsCaS0", -240, 0, 0, -Math.PI / 2));
				scene.add(group);

				// TrackballControls 轨迹球控制器
				controls = new TrackballControls(camera, renderer.domElement);
				controls.rotateSpeed = 4;

				window.addEventListener("resize", onWindowResize);

				// Block iframe events when dragging camera

				const blocker = document.getElementById("blocker");
				blocker.style.display = "none";

				controls.addEventListener("start", function () {
					blocker.style.display = "";
				});
				controls.addEventListener("end", function () {
					blocker.style.display = "none";
				});
			}

			function onWindowResize() {
				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();
				renderer.setSize(window.innerWidth, window.innerHeight);
			}

			function animate() {
				requestAnimationFrame(animate);
				controls.update();
				renderer.render(scene, camera);
			}
		</script>
	</body>
</html>