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

2024-06-15 14:44

本文主要是介绍【初体验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》

这篇关于【初体验threejs】【学习】【笔记】hello,正方体2!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1063752

相关文章

51单片机学习记录———定时器

文章目录 前言一、定时器介绍二、STC89C52定时器资源三、定时器框图四、定时器模式五、定时器相关寄存器六、定时器练习 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示:以下是本篇文章正文内容,下面案例可供参考 一、定时器介绍 定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。 定时器作用: 1.用于计数系统,可

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

Tolua使用笔记(上)

目录   1.准备工作 2.运行例子 01.HelloWorld:在C#中,创建和销毁Lua虚拟机 和 简单调用。 02.ScriptsFromFile:在C#中,对一个lua文件的执行调用 03.CallLuaFunction:在C#中,对lua函数的操作 04.AccessingLuaVariables:在C#中,对lua变量的操作 05.LuaCoroutine:在Lua中,

AssetBundle学习笔记

AssetBundle是unity自定义的资源格式,通过调用引擎的资源打包接口对资源进行打包成.assetbundle格式的资源包。本文介绍了AssetBundle的生成,使用,加载,卸载以及Unity资源更新的一个基本步骤。 目录 1.定义: 2.AssetBundle的生成: 1)设置AssetBundle包的属性——通过编辑器界面 补充:分组策略 2)调用引擎接口API

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

大学湖北中医药大学法医学试题及答案,分享几个实用搜题和学习工具 #微信#学习方法#职场发展

今天分享拥有拍照搜题、文字搜题、语音搜题、多重搜题等搜题模式,可以快速查找问题解析,加深对题目答案的理解。 1.快练题 这是一个网站 找题的网站海量题库,在线搜题,快速刷题~为您提供百万优质题库,直接搜索题库名称,支持多种刷题模式:顺序练习、语音听题、本地搜题、顺序阅读、模拟考试、组卷考试、赶快下载吧! 2.彩虹搜题 这是个老公众号了 支持手写输入,截图搜题,详细步骤,解题必备

《offer来了》第二章学习笔记

1.集合 Java四种集合:List、Queue、Set和Map 1.1.List:可重复 有序的Collection ArrayList: 基于数组实现,增删慢,查询快,线程不安全 Vector: 基于数组实现,增删慢,查询快,线程安全 LinkedList: 基于双向链实现,增删快,查询慢,线程不安全 1.2.Queue:队列 ArrayBlockingQueue:

17.用300行代码手写初体验Spring V1.0版本

1.1.课程目标 1、了解看源码最有效的方式,先猜测后验证,不要一开始就去调试代码。 2、浓缩就是精华,用 300行最简洁的代码 提炼Spring的基本设计思想。 3、掌握Spring框架的基本脉络。 1.2.内容定位 1、 具有1年以上的SpringMVC使用经验。 2、 希望深入了解Spring源码的人群,对 Spring有一个整体的宏观感受。 3、 全程手写实现SpringM

工作流Activiti初体验—流程撤回【二】

已经玩工作流了,打算还是研究一下撤回的功能。但是流程图里面并不带撤回的组件,所以需要自己动态改造一下,还是延续上一个流程继续试验撤回功能。《工作流Activiti初体验【一】》 完整流程图 我们研究一下分发任务撤回到发起任务,其他环节的撤回类似 撤回的原理大概如下: 将分发任务后面的方向清空,把发起任务拼接到原来的判断网关,然后结束分发任务,这样流程就到发起任务了 此时的流程如上图,