网站标题图片怎么做,建立网站的请示,域名备案和网站备案有什么不同,北京展厅设计制作前言
为了满足工作需求#xff0c;我已着手学习 Three.js#xff0c;并决定详细记录这一学习过程。在此旅程中#xff0c;如果出现理解偏差或有其他更佳的学习方法#xff0c;请大家不吝赐教#xff0c;在评论区给予指正或分享您的宝贵建议#xff0c;我将不胜感激。
项…前言
为了满足工作需求我已着手学习 Three.js并决定详细记录这一学习过程。在此旅程中如果出现理解偏差或有其他更佳的学习方法请大家不吝赐教在评论区给予指正或分享您的宝贵建议我将不胜感激。
项目基础
请参考hello正方体 2。
1. 抗锯齿
除非直线完全水平或垂直否则使用方形像素绘制直线是很困难的。我们将使用一种称为抗锯齿(AA) 的技术来解决这个问题。
1.1 多重采样抗锯齿 (MSAA)
抗锯齿是使用内置的 WebGL 方法执行的即 多重采样抗锯齿 (MSAA)。
1.2 启用抗锯齿
renderer.js: 添加如下
import { WebGLRenderer } from three;/*** description - 创建渲染器* returns {WebGLRenderer} - 渲染器实例*/
export const createRenderder () {// 创建WebGLRenderer类的一个实例// 打开抗锯齿antialiasconst renderer new WebGLRenderer({ antialias: true });// 启用物理上正确的光照renderer.physicallyCorrectLights true;return renderer;
};注意 一旦创建了渲染器就无法更改此设置。
2. 浏览器窗口大小变化
目的是为了用户调整预览窗口的大小场景适应新的大小。
2.1 扩展 Resizer 类
因为初始化时要设置初始大小页面变化时也要重新设置大小所有要把这部分设置代码移入到一个函数里。将使用 ResizeObserver 来监听容器的大小变化。因为我们只调用了.render 一次它在画布中绘制了一个帧。当画布被调整大小时这个框架被拉伸以适应新的大小。所以要创建一个 onResize 钩子每次调整大小事件触发时生成一个新帧。在 World 中使用 onResize 函数。
Resizer.js添加如下
import { PerspectiveCamera, WebGLRenderer } from three;
class Resizer {#container; // 容器#camera; // 相机#renderer; // 渲染器onResize () {}; // 页面大小变化钩子函数/*** param {Element} container - 容器* param {PerspectiveCamera} camera - 相机* param {WebGLRenderer} renderer - 渲染器*/constructor(container, camera, renderer) {this.#container container;this.#camera camera;this.#renderer renderer;// 初始化sizethis.#setSize();// 监听容器大小变化const resizeObserver new ResizeObserver(() {this.#setSize();this.onResize();});resizeObserver.observe(container);}#setSize() {this.#camera.aspect this.#container.clientWidth / this.#container.clientHeight;this.#camera.updateProjectionMatrix();this.#renderer.setSize(this.#container.clientWidth,this.#container.clientHeight);this.#renderer.setPixelRatio(window.devicePixelRatio);}
}
export { Resizer };World.js添加如下
import { createScene } from ./components/scene;
import { createCamera } from ./components/camera;
import { createCude } from ./components/cube;
import { createLights } from ./components/lights;
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();const light createLights();this.#scene.add(cube, light);const resizer new Resizer(container, this.#camera, this.#renderer);resizer.onResize () {this.render();};}/*** description - 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}
}export { World };3. 动画循环运动
为立方体添加一个简单的旋转动画。
创建一个动画循环 创建 systems/Loop.js 模块并在其中创建一个新 Loop 类
import { PerspectiveCamera, Scene, WebGLRenderer } from three;
class Loop {#camera; // 相机#scene; // 场景#renderer; // 渲染/*** param {PerspectiveCamera} camera - 相机* param {Scene} scene - 场景* param {WebGLRenderer} renderer - 渲染*/constructor(camera, scene, renderer) {this.#camera camera;this.#scene scene;this.#renderer renderer;}/*** description - 动画循环开始*/start() {}/*** description - 动画循环结束*/stop() {}
}export { Loop };在 World 中将这个新类添加到导入列表中 World.js: 添加如下
import { createScene } from ./components/scene;
import { createCamera } from ./components/camera;
import { createCude } from ./components/cube;
import { createLights } from ./components/lights;
import { createRenderder } from ./systems/renderer;
import { Resizer } from ./systems/Resizer;
import { Loop } from ./systems/Loop;
class World {#scene; // 场景#camera; // 相机#renderer; // 渲染#loop; // 动画循环/*** param {Element} container - 容器*/constructor(container) {this.#scene createScene();this.#camera createCamera();this.#renderer createRenderder();this.#loop new Loop(this.#camera, this.#scene, this.#renderer);container.append(this.#renderer.domElement);const cube createCude();const light createLights();this.#scene.add(cube, light);const resizer new Resizer(container, this.#camera, this.#renderer);resizer.onResize () {this.render();};}/*** description - 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}/*** description - 动画循环开始*/start() {this.#loop.start();}/*** description - 动画循环结束*/stop() {this.#loop.stop();}
}export { World };启动动画循环 main.js添加如下
import { World } from ../World/World;function main() {// 获取容器const container document.querySelector(#scene-container);// 创建一个world类实例const world new World(container);// 开始动画循环world.start();
}main();使用.setAnimationLoop 创建循环 在内部循环是使用 .requestAnimationFrame。这种内置的浏览器方法可以智能地安排帧与显示器的刷新率同步如果您的硬件跟不上它会平滑地降低帧率。然而.setAnimationLoop 还有一点额外的魔力可以确保循环在虚拟现实和增强现实环境中工作。 Loop.js添加如下
import { PerspectiveCamera, Scene, WebGLRenderer } from three;
class Loop {#camera; // 相机#scene; // 场景#renderer; // 渲染/*** param {PerspectiveCamera} camera - 相机* param {Scene} scene - 场景* param {WebGLRenderer} renderer - 渲染*/constructor(camera, scene, renderer) {this.#camera camera;this.#scene scene;this.#renderer renderer;}/*** description - 动画循环开始*/start() {this.#renderer.setAnimationLoop(() {this.#renderer.render(this.#scene, this.#camera);});}/*** description - 动画循环结束*/stop() {this.#renderer.setAnimationLoop(null);}
}export { Loop };移除 onResize 钩子 现在循环正在运行每当我们调整窗口大小时都会在循环的下一次迭代中生成一个新帧。这足够快不会注意到任何延迟因此我们不再需要在调整大小时手动重绘场景。从 World 中移除 resizer.onResize 钩子 World.js添加如下
import { createScene } from ./components/scene;
import { createCamera } from ./components/camera;
import { createCude } from ./components/cube;
import { createLights } from ./components/lights;
import { createRenderder } from ./systems/renderer;
import { Resizer } from ./systems/Resizer;
import { Loop } from ./systems/Loop;
class World {#scene; // 场景#camera; // 相机#renderer; // 渲染#loop; // 动画循环/*** param {Element} container - 容器*/constructor(container) {this.#scene createScene();this.#camera createCamera();this.#renderer createRenderder();this.#loop new Loop(this.#camera, this.#scene, this.#renderer);container.append(this.#renderer.domElement);const cube createCude();const light createLights();this.#scene.add(cube, light);new Resizer(container, this.#camera, this.#renderer);}/*** description - 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}/*** description - 动画循环开始*/start() {this.#loop.start();}/*** description - 动画循环结束*/stop() {this.#loop.stop();}
}export { World };4. 动画系统
每次循环运行时希望通过将它们向前移动一帧来更新所有这些动画。就在我们渲染每一帧之前我们会让立方体旋转一点点 几乎是肉眼无法看到的微小量但随着时间的推移会产生流畅的动画效果。
Loop.tick 方法 为了处理所有这些我们需要一个更新所有动画的函数并且这个函数应该在每一帧开始时运行一次。 Loop.js添加如下
import { PerspectiveCamera, Scene, WebGLRenderer } from three;
class Loop {#camera; // 相机#scene; // 场景#renderer; // 渲染/*** param {PerspectiveCamera} camera - 相机* param {Scene} scene - 场景* param {WebGLRenderer} renderer - 渲染*/constructor(camera, scene, renderer) {this.#camera camera;this.#scene scene;this.#renderer renderer;}/*** description - 动画循环开始*/start() {this.#renderer.setAnimationLoop(() {this.tick();this.#renderer.render(this.#scene, this.#camera);});}/*** description - 动画循环结束*/stop() {this.#renderer.setAnimationLoop(null);}/*** description - 更新动画*/tick() {}
}export { Loop };cube.tick 方法 为动画对象创建一个.tick 方法更新自身。 cube.js: 添加如下
import { BoxGeometry, Mesh, MeshStandardMaterial, MathUtils } 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);// 添加更新动画方法tickcube.tick () {cube.rotation.z 0.01;cube.rotation.x 0.01;cube.rotation.y 0.01;};return cube;
};注意 像这样在运行时向现有类添加属性称为 猴子补丁。
Loop.#updatables 循环类中的动画对象列表。 Loop.js添加如下
import { PerspectiveCamera, Scene, WebGLRenderer, Object3D } from three;
class Loop {#camera; // 相机#scene; // 场景#renderer; // 渲染#updatables []; // 循环里的动画对象列表/*** param {PerspectiveCamera} camera - 相机* param {Scene} scene - 场景* param {WebGLRenderer} renderer - 渲染* param {Object3D[]} updatables - 循环里的动画对象列表*/constructor(camera, scene, renderer, updatables []) {this.#camera camera;this.#scene scene;this.#renderer renderer;this.#updatables updatables;}/*** description - 动画循环开始*/start() {this.#renderer.setAnimationLoop(() {this.tick();this.#renderer.render(this.#scene, this.#camera);});}/*** description - 动画循环结束*/stop() {this.#renderer.setAnimationLoop(null);}/*** description - 更新动画*/tick() {for (const object of this.#updatables) {object.tick();}}/*** description - 添加动画对象* param {...Object3D} object*/addObjectToUpdatables(...object) {this.#updatables.push(...object);}/*** description - 删除动画对象* param {...Object3D} object*/removeObjectFromUpdatables(...object) {this.#updatables this.#updatables.filter((obj) !object.includes(obj));}
}export { Loop };添加 cube 到 Loop.#updatables World.js添加如下
import { createScene } from ./components/scene;
import { createCamera } from ./components/camera;
import { createCude } from ./components/cube;
import { createLights } from ./components/lights;
import { createRenderder } from ./systems/renderer;
import { Resizer } from ./systems/Resizer;
import { Loop } from ./systems/Loop;
class World {#scene; // 场景#camera; // 相机#renderer; // 渲染#loop; // 动画循环/*** param {Element} container - 容器*/constructor(container) {this.#scene createScene();this.#camera createCamera();this.#renderer createRenderder();this.#loop new Loop(this.#camera, this.#scene, this.#renderer);container.append(this.#renderer.domElement);const cube createCude();this.#loop.addObjectToUpdatables(cube);const light createLights();this.#scene.add(cube, light);new Resizer(container, this.#camera, this.#renderer);}/*** description - 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}/*** description - 动画循环开始*/start() {this.#loop.start();}/*** description - 动画循环结束*/stop() {this.#loop.stop();}
}export { World };小结 立方体应该立即开始旋转。
5. 将动画速度与帧速率解耦
我们动画的速度取决于观看它的设备。我们的动画循环不会以固定速率生成帧。这意味着在 60Hz 屏幕上目标帧率为 60FPS在 90Hz 屏幕上目标帧率为 90FPS以此类推。 在每一种情况下动画循环都会以较低的速率生成帧并且这个速率可能会因为许多因素从一个时刻到下一个时刻波动。这称为可变帧速率。 为了防止这种情况我们需要将动画速度与帧速率解耦。我们将这样做当我们告诉一个对象.tick 前进一帧时我们将根据前一帧花费的时间来缩放移动的大小。
使用 Clock 类 用 Clock.getDelta 来衡量前一帧花了多长时间。.getDelta 告诉我们自上次调用.getDelta 以来已经过去了多少时间。 将结果保存在一个名为 delta 的变量中然后我们将其传递给每个动画对象的.tick 方法。 Loop.js添加如下
import {PerspectiveCamera,Scene,WebGLRenderer,Object3D,Clock,
} from three;
class Loop {#camera; // 相机#scene; // 场景#renderer; // 渲染#updatables []; // 循环里的动画对象列表#clock; // 秒表/*** param {PerspectiveCamera} camera - 相机* param {Scene} scene - 场景* param {WebGLRenderer} renderer - 渲染* param {Object3D[]} updatables - 循环里的动画对象列表*/constructor(camera, scene, renderer, updatables []) {this.#camera camera;this.#scene scene;this.#renderer renderer;this.#updatables updatables;this.#clock new Clock();}/*** description - 动画循环开始*/start() {this.#renderer.setAnimationLoop(() {this.tick();this.#renderer.render(this.#scene, this.#camera);});}/*** description - 动画循环结束*/stop() {this.#renderer.setAnimationLoop(null);}/*** description - 更新动画*/tick() {// .getDelta告诉我们自上次调用.getDelta以来已经过去了多少时间。const delta this.#clock.getDelta();for (const object of this.#updatables) {object.tick(delta);}}/*** description - 添加动画对象* param {...Object3D} object*/addObjectToUpdatables(...object) {this.#updatables.push(...object);}/*** description - 删除动画对象* param {...Object3D} object*/removeObjectFromUpdatables(...object) {this.#updatables this.#updatables.filter((obj) !object.includes(obj));}
}export { Loop };cube.js添加如下
import { BoxGeometry, Mesh, MeshStandardMaterial, MathUtils } 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);// 度数转弧度const radiansPerSecond MathUtils.degToRad(30);// 添加更新动画方法tickcube.tick (delta) {const radian radiansPerSecond * delta;cube.rotation.z radian;cube.rotation.x radian;cube.rotation.y radian;};return cube;
};6. 总结
至此已经全部完成。你好正方体 3如果出现理解偏差或有其他更佳的学习方法请大家不吝赐教在评论区给予指正或分享您的宝贵建议我将不胜感激。
主要文献
three.js 官网 《discoverthreejs》