【初体验threejs】【学习】【笔记】hello,正方体2!

前言

为了满足工作需求,我已着手学习Three.js,并决定详细记录这一学习过程。在此旅程中,如果出现理解偏差或有其他更佳的学习方法,请大家不吝赐教,在评论区给予指正或分享您的宝贵建议,我将不胜感激。

搭建一个threejs项目

请参考hello,正方体!创建threejs项目部分。

1. 规划项目组织结构

模块化软件设计
模块化软件设计是一种软件开发方法,其核心思想是将一个复杂的系统分解为多个相互独立、功能单一的模块。每个模块负责完成特定的功能,这样可以使得软件的开发、维护、测试和重用变得更加容易和高效。模块化设计遵循“高内聚,低耦合”的原则:

  • 高内聚:意味着每个模块内部的元素(函数、类等)紧密相关,共同完成一个明确的任务。这样可以确保模块内部逻辑清晰,易于理解和修改。
  • 低耦合:表示不同模块之间的依赖关系和交互尽可能减少,每个模块对外部的依赖仅限于必要的接口。这样做的好处是可以独立地开发、测试每个模块,以及在不影响其他模块的情况下修改或替换某个模块。
  1. 在根目录下创建World文件夹并在该文件夹内创建World.js。
    World.js:添加如下
class World {#scene; // 场景#camera; // 相机#renderer;// 渲染/*** @param {Element} container - 容器*/constructor(container) {}/*** 渲染函数*/render() {}
}
export { World };

小结
设置私有属性,仅使用设计的接口进行交互,并且隐藏其他所有内容。

  1. 在src/main.js中引入World类
    main.js:添加如下
import { World } from "../World/World";
// 主函数
function main() {// 获取容器const container = document.querySelector("#scene-container");// 创建一个World类的实例const world = new World(container);// 渲染场景world.render();
}
// 调用主函数
main();
  1. 创建文件夹
  • 在World文件内新建components文件夹存放在组件,如立方体、相机和场景本身。
  • 在World文件内新建systems文件夹存放在组件或其他系统上运行的东西
  1. 创建渲染器模块
    在systems文件夹内新建renderer.js
    renderer.js:添加如下
import { WebGLRenderer } from "three";/*** @description - 创建渲染器* @returns {WebGLRenderer} - 渲染器实例*/
export const createRenderder = () => {// 创建WebGLRenderer类的一个实例const renderer = new WebGLRenderer();return renderer;
};
  1. 创建场景模块
    在components内新建scene.js
    scene.js:添加如下
import { Scene, Color } from "three";/*** @description - 创建场景* @returns {Scene} - 场景实例*/
export const createScene = () => {// 创建WebGLRenderer类的一个实例const scene = new Scene();// 设置场景背景颜色为天蓝色scene.background = new Color("skyblue");return scene;
};
  1. 创建相机模块
    在components文件夹内创建camera.js文件
    camera.js:添加如下
import { PerspectiveCamera } from "three";
/*** @description - 创建相机* @returns {PerspectiveCamera} - 透视相机实例*/
export const createCamera = () => {// 创建一个PerspectiveCamera类实例 并设置初始值const camera = new PerspectiveCamera(35, 1, 0.1, 100);// 设置相机位置camera.position.set(0, 0, 10);return camera;
};

小结
使用了一个虚拟值1作为纵横比(aspect),因为它依赖于container的尺寸。避免不必要地传递东西,将推迟设置纵横比。如有更好的想法,请在评论区留言,谢谢。

  1. 创建立方体模块
    在components文件夹内创建cube.js文件
    cube.js:添加如下
import { BoxGeometry, Mesh, MeshBasicMaterial } from "three";
/*** @description - 创建立方体* @returns {Mesh} - 网格实例*/
export const createCude = () => {// 创建边长为2的几何体(就是边长2米)const geometry = new BoxGeometry(2, 2, 2);// 创建一个默认基础材质(白色)const material = new MeshBasicMaterial();// 创建一个网格添加几何体和材质const cube = new Mesh(geometry, material);return cube;
};
  1. 创建大小模块
    在systems文件夹内创建Resizer.js(文件名以大写 R 开头表示它是一个类)
    Resizer.js:添加如下
class Resizer {constructor() {}
}
export { Resizer };
  1. 设置World类
  • 1.World.js中引入刚刚创建的五个模块。
  • 2.设置场景,相机,渲染。
  • 3.将画布添加到容器中。
  • 4.渲染场景。
  • 5.创建立方体并添加如场景中。
    World.js:添加如下
