《Learn Three.js》学习(2)构建Three.js基本组件
前言:
本章将了解内容包括Three中的主要组件;THERE.SCENE对象的作用;几何图形和格网如何关联;区别正射/透视投影摄像机
基础理论知识:
Three.scene(场景图)保存所有对象、光源和渲染所需的其他对象。
Three.scene 不仅仅是一个对象数组,还包含场景树形结构中的所有节点。
所有场景中的对象包括Three.scene都继承于THREE.Object3D的对象
为了更自由的查看场景,可以添加相关控制器,使用鼠标移动场景观看视角
// 创建鼠标控制器let mouseControls = new OrbitControls(camera, renderer.domElement);// 监听控制器,每次拖动后重新渲染画面mouseControls.addEventListener('change', function(){renderer.render(scene, camera);});
THREE.Scene.Add - 向场景中添加对象
THREE.Scene.Remove - 向场景中移除对象
THREE.Scene.children - 获取场景中所有的子对象列表
THREE.Scene.getObjectByName - 利用name属性,用于获取场景中的特定对象
THERE.Scene中常用的方法和属性表:
demo
效果:
代码:
<script setup>
// 导入three.js
import * as THREE from 'three';
//导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// 导入调试工具
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import Stats from 'three/examples/jsm/libs/stats.module.js';
import { ElMessage } from 'element-plus';
import { ref, onMounted } from 'vue';
const webglOutput = ref(null);
const statsOutput = ref(null);onMounted(() => {const stats = initStats();const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);scene.add(camera);const renderer = new THREE.WebGLRenderer();renderer.setClearColor(new THREE.Color(0xFFFFFF, 1.0));renderer.setSize(window.innerWidth, window.innerHeight);renderer.shadowMap.enabled = true;const planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1);const planeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff, side: THREE.DoubleSide });const plane = new THREE.Mesh(planeGeometry, planeMaterial);plane.receiveShadow = true;plane.rotation.x = -0.5 * Math.PI;plane.position.set(0, 0, 0);scene.add(plane);camera.position.set(-30, 40, 30);camera.lookAt(scene.position);const ambientLight = new THREE.AmbientLight(0x0c0c0c);scene.add(ambientLight);const spotLight = new THREE.SpotLight(0xffffff);spotLight.position.set(-40, 60, -10);spotLight.castShadow = true;scene.add(spotLight);webglOutput.value.appendChild(renderer.domElement);const controls = {rotationSpeed: 0.02,numberOfObjects: scene.children.length,removeCube() {const lastObject = scene.children[scene.children.length - 1];// 判断最后一个物体是否是一个网格,避免移除摄像机和光源if (lastObject instanceof THREE.Mesh) {scene.remove(lastObject);// 重新计算物体数量controls.numberOfObjects = scene.children.length;}},addCube() {const cubeSize = Math.ceil(Math.random() * 3);const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);const cubeMaterial = new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff });const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);cube.castShadow = true;cube.name = "cube-" + scene.children.length;cube.position.x = -30 + Math.round(Math.random() * planeGeometry.parameters.width);cube.position.y = Math.round(Math.random() * 5);cube.position.z = -20 + Math.round(Math.random() * planeGeometry.parameters.height);scene.add(cube);controls.numberOfObjects = scene.children.length;},outputObjects() {console.log(scene.children);}};const gui = new GUI();gui.add(controls, 'rotationSpeed', 0, 0.5);gui.add(controls, 'addCube');gui.add(controls, 'removeCube');gui.add(controls, 'outputObjects');gui.add(controls, 'numberOfObjects').listen();function render() {stats.update();scene.traverse((e) => {if (e instanceof THREE.Mesh && e !== plane) {e.rotation.x += controls.rotationSpeed;e.rotation.y += controls.rotationSpeed;e.rotation.z += controls.rotationSpeed;}});requestAnimationFrame(render);renderer.render(scene, camera);}function initStats() {const stats = new Stats();stats.setMode(0);stats.domElement.style.position = 'absolute';stats.domElement.style.left = '0px';stats.domElement.style.top = '0px';statsOutput.value.appendChild(stats.domElement);return stats;}// 创建鼠标控制器let mouseControls = new OrbitControls(camera, renderer.domElement);// 监听控制器,每次拖动后重新渲染画面mouseControls.addEventListener('change', function(){renderer.render(scene, camera);});render();
});
</script>
<template><div class="webgl-output" ref="webglOutput"></div><div class="stats-output" ref="statsOutput"></div>
</template>
<style>
*{margin:0;padding:0;
}canvas{display: block;position: fixed;top: 0;left: 0;width: 100vw;height: 100vh;
}
</style>
THERE.Scene
THERE.Scene包含两个属性 ——fog、overrideMaterial
fog
Scene.fog 线性雾
scene.fog = new THREE.Fog(0xffffff, 0.015, 100); // 0.015是近处的雾的浓度,100是远处的雾的浓度
Scene.fogExp2 指数雾
// 指数雾scene.fog = new THREE.FogExp2(0xffffff, 0.01); // 0.01是雾的浓度
overrideMaterial
设置overrideMaterial属性后,场景中所有物体都会使用该属性指向的材质,即使物体本身也设置了材质,使用该属性可以减少Three管理的材质数来提高运行效率。
scene.overrideMaterial = new THREE.MeshLambertMaterial({ color: 0x95d2c3 });
THERE.Geometry
一个立方体(三维空间中点集)有8个角,6个面,每个面都是包含3个顶角的三角形。
创建简单立方体
// 创建顶点
const vertices = new Float32Array([1, 3, 1,1, 3, -1,1, -1, 1,1, -1, -1,-1, 3, -1,-1, 3, 1,-1, -1, -1,-1, -1, 1
]);// 创建面(使用索引而不是直接引用顶点)
const indices = new Uint32Array([0, 2, 1,2, 3, 1,4, 6, 5,6, 7, 5,4, 5, 1,5, 0, 1,7, 6, 2,6, 3, 2,5, 7, 0,7, 2, 0,1, 3, 4,3, 6, 4
]);// 创建BufferGeometry
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
geometry.computeVertexNormals(); // 计算顶点法线// 创建材质
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide });// 创建网格(Mesh)
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 10, 0);
// 将网格添加到场景中
scene.add(mesh);
说明:由于版本更新使用对于简单几何体构造的方法出现不同,但核心思想一致
THREE.Geometry
和THREE.Face
在最新的Three.js版本中被THREE.BufferGeometry
和THREE.Float32BufferAttribute
所取代
进行面的绘制的点顺序很重要,顺时针为面向摄像机,反之背向摄像机
clone()
创建出几何体对象的拷贝
this.clone = function(){const clonedGeometry = mesh.geometry.clone();const materials = [new THREE.MeshLambertMaterial({ opacity: 0.6, color: 0xff44ff, transparent: true }),new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true })];const mesh = THREE.SceneUtils.createMultiMaterialObject(clonedGeometry, materials);mesh.children.forEach((e) => {e.castShadow = true;});mesh.name = "clone";mesh.position.x = 0;mesh.position.y = 10;mesh.position.z = 0;mesh.translateX = 5;scene.add(mesh);}
可以使用createMultiMaterialObject 和 WireFrameGeometry来添加线框,并使用xxx.material.linewidth 设置线框粗细
THERE.Mesh(网格)
Mesh作用于Geometry之上
mesh的常见的属性和方法:
正射/透视投影摄像机
若只需要简单VR摄像机(即简单的立体视觉效果),可用
1、THREE.StereoCamera将左右眼画面并排渲染;
2、使用其他特殊摄像机渲染时差屏障式的3D图像
3、传统的红蓝重叠式3D图像
透视(perspective)——更贴近真实世界
perspectiveCamera:
转换函数PToO:
// 相机转换函数this.changeCamera = function(){if(camera instanceof THREE.PerspectiveCamera){camera = new THREE.OrthographicCamera(window.innerWidth / -16, window.innerWidth / 16, window.innerHeight / 16, window.innerHeight / -16, -200, 500);camera.position.set(120, 60, 180);camera.lookAt(scene.position);this.perspective = "Orthographic";}else{camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.set(-30, 40, 30);camera.lookAt(scene.position);this.perspective = "Perspective";}}
正射(orthographic)
orthographicCamera: