把网站做静态化是什么意思,美橙网站建设,中山专业网站建设价格,怎么创建网页的快捷方式最近在看three.js相关的东西#xff0c;想着学习一下threejs给的examples。源码是用html结合js写的#xff0c;恰好最近也在学习react#xff0c;就用react框架学习一下。 本文参考的是threeJs给的第一个示例 three.js examples (threejs.org) 一、下载threeJS源码 通常我们… 最近在看three.js相关的东西想着学习一下threejs给的examples。源码是用html结合js写的恰好最近也在学习react就用react框架学习一下。 本文参考的是threeJs给的第一个示例 three.js examples (threejs.org) 一、下载threeJS源码 通常我们只用通过npm引入threejs的包就可以使用threejs了。为什么这里需要下载源码呢因为我们要复刻源码给的示例相关的模型我们是没有的需要使用源码里用到的模型及解析工具 GitHub - mrdoob/three.js: JavaScript 3D Library. 从git上拉取代码后可以找到示例一的源码 阅读源码可以发现完成示例需要引入jsm/libs/draco/gltf/路径以及models/gltf/LittlestTokyo.glb模型。 拷贝threeJS的必要的模型和方法
为了方便后续学习我们直接将这两个文件夹jsm和models拷贝到react项目中注意路径最好是public下public是默认的静态资源加载入口 二、功能解析与改写 react搭建及threejs引入可以参考我的之前的博客这里不多赘述 Three.js机器人与星系动态场景实现3D渲染与交互式控制-CSDN博客 引入必要信息
import { useEffect, useRef } from react;
import * as THREE from three;
import Stats from three/examples/jsm/libs/stats.module.js;
import { GLTF, GLTFLoader } from three/examples/jsm/loaders/GLTFLoader.js;
import { OrbitControls } from three/examples/jsm/controls/OrbitControls;
import { RoomEnvironment } from three/examples/jsm/environments/RoomEnvironment.js;
import { DRACOLoader } from three/examples/jsm/loaders/DRACOLoader.js;
初始化Render渲染器/Scene场景/ camer相机/controls轨道控制器 // 初始化渲染器的函数
/*** 初始化 WebGL 渲染器* returns {THREE.WebGLRenderer} 创建并配置好的渲染器实例*/
// 初始化渲染
function initRender(): THREE.WebGLRenderer {// 创建一个WebGL渲染器const renderer new THREE.WebGLRenderer({ antialias: true });// 根据设备像素比设置渲染器像素比renderer.setPixelRatio(window.devicePixelRatio);// 设置渲染器大小renderer.setSize(window.innerWidth, window.innerHeight);return renderer;
}// 初始化场景的函数
/*** 初始化场景* param {THREE.WebGLRenderer} renderer - 渲染器实例* returns {THREE.Scene} 创建并配置好的场景实例*/
function initScene(renderer: THREE.WebGLRenderer) {// 创建 PMREM 生成器const pmremGenerator new THREE.PMREMGenerator(renderer);// 创建场景const scene new THREE.Scene();// 设置场景背景scene.background new THREE.Color(0xbfe3dd);// 设置场景环境scene.environment pmremGenerator.fromScene(new RoomEnvironment(renderer), 0.04).texture;return scene;
}// 初始化相机的函数
/*** 初始化相机* param {number} x - 相机在 x 轴的位置* param {number} y - 相机在 y 轴的位置* param {number} z - 相机在 z 轴的位置* returns {THREE.PerspectiveCamera} 创建并配置好位置的相机实例*/
function initCamera(x: number, y: number, z: number) {const camera new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);camera.position.set(x, y, z);return camera;
}// 初始化控制器的函数
/*** 初始化轨道控制器* param {THREE.PerspectiveCamera} camera - 相机实例* param {THREE.WebGLRenderer} renderer - 渲染器实例* returns {OrbitControls} 创建并配置好的轨道控制器实例*/
function initControls(camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer) {const controls new OrbitControls(camera, renderer.domElement);controls.update();controls.enablePan false;controls.enableDamping true;return controls;
}组件核心方法Keyframes 采用react的函数式组件写法首字母大写作为组件名并导出 整个流程是初始化渲染器、scene场景、camera相机、controls轨道控制器在场景中引入模型并使用dracoLoader解压GLTFLoader引入的模型开启模型上的动画设置场景动画。 /*** Keyframes 组件函数*/
function Keyframes() {const containerRef useRefHTMLDivElement(null); // 创建用于引用 HTML 元素的 refconst clock new THREE.Clock(); // 创建时钟实例const statsRef useRefStats(); // 创建用于引用统计信息的 refconst mixerRef useRefTHREE.AnimationMixer(); // 创建用于引用动画混合器的 refconst renderer initRender(); // 初始化渲染器const scene initScene(renderer); // 初始化场景const camera initCamera(5, 2, 10); // 初始化相机const controls initControls(camera, renderer); // 初始化控制器controls.target.set(0, 0.5, 0); // 设置控制器的目标const dracoLoader new DRACOLoader(); // 创建 Draco 加载器dracoLoader.setDecoderPath(jsm/libs/draco/gltf/); // 设置 Draco 解码器路径const loader new GLTFLoader(); // 创建 GLTF 加载器loader.setDRACOLoader(dracoLoader); // 为 GLTF 加载器设置 Draco 加载器// 加载 GLTF 模型loader.load(models/gltf/LittlestTokyo.glb,(gltf: GLTF) {const model gltf.scene; // 获取模型的场景model.position.set(1, 1, 0); // 设置模型的位置model.scale.set(0.01, 0.01, 0.01); // 设置模型的缩放scene.add(model); // 将模型添加到场景mixerRef.current new THREE.AnimationMixer(model); // 创建动画混合器mixerRef.current.clipAction(gltf.animations[0]).play(); // 播放动画renderer.setAnimationLoop(animate); // 设置渲染循环},undefined,(e) {console.error(e); // 处理加载错误},);// 渲染循环函数/*** 每一帧的更新和渲染逻辑*/function animate() {const delta clock.getDelta(); // 获取时间间隔mixerRef.current mixerRef.current.update(delta); // 更新动画混合器controls.update(); // 更新控制器statsRef.current statsRef.current.update(); // 更新统计信息renderer.render(scene, camera); // 渲染场景和相机}// 处理窗口大小改变的函数/*** 处理窗口大小改变时的相机和渲染器更新*/function onWindowResize() {camera.aspect window.innerWidth / window.innerHeight; // 更新相机的宽高比camera.updateProjectionMatrix(); // 更新相机的投影矩阵controls.update(); // 更新控制器renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器的大小}// 使用 useEffect 钩子useEffect(() {if (!containerRef.current) return;containerRef.current.appendChild(renderer.domElement); // 将渲染器的 DOM 元素添加到引用的元素中statsRef.current new Stats(); // 创建统计信息实例containerRef.current.appendChild(statsRef.current.dom); // 将统计信息的 DOM 元素添加到引用的元素中window.addEventListener(resize, onWindowResize); // 添加窗口大小改变的监听事件return () {window.removeEventListener(resize, onWindowResize); // 清除窗口大小改变的监听事件renderer.setAnimationLoop(null); // 清除渲染循环};}, []);return div ref{containerRef}/div; // 返回一个带有 ref 的 div 元素
}
export default Keyframes; // 导出 Keyframes 组件通过div ref{containerRef}/div 创建一个dom元素用于3D场景挂载 模型加载与Draco解码
示例模型提供的是压缩后的模型在页面加载时需要进行解压必须使用dracoLoader方法设置解码方法所在路径。在通过GLTFLoader导入。示例如下
const dracoLoader new DRACOLoader(); // 创建 Draco 加载器dracoLoader.setDecoderPath(jsm/libs/draco/gltf/); // 设置 Draco 解码器路径const loader new GLTFLoader(); // 创建 GLTF 加载器loader.setDRACOLoader(dracoLoader); // 为 GLTF 加载器设置 Draco 加载器// 加载 GLTF 模型loader.load(models/gltf/LittlestTokyo.glb,(gltf: GLTF) {//处理模型},undefined,(e) {console.error(e); // 处理加载错误},);AnimationMixer 动画混合器 AnimationMixer动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时每个对象都可以使用同一个动画混合器。 参数rootObject 混合器播放的动画所属的对象。就是包含动画模型的场景对象。常用参数和属性 .time 全局的混合器时间。.clipAction(AnimationClip) 返回所传入的剪辑参数的AnimationAction对象。AnimationAction用来调度存储在AnimationClip中的动画。 AnimationClip 动画剪辑是一个可重用的关键帧轨道集它代表动画。 .getRoot() 返回混合器的根对象。.update() 推进混合器时间并更新动画。在渲染函数中调用更新动画。 在我们的示例中模型加载到场景时默认时没有动画的也就是模型自身的动画比如小火车和风扇小人都是不动的。 在模型加载的时候通过AnimationMixer开启模型动画 // 加载 GLTF 模型loader.load(models/gltf/LittlestTokyo.glb,(gltf: GLTF) {const model gltf.scene; // 获取模型的场景model.position.set(1, 1, 0); // 设置模型的位置model.scale.set(0.01, 0.01, 0.01); // 设置模型的缩放scene.add(model); // 将模型添加到场景mixerRef.current new THREE.AnimationMixer(model); // 创建动画混合器mixerRef.current.clipAction(gltf.animations[0]).play(); // 播放动画renderer.setAnimationLoop(animate); // 设置渲染循环},undefined,(e) {console.error(e); // 处理加载错误},);
setAnimationLoop动画循环 在Three.js中setAnimationLoop 方法是用来设置一个函数这个函数会在每一帧被调用来进行渲染。这是必须的因为在Three.js中渲染循环不是自动开始的你需要告诉渲染器何时以及如何进行渲染。 以下是为什么加载模型时必须使用 setAnimationLoop 的一些原因 渲染控制通过 setAnimationLoop你可以控制渲染循环的开始和结束。如果你不设置它即使模型加载完成也不会自动开始渲染过程。 动画播放在你的代码中你使用了 AnimationMixer 来播放模型中的动画。这个动画需要在每一帧更新以确保动画的连贯性和流畅性。setAnimationLoop 允许你在每一帧更新动画状态。 性能优化使用 setAnimationLoop 可以让你在不需要渲染的时候停止渲染比如在浏览器标签页不可见时这样可以节省资源并提高性能。 逻辑更新在 animate 函数中你可以执行除了渲染之外的其他逻辑比如更新动画、控制器和统计信息等。这些更新是渲染过程的一部分需要在每一帧进行。 如果你不使用 setAnimationLoop你需要自己手动创建一个循环来不断调用 renderer.render(scene, camera)并且确保在合适的时机更新动画和其他逻辑。这通常是通过 requestAnimationFrame 函数来实现的但Three.js提供了 setAnimationLoop 来简化这一过程。 总之setAnimationLoop 是Three.js中用来启动和维持渲染循环的关键方法特别是在涉及到动画的情况下它是必须的。 可以看到模型自身的多个动画都动起来了
Stats.js帧检测工具 不管是做游戏还是做普通网页在这个时代基本都离不开动画。说到动画第一个联想到的概念就是“帧”。这是用来衡量和描述动画是否流畅的一个单位。 示例程序的左上角有个工具窗口持续监测FPS数值 FPS是“Frames Per Second”的缩写意为“每秒帧数”。在视频游戏和计算机图形学中FPS用来衡量显示设备每秒钟能够显示的静止图像帧的数量。这个数值越高表示图像更新得越快视觉效果就越流畅。 在游戏领域高FPS通常意味着更平滑的游戏体验尤其是在快速移动或复杂场景中。然而FPS并不是唯一影响游戏体验的因素图像质量、响应时间和系统稳定性也同样重要。 一般来说人眼能够感知到的流畅动画大约需要30FPS以上而60FPS或更高则被认为是高质量游戏体验的标准。不过这也取决于个人的视觉感知能力和对流畅度的要求。 用法 在使用 npm install three 下载的依赖包中已经包含了 Stats.js 了 可以这样引入到项目中
import Stats from three/examples/jsm/libs/stats.module.js;通过new Stats()方法创建一个stats实例 。默认showPanel是0显示FPS面板。 通过showPanel方法切换显示方式可以根据dom改变stats面板的位置使用示例如下 const statsRef useRefStats(); // 创建用于引用统计信息的 refstatsRef.current new Stats(); // 创建统计信息实例statsRef.current.showPanel(1);statsRef.current.dom.style.position absolute; // 设置统计信息的 DOM 元素的位置statsRef.current.dom.style.top 0px; // 设置统计信息的 DOM 元素的位置statsRef.current.dom.style.left 0px; // 设置统计信息的 DOM 元素的位置通过操作dom的方式将stats节点追加到3D场景中 containerRef.current.appendChild(statsRef.current.dom); // 将统计信息的 DOM 元素添加到引用的元素中默认就显示在屏幕的左上角 当点击该面板时还可以切换监听的类型 响应式窗口
页面加载时给了初始的renderer的宽高但是如果用户使用过程中可视区域发生了变化renderer无法自动使用屏幕 可以在useEffect里通过事件监听浏览器的resize事件当浏览器尺寸变化时重新以最新的宽高设为renderer的尺寸信息 // 处理窗口大小改变的函数/*** 处理窗口大小改变时的相机和渲染器更新*/function onWindowResize() {camera.aspect window.innerWidth / window.innerHeight; // 更新相机的宽高比camera.updateProjectionMatrix(); // 更新相机的投影矩阵controls.update(); // 更新控制器renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器的大小}// 使用 useEffect 钩子useEffect(() {if (!containerRef.current) return;containerRef.current.appendChild(renderer.domElement); // 将渲染器的 DOM 元素添加到引用的元素中statsRef.current new Stats(); // 创建统计信息实例containerRef.current.appendChild(statsRef.current.dom); // 将统计信息的 DOM 元素添加到引用的元素中window.addEventListener(resize, onWindowResize); // 添加窗口大小改变的监听事件return () {window.removeEventListener(resize, onWindowResize); // 清除窗口大小改变的监听事件renderer.setAnimationLoop(null); // 清除渲染循环};}, []); 完整代码
import { useEffect, useRef } from react;
import * as THREE from three;
import Stats from three/examples/jsm/libs/stats.module.js;
import { GLTF, GLTFLoader } from three/examples/jsm/loaders/GLTFLoader.js;
import { OrbitControls } from three/examples/jsm/controls/OrbitControls;
import { RoomEnvironment } from three/examples/jsm/environments/RoomEnvironment.js;
import { DRACOLoader } from three/examples/jsm/loaders/DRACOLoader.js;// 初始化渲染器的函数
/*** 初始化 WebGL 渲染器* returns {THREE.WebGLRenderer} 创建并配置好的渲染器实例*/
// 初始化渲染
function initRender(): THREE.WebGLRenderer {// 创建一个WebGL渲染器const renderer new THREE.WebGLRenderer({ antialias: true });// 根据设备像素比设置渲染器像素比renderer.setPixelRatio(window.devicePixelRatio);// 设置渲染器大小renderer.setSize(window.innerWidth, window.innerHeight);return renderer;
}// 初始化场景的函数
/*** 初始化场景* param {THREE.WebGLRenderer} renderer - 渲染器实例* returns {THREE.Scene} 创建并配置好的场景实例*/
function initScene(renderer: THREE.WebGLRenderer) {// 创建 PMREM 生成器const pmremGenerator new THREE.PMREMGenerator(renderer);// 创建场景const scene new THREE.Scene();// 设置场景背景scene.background new THREE.Color(0xbfe3dd);// 设置场景环境scene.environment pmremGenerator.fromScene(new RoomEnvironment(renderer), 0.04).texture;return scene;
}// 初始化相机的函数
/*** 初始化相机* param {number} x - 相机在 x 轴的位置* param {number} y - 相机在 y 轴的位置* param {number} z - 相机在 z 轴的位置* returns {THREE.PerspectiveCamera} 创建并配置好位置的相机实例*/
function initCamera(x: number, y: number, z: number) {const camera new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);camera.position.set(x, y, z);return camera;
}// 初始化控制器的函数
/*** 初始化轨道控制器* param {THREE.PerspectiveCamera} camera - 相机实例* param {THREE.WebGLRenderer} renderer - 渲染器实例* returns {OrbitControls} 创建并配置好的轨道控制器实例*/
function initControls(camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer) {const controls new OrbitControls(camera, renderer.domElement);controls.update();controls.enablePan false;controls.enableDamping true;return controls;
}/*** Keyframes 组件函数*/
function Keyframes() {const containerRef useRefHTMLDivElement(null); // 创建用于引用 HTML 元素的 refconst clock new THREE.Clock(); // 创建时钟实例const statsRef useRefStats(); // 创建用于引用统计信息的 refconst mixerRef useRefTHREE.AnimationMixer(); // 创建用于引用动画混合器的 refconst renderer initRender(); // 初始化渲染器const scene initScene(renderer); // 初始化场景const camera initCamera(5, 2, 10); // 初始化相机const controls initControls(camera, renderer); // 初始化控制器controls.target.set(0, 0.5, 0); // 设置控制器的目标const dracoLoader new DRACOLoader(); // 创建 Draco 加载器dracoLoader.setDecoderPath(jsm/libs/draco/gltf/); // 设置 Draco 解码器路径const loader new GLTFLoader(); // 创建 GLTF 加载器loader.setDRACOLoader(dracoLoader); // 为 GLTF 加载器设置 Draco 加载器// 加载 GLTF 模型loader.load(models/gltf/LittlestTokyo.glb,(gltf: GLTF) {const model gltf.scene; // 获取模型的场景model.position.set(1, 1, 0); // 设置模型的位置model.scale.set(0.01, 0.01, 0.01); // 设置模型的缩放scene.add(model); // 将模型添加到场景mixerRef.current new THREE.AnimationMixer(model); // 创建动画混合器mixerRef.current.clipAction(gltf.animations[0]).play(); // 播放动画renderer.setAnimationLoop(animate); // 设置渲染循环},undefined,(e) {console.error(e); // 处理加载错误},);// 渲染循环函数/*** 每一帧的更新和渲染逻辑*/function animate() {const delta clock.getDelta(); // 获取时间间隔mixerRef.current mixerRef.current.update(delta); // 更新动画混合器controls.update(); // 更新控制器statsRef.current statsRef.current.update(); // 更新统计信息renderer.render(scene, camera); // 渲染场景和相机}// 处理窗口大小改变的函数/*** 处理窗口大小改变时的相机和渲染器更新*/function onWindowResize() {camera.aspect window.innerWidth / window.innerHeight; // 更新相机的宽高比camera.updateProjectionMatrix(); // 更新相机的投影矩阵controls.update(); // 更新控制器renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器的大小}// 使用 useEffect 钩子useEffect(() {if (!containerRef.current) return;containerRef.current.appendChild(renderer.domElement); // 将渲染器的 DOM 元素添加到引用的元素中statsRef.current new Stats(); // 创建统计信息实例containerRef.current.appendChild(statsRef.current.dom); // 将统计信息的 DOM 元素添加到引用的元素中window.addEventListener(resize, onWindowResize); // 添加窗口大小改变的监听事件return () {window.removeEventListener(resize, onWindowResize); // 清除窗口大小改变的监听事件renderer.setAnimationLoop(null); // 清除渲染循环};}, []);return div ref{containerRef}/div; // 返回一个带有 ref 的 div 元素
}export default Keyframes; // 导出 Keyframes 组件