import { createScene } from "./components/scene";
import { createCamera } from "./components/camera";
import { createCude } from "./components/cube";
import { createRenderder } from "./systems/renderer";
import { Resizer } from "./systems/Resizer";
class World {#scene; // 场景#camera; // 相机#renderer;// 渲染/*** @param {Element} container - 容器*/constructor(container) {this.#scene = createScene();this.#camera = createCamera();this.#renderer = createRenderder();container.append(this.#renderer.domElement);const cube = createCude();this.#scene.add(cube);}/*** 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}
}
export { World };
  1. 设置Resizer类
    Resizer.js:添加如下
import { PerspectiveCamera, WebGLRenderer } from "three";
class Resizer {/*** @param {Element} container - 容器* @param {PerspectiveCamera} camera - 相机* @param {WebGLRenderer} renderer - 渲染器*/constructor(container, camera, renderer) {// 设置纵横比camera.aspect = container.clientWidth / container.clientHeight;// 更新平截头体camera.updateProjectionMatrix();// 设置渲染器大小renderer.setSize(container.clientWidth, container.clientHeight);// 设置设备像素大小 这是防止 HiDPI 显示器模糊所必需的 (也称为视网膜显示器)。renderer.setPixelRatio(window.devicePixelRatio);}
}
export { Resizer };

小结
平截头体不会自动重新计算,因此当我们更改存储在camera.aspect、camera.fov、camera.near和camera.far中的任何这些设置时,我们还需要更新平截头体。

  1. 在World类构造函数中创建一个Resizer实例
    World.js:添加如下
import { createScene } from "./components/scene";
import { createCamera } from "./components/camera";
import { createCude } from "./components/cube";
import { createRenderder } from "./systems/renderer";
import { Resizer } from "./systems/Resizer";
class World {#scene; // 场景#camera; // 相机#renderer;// 渲染/*** @param {Element} container - 容器*/constructor(container) {this.#scene = createScene();this.#camera = createCamera();this.#renderer = createRenderder();container.append(this.#renderer.domElement);const cube = createCude();this.#scene.add(cube);new Resizer(container, this.#camera, this.#renderer);}/*** 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}
}
export { World };

小结
至此规划项目组织结构已经结束了,现在运行项目看看吧。

2. 基于物理的渲染和照明

基于物理的渲染 (PBR)已成为渲染实时和电影 3D 场景的行业标准方法。顾名思义,这种渲染技术使用真实世界的物理学来计算表面对光的反应方式,从而避免在场景中设置材质和照明时进行猜测。

  1. 启用物理上正确的光照
    renderer.js:启用物理正确的照明
import { WebGLRenderer } from "three";/*** @description - 创建渲染器* @returns {WebGLRenderer} - 渲染器实例*/
export const createRenderder = () => {// 创建WebGLRenderer类的一个实例const renderer = new WebGLRenderer();// 启用物理上正确的光照renderer.physicallyCorrectLights = true;return renderer;
};
  1. 添加一个DirectionalLight到我们的场景
    在components目录下创建light.js文件
    light.js:添加如下
import { DirectionalLight } from "three";
/*** @description - 直接照明 (阳光)* @returns {DirectionalLight} - 直照光照实例*/
export const createLights = () => {// 创建一个直照光照实例并设置颜色是白色强度为8const light = new DirectionalLight("white", 8);// 设置光源位置 现在灯光从(10,10,10)照向(0,0,0)。light.position.set(10, 10, 10);return light;
};

小结
DirectionalLight设计的目的是模仿遥远的光源,例如太阳。来自DirectionalLight的光线不会随着距离而消失。场景中的所有对象都将被同样明亮地照亮,无论它们放在哪里——即使是在灯光后面。DirectionalLight的光线是平行的,从一个位置照向一个目标。默认情况下,目标放置在我们场景的中心(点(0,0,0)),所以当我们移动周围的光线时,它总是会向中心照射。

