一个好习惯,先给结论
最终效果如下:
在线体验地址:点我预览
代码地址:点我github
本文首发于:https://blog.gis1024.com/three_news_op_2.html
这里才是引言
本来预计第二章是写地球的创建的,但是在这之前还得写一下资源的预加载和三维场景的初始化,地球的创建只能放到第三章去啦。
资源预加载
预加载逻辑放到 preload.js
文件中,通过对three.js
自带的加载回调函数进行封装,改造成promise
形式。等所有promise
都完成后,再继续后续的操作。
我们将用到的红黄蓝色带、地球贴图、地球云层贴图、字体全都进行预加载。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import * as THREE from "three"; import { TTFLoader } from "three/examples/jsm/loaders/TTFLoader.js"; import { Font } from "three/examples/jsm/loaders/FontLoader";
const images = [ "/assets/rgb-r-small.png", "/assets/rgb-g-small.png", "/assets/rgb-b-small.png", ]; const list = images.map((item) => { return loadImage(item); }); const rgb = await Promise.all(list);
const textures = ["/assets/earthmap2k.jpg", "/assets/earthCloud.png"]; const textureList = textures.map((item) => { return loadTexture(item); }); const [earthTexture, cloudTexture] = await Promise.all(textureList);
const font = await loadTTF("/assets/minTFF/FangZhengHeiTiJianTi-1.ttf");
export { rgb, earthTexture, cloudTexture, font };
function loadImage(url) { return new Promise((resolve, reject) => { let img = new Image(); img.onload = () => resolve(img); img.onerror = reject; img.src = url; }); }
function loadTexture(url) { const loader = new THREE.TextureLoader(); return new Promise((resolve, reject) => { loader.load( url, (texture) => { resolve(texture); }, (progress) => {}, (e) => reject(e) ); }); }
function loadTTF(url) { const loader = new TTFLoader(); return new Promise((resolve, reject) => { loader.load( url, (json) => { const font = new Font(json); resolve(font); }, (progress) => {}, (e) => reject(e) ); }); }
|
有一点需要注意, preload.js
中用到了顶层 await
。在 dev
环境的时候是没事的,一切都正常,但是等到 build
时会报错,提示不能使用顶层 await
。
这是因为顶层 await
目前还是实验性质,属于比较新的特性,在 webpack
、 vite
等构建工具中都要进行设置才能正确构建。
vite
中需要这样设置,代表构建成下一代js:
1 2 3
| build: { target: 'esnext' }
|
这样可以打包,在最新chrome上也可以正确打开,但当我分享到iPhone手机上时,却死活打不开,整个裂开。
一番折腾后发现是手机safari浏览器不支持最新的js顶层await特性,裂开。
于是找了一个vite插件, vite-plugin-top-level-await
,按照其配置,可以将顶层await打包成常规js。其原理是将所有用到的顶层 await
进行一层封装,封装成 Promise.all
,再进行后续的操作,这里不赘述。有了这个插件,上面的build target配置也可以删除了。
三维初始化
在html中我们有
1
| `<div id="three"></div>`
|
我们在 initThree.js
进行three.js场景的初始化,也是各种水文(包括本文😁)中常提的三大件: scene
(场景) 、 camera
(相机) 、 renderer
(渲染器)。
额外的,还需要搞一个 control
(控制器,旋转视角方便调试), EffectComposer
(场景后处理,方便进行特效处理), TWEEN
(补间库,插入到render函数中,后面的动画才能动起来) 。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js"; import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js"; import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js"; import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js"; import { TWEEN } from "three/examples/jsm/libs/tween.module.min.js";
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, });
const dom = document.getElementById("three"); const domW = dom.offsetWidth; const domH = dom.offsetHeight;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.outputEncoding = THREE.sRGBEncoding; renderer.textureEncoding = THREE.sRGBEncoding;
renderer.setSize(domW, domH);
dom.appendChild(renderer.domElement);
const camera = new THREE.PerspectiveCamera(45, domW / domH, 1, 1000); camera.position.set(0, 0, 35);
const controls = new OrbitControls(camera, renderer.domElement); controls.maxDistance = 800; controls.autoRotateSpeed = 1.7; if (!import.meta.env.DEV) controls.enabled = false;
const composer = new EffectComposer(renderer); const renderPass = new RenderPass(scene, camera); composer.addPass(renderPass);
const effectFXAA = new ShaderPass(FXAAShader); effectFXAA.uniforms["resolution"].value.set( 1 / window.innerWidth, 1 / window.innerHeight ); composer.addPass(effectFXAA);
function animate() { controls.update(); requestAnimationFrame(animate); composer.render(); TWEEN.update(); } animate();
window.addEventListener("resize", () => { const dom = document.getElementById("three"); const domW = dom.offsetWidth; const domH = dom.offsetHeight; renderer.setSize(domW, domH); renderer.setPixelRatio(window.devicePixelRatio); composer.setSize(domW, domH); composer.setPixelRatio(window.devicePixelRatio); camera.aspect = domW / domH; camera.updateProjectionMatrix(); });
if (import.meta.env.DEV) { const helper = new THREE.AxesHelper(200); scene.add(helper); }
const light = new THREE.AmbientLight("#ffffff", 0.8); scene.add(light);
const light2 = new THREE.DirectionalLight("#ffffff", 0.5); light2.position.set(1, 1, 1).normalize(); scene.add(light2);
window.app = { scene, camera, renderer, controls, composer };
export { scene, camera, renderer, controls, composer };
|
本文首发于:https://blog.gis1024.com/three_news_op_2.html