欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

使用 three.js 渲染上海外滩模型

最编程 2024-03-15 11:17:53
...


记录一下用three.js加载并渲染上海外滩的BIM模型的小demo



<!DOCTYPE html><html lang="en">  <head>    <style>      body {        margin: 0;      }      section {        position: fixed;        top: 0;      }</style>    <script src="lib/three.js"></script>    <script src="lib/OrbitControls.js"></script>    <script src="lib/GLTFLoader.js"></script>    <script src="lib/Water.js"></script>  </head>
<body> <script src="index.js"></script>
<section>天空:<input type="checkbox" onchange="skyToggle()" /></section> </body></html>



用到的three官方库:

  • three.min.js:THREE.js WebGL引擎

  • OrbitControls.js:轨道控制,鼠标控制视角变幻

  • GLTFLoader.js:gltf加载库

  • Water.js:水面效果,纯glsl实现



天空的实现:

天空的实现有多种方式,最常见的是一个包围全部的天空球,通常是UV球,也叫经纬球,其UV很方便映射到一张天空图片,比如:




天空球的所有面的法线必须朝向圆心(默认是朝外),或者渲染的时候采用背面渲染。


第二种方式是天空盒,即将上述的天空球变成一个正方体盒子,好处是减少了许多三角面片,只剩12个面,但通常要准备上下左右前后6张图片来贴合天空盒。比如这样:




与这2种方法相比,性能最好的方案是静态天空球(盒),即理想情况下的宇宙背景,天空球的半径无限大,导致渲染的时候,天空不会因为相机的移动而变化,只随旋转而变化,这样减少了许多计算量。


静态天空球就是360度全景摄像机的原理,它和墨卡托投影有点类似,但是正轴等距圆柱投影,想象一个经纬球,它的经纬线自然展开,UV坐标如下:






可以看到,图中每个矩形的宽高比是1:2,应该把它们都拉伸成正方形,因为赤道:经线=2:1,这样UV贴图的宽高比就是2:1,比如:



图中红线是赤道(圆周),每条竖线是经线(一半圆周),每条纬线都被拉伸至赤道长度。可以看到两极地区被拉伸严重,靠近赤道地区比例正常。


然后将它通过圆柱投影到球面上,或者转换成6张图作为正方体,作为静态天空,代码如下:


async function skyToggle() {  if (scene.bgURL === "light.png") {    scene.bgURL = "dark.png";  } else {    scene.bgURL = "light.png";  }
const texture = await new THREE.TextureLoader().loadAsync(scene.bgURL);
const rt = new THREE.WebGLCubeRenderTarget(texture.image.height); rt.fromEquirectangularTexture(renderer, texture);  scene.background = rt.texture;}



然后是加载gltf模型:


const loader = new THREE.GLTFLoader();const modelLoaded = loader  .loadAsync("shanghai.glb"function (xhr{    console.log((xhr.loaded / xhr.total) * 100 + "% loaded");  })  .then(function (gltf) {    console.log("shanghai model:", gltf);    const { min, max } = new THREE.Box3().setFromObject(gltf.scene);    gltf.scene.position.sub(      new THREE.Vector3((min.x + max.x) / 2, min.y, (min.z + max.z) / 2)    );
    scene.add(gltf.scene);
gltf.scene .getObjectByName("castShadow") .traverse((x) => x.isMesh && (x.castShadow = true)); gltf.scene .getObjectByName("ground") .traverse((x) => x.isMesh && (x.receiveShadow = true)); }) .catch(function (error) { console.error(error); });


其中让所有的大厦投影,让地面接收投影,实现真实的效果,又避免了所有物体都投影。

其中增加了一个视频plane,作为震旦大厦的大屏动画,plane的4个顶点的UV就是图片的4个角:


const video = document.createElement("video");    video.src = "test2.mp4";    video.loop = true;    video.muted = true;    video.playbackRate = 0.6;    video.play();
const videoPlane = gltf.scene.getObjectByName("video");
videoPlane.material = new THREE.MeshBasicMaterial({ map: new THREE.VideoTexture(video), }); videoPlane.geometry.attributes.uv = new THREE.BufferAttribute( new Uint8Array([0, 0, 1, 0, 0, 1, 1, 1]), 2 );


视频:



增加海水:


new THREE.TextureLoader().loadAsync("waternormals.jpg").then((waterNormals) => {  waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;
const water = new THREE.Water(new THREE.PlaneGeometry(1000, 1000), { textureWidth: 512, textureHeight: 512, waterNormals, sunDirection: new THREE.Vector3(), sunColor: 0xffffff, waterColor: 0x001e0f, distortionScale: 3.7, fog: !!scene.fog, }); water.material.uniforms.size.value = 10; water.rotation.x = -Math.PI / 2;
scene.add(water); tasks.push(() => { water.material.uniforms.time.value += 0.005; });});



海水法线贴图:



最后再加1个环境光,开启环境光遮蔽,再加一个平行光就好了:






视频来源:https://blog.****.net/goodriver1

推荐阅读