  1. 在World.js中,导入新模块并使用
    World.js:添加如下
import { createScene } from "./components/scene";
import { createCamera } from "./components/camera";
import { createCude } from "./components/cube";
import { createRenderder } from "./systems/renderer";
import { createLights } from "./components/lights";
import { Resizer } from "./systems/Resizer";
class World {#scene; // 场景#camera; // 相机#renderer;// 渲染/*** @param {Element} container - 容器*/constructor(container) {this.#scene = createScene();this.#camera = createCamera();this.#renderer = createRenderder();container.append(this.#renderer.domElement);const cube = createCude();const light = createLights();this.#scene.add(cube, light);new Resizer(container, this.#camera, this.#renderer);}/*** 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}
}
export { World };
  1. 切换材质MeshStandardMaterial
    cube.js:添加如下
import { BoxGeometry, Mesh, MeshStandardMaterial } from "three";
/*** @description - 创建立方体* @returns {Mesh} - 网格实例*/
export const createCude = () => {// 创建边长为2的几何体(就是边长2米)const geometry = new BoxGeometry(2, 2, 2);// 创建一个高质量、通用、物理精确的材料 设置颜色为紫色const material = new MeshStandardMaterial({ color: "purple" });  // 创建一个网格添加几何体和材质const cube = new Mesh(geometry, material);// 旋转立方体cube.rotation.set(-0.5, -0.1, 0.8);return cube;
};

小结
用 MeshStandardMaterial代替基本材料MeshBasicMaterial。这是一种高质量、通用、物理精确的材料,可以使用真实世界的物理方程对光做出反应。

总结

在向场景添加灯光之前,我们将切换到使用物理上正确的光照强度计算。
创建物理大小的场景,为了使物理上正确的照明准确,如果你的房间有 1000 公里宽,那么使用真实灯泡的数据是没有意义的!
three.js 中的大小单位是米。我们之前创建的2×2×2的立方体每边长为两米。camera.far = 100意味着我们可以看到一百米的距离。camera.near = 0.1意味着距离相机不到十厘米的物体将不可见。使用米为单位是一种约定,而不是规则。如果不遵循它,那么除了物理上精确的照明之外的一切都仍然有效。但是,如果想要物理上准确的照明,那么必须使用以下公式将场景构建到真实世界的规模:1单位 = 1米。
即使我们使用 PBR,现实世界和 three.js 之间的一个区别是默认情况下对象不会阻挡光线。光路径中的每个物体都会收到照明,即使路上有一堵墙。落在物体上的光会照亮它,但也会直接穿过并照亮后面的物体。物理正确性就这么多!
至此已经全部完成。你好,正方体2!如果出现理解偏差或有其他更佳的学习方法,请大家不吝赐教,在评论区给予指正或分享您的宝贵建议,我将不胜感激。

主要文献

three.js官网
《discoverthreejs》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/853089.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

**自动驾驶技术介绍**

自动驾驶技术介绍 自动驾驶技术是一种使车辆能够在无需人类操控的情况下自主行驶的技术。它基于先进的传感器、计算机视觉、人工智能和机器学习等技术,让车辆能够感知周围环境、做出决策并执行相应的行动。自动驾驶技术的发展旨在提高交通安全性、减少交通事故&…

AI金融投资:批量下载深交所公募REITs公开说明书

打开深交所公募REITs公开说明书页面,F12查看网络,找到真实地址:https://reits.szse.cn/api/disc/announcement/annList?random0.3555675437003616 { "announceCount": 39, "data": [ { "id": "80bc9…

学习笔记——网络管理与运维——SNMP(概述)

一、SNMP概述 1、SNMP背景 SNMP的基本思想:为不同种类的设备、不同厂家生产的设备、不同型号的设备,定义为一个统一的接口和协议,使得管理员可以是使用统一的外观面对这些需要管理的网络设备进行管理。 通过网络,管理员可以管理…

flask返回的数据怎么是转义后的字符串啊

Flask在返回JSON数据时,默认情况下会对特殊字符进行转义,以确保数据能安全地在HTML页面中展示,避免XSS(跨站脚本攻击)等安全问题。如果不希望Flask对JSON响应中的字符串自动转义,通常是因为你希望在前端直接使用这些数据(例如作为JavaScript的一部分),那么需要确保数据…

主题切换之根元素CSS自定义类

要实现CSS样式的主题切换,可以通过在HTML中添加一个按钮来触发JavaScript事件,进而通过JavaScript动态修改HTML元素的class或直接切换CSS文件,以达到改变页面整体风格的目的。以下是实现这一功能的步骤、原理及代码示例。 原理: …

JavaScript 的运行

语法分析预编译解释执行 1.语法分析 语法分析是 JavaScript 引擎处理代码的第一步。 在这个阶段,引擎将源代码字符串分解成一个个的词素(token),这些词素是语言中有意义的最小单元,如关键字、变量名、操作符等。 语…

微服务与分布式面试题

什么是RPC远程调用? RPC 的全称是 Remote Procedure Call 是一种进程间通信方式。 它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即无论是调用本地接口/服…

深度学习中的热力图

深度学习中的热力图 热力图(Heatmap)在深度学习中是用于可视化数据、模型预测结果或特征的重要工具。它通过颜色的变化来表示数值的大小,便于直观地理解数据的分布、模型的关注区域以及特征的重要性。以下是深度学习中热力图的主要应用和特点…

Python 正则表达式语法

Python 中的正则表达式是通过 re 模块提供的,它支持大多数正则表达式的语法。以下是一些基本的正则表达式语法元素: 字符匹配: . 匹配任意单个字符,除了换行符。\d 匹配任意数字,等同于 [0-9]。\D 匹配任意非数字字符,…

6个免费自动写文章软件,简直好用到爆

对于创作者而言,创作一篇高质量的文章并非易事,它需要耗费大量的时间与精力去构思、组织语言、斟酌字句。灵感并非总是源源不断,有时我们可能会陷入思维的僵局,不知从何下手。而此时,免费自动写文章软件就如同黑暗中的…

RabbitMQ无法删除unsynchronized队列及解决办法

一、故障环境 操作系统:CentOS7 RabbitMQ:3 nodes Cluster RabbitMQ version: 3.8.12 Erlang Version:22.3 Queue Type:Mirror,with polices 二、故障表现: 2.1 管理界面队列列表中存在部分队列镜像同步状态标红: 2.2 TPS为0,无消费者,其他节点镜像未同步且无法手动…

QBrush 详解

QBrush是Qt框架中的一个类,它用于定义图形的填充模式。QBrush可以用于填充图形项(如QGraphicsItem)的形状,也可以用于绘制背景等。 关键特性 颜色:QBrush可以设置颜色,用于填充图形。样式:QBr…

C# Web控件与数据感应之模板循环输出

目录 关于模板循环输出 准备数据源 ​范例运行环境 RepeatHtml 方法 设计与实现 如何获取模板内容 getOuterHtml 方法 getInnerHtml 方法 调用示例 小结 关于模板循环输出 数据感应也即数据捆绑,是一种动态的,Web控件与数据源之间的交互&…

Web前端进国企:挑战与机遇并存

Web前端进国企:挑战与机遇并存 随着互联网的飞速发展,Web前端技术已经成为企业信息化建设的重要组成部分。对于许多热衷于前端技术的年轻人来说,进入国企工作既是一种挑战,也是一种机遇。本文将从四个方面、五个方面、六个方面和…

Qt C++ TCP服务端响应多客户端通讯

本示例使用的设备&#xff1a;WIFI无线4G网络RFID云读卡器远程网络开关物流网阅读器TTS语音-淘宝网 (taobao.com) #include "mainwindow.h" #include "ui_mainwindow.h" #include "QMessageBox" #include <QDebug> #include <exceptio…

AI与Python:探索智能化时代的编程利器

AI与Python的强大组合 Python因其简洁易学的语法和丰富的库生态&#xff0c;成为了AI开发的首选语言。无论是数据处理、机器学习还是深度学习&#xff0c;Python都能提供强大的支持。以下是几个常见的AI应用领域&#xff1a; 数据分析与处理 使用Pandas和NumPy库进行数据处理和…

pnpm : 无法加载文件 C:\Users\WTK\AppData\Roaming\npm\pnpm.ps1,因为在此系统上禁止运行脚本。

PS D:\VUE3\vue-pure-admin-main> pnpm i pnpm : 无法加载文件 C:\Users\WTK\AppData\Roaming\npm\pnpm.ps1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https:/go.microsoft.com/fwlink/?Link ID135170 中的 about_Execution_Policies。 所在…

Lexar NM620 512GB SSD PCIE3.0 X4测评

Lexar NM620 512GB SSD PCIE3.0 X4测评 官方可选容量256GB~2TB PCIE 3.0X4 支持NVME 1.4协议 CDM顺序Read速度3448MB\s CDM顺序Write速度2626MB\s CDM 4K随机Read速度465MB\s CDM 4K随机Write速度602MB\s AS SSD顺序Read速度为2855MB\s AS SSD顺序Write速度为2331MB\s AS SSD…

vue2和vue 3 的响应式原理

vue 3 响应式原理 在 Vue 3 中&#xff0c;响应式系统的核心是使用了 ES6 的 Proxy 对象来实现对数据的拦截和响应式更新。 简单的 Proxy 示例&#xff1a; const data { count: 0 }; const handler {get(target, key, receiver) {// 当访问属性时触发track(target, key);…

几款让你怦然心动的神奇工具——搜嗖工具箱

alteredqualia AlteredQualia 脑洞爆炸器网站&#xff0c;不得不说这是一个神奇的网站&#xff0c;在这个网站上你可以实现不可思议的各种操作&#xff0c;让我们对网站有了新的认知&#xff0c;因为它告诉你不是所有有趣的网站都那么花哨&#xff0c;有些网站看着外形平淡无奇…