【初体验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

相关文章

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学