【初体验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 = () => {
  // 创建一个直照光照实例并设置颜色是白色强度为8
  const 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》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值