当前位置: 首页 > article >正文

《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.GeometryTHREE.Face在最新的Three.js版本中被THREE.BufferGeometryTHREE.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:


http://www.kler.cn/a/414991.html

相关文章:

  • 网安瞭望台第4期:nuclei最新poc分享
  • Mac 系统上控制台常用性能查看命令
  • websocket前后端长连接之java部分
  • 【Python】Uvicorn服务器
  • 使用 Spring AI + Elasticsearch 让 RAG 变得简单
  • Swagger记录一次生成失败
  • 专业学习|如何绘制算法流程图?
  • 华为E9000刀箱(HWE9000V2)服务器硬件监控指标解读
  • http的文件上传和下载原理
  • 什么是C++中的函数对象?
  • 【二分查找】力扣 34. 在排序数组中查找元素的第一个和最后一个位置
  • 鸿蒙多线程应用-taskPool
  • spark3.x之后时间格式数据偶发报错org.apache.spark.SparkUpgradeException
  • Linux中网络文件系统nfs使用
  • S4 UPA of AA :新资产会计概览
  • 如何使用PHP爬虫获取店铺详情:一篇详尽指南
  • 初识 Django
  • 2024第六次随堂测验参考答案
  • leetcode 208. 实现 Trie (前缀树)
  • pico-sdk(八)-程序架构之自定义预处理变量
  • 【opencv-python】的cv2.imdecode()与cv2.imencode()
  • 力扣--LCR 148.验证图书取出顺序
  • 二维码有哪些网络安全风险隐患?
  • 【C语言篇】探索 C 语言结构体:从基础语法到数据组织的初体验
  • 力扣,88. 合并两个有序数组
  • [2024年3月10日]第15届蓝桥杯青少组stema选拔赛C++中高级(第二子卷、编程题(